1up-ruby

Download as .zip Download as .tar.gz View on GitHub

Welcome

Hello and welcome to 1-UP's Ruby workshop. Whether you're entirely new to programming, new to the Ruby language or just want to learn something new - we hope that you will get something of benefit.

You can work alone, or in pairs. Pair programming is a very good technique that is used alot in Agile teams. While it may seem like you'd get less work done with two programmers per computer, just think how much of your programming time are you spending actually typing and how much are you spending thinking.

Solutions and random snippets are available by the links above / GitHub. Among these are the solutions to the tasks. It would be possible to read them and to 'cheat', but this would probably just hurt your own learning.

You can see how to install Ruby on you operating system of choice here.

If you can't install Ruby on your local machine, you can use an online version here.

Basic syntax

First off let's take a look at some basic methods in Ruby. puts() is used to print text to the screen. Try typing this in into a file called anything.rb and running it with ruby anything.rb.

puts("Hello World!")
puts "Hello world!"

We can see that "Hello World!" is printed twice, even though the second time the brackets were missing! In Ruby methods which take only one argument don't need brackets - this might seem weird if you're coming from other languages, but similar things happen in many languages it is often just that we don't think about what is happening behind the scenes.

# Print out some arithmetic
puts 1+2
puts 1.+(2)

These two lines do exactly the same thing. As it turns out the operators we use all the time are just methods like anything else and can be called with that standard thing1.method(argument) syntax. The process of making an easier to read form of elements of a programming language is called sugar. Ruby is a very sugary language - I'd argue this makes it pretty sweet to write ;)

Time for Task 1

Write a program to print out your own name, taken from standard input. In order to make this workshop a little less of a walkthrough, I'm going to try to avoid just giving you code to run and instead give you the building blocks and let you come up with a solution, this is to make it less tedious for those with some (or lots!) of programming experience. If you are new, and find yourself getting stuck be sure to ask for help!

Read the following snippet, and see if you can print out a greeting to yourself, giving your name from standard input in just one line.

someInput = gets.chomp
# gets is used to get a line of input from standard in
# chomp is used to 'eat up' newline characters off of strings

string1 = "Ruby"
string2 = "open source"

puts "#{string1} is completely #{strings2}"
# #{variable} is used to interpolate (include) variables in a string.
# If you're used to other languages (Python, Java, C*), Ruby also supports interpolation in their style.
puts "%s is completely %s" % [string1, string2]

Assuming you've got a solution for that (don't worry if it's a few lines) let's compare that to the same thing in Java.

import java.io.*;
public class example
{
  public static void main (String[] args)
  {
    try
    {
      BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
      String name;
      name = stdin.readLine();
      System.out.println("Hello " + name);
    } catch (IOException e) {
      System.err.println("IOException: " + e.getMessage());
    }
  }
}

A lot longer and harder to understand right? Maybe this will give an insight into why ruby programmers start preferring it so much over other languages. While this is a tiny example, the extra readability and ease of writing can make a huge difference in big projects!

Collections

In programs we often want to have groups of things, the main ways we represent this is with arrays and hashes. Hashes are also called maps, dictionaries and hashmaps in other languages.

animals = ["dog", "cat", "hamster"]
puts animals

people = ["charlie", "mark", "dave"]
puts people[0]
puts people.first
puts people

numbers = [78, 0, 47.8, -100, 1/0.0, Math::PI]
numbers.push 3      # Add three to the end of the array
puts numbers.sort
puts numbers.first
puts numbers.max
# Arrays come with some useful methods

mixed = [78, nil, "charlie"]
puts mixed
# We can easily mix types in arrays (as everything is an object)

petHash = {"charlie" => "dog", "mark" => "cat", "dave" => "hamster"}
puts petHash.keys
puts petHash.values
puts petHash["charlie"]
puts petHash["dave"]

petHash["lewis"] = "snake"
puts petHash

If we wanted to do some simple operations, like put the two arrays animals and people together to make the hash petHash we normally might think in terms of loops. Going through every element in the arrays and adding them into a hash. In Ruby this is not very idiomatic - in fact it may shock you to learn that Ruby has no For construct!

petHash = Hash[people.zip(animals)]
# This is (one of the ways) how we can do the above in Ruby.

Control Flow

To start simply, Ruby features the standard if, while and case statements. Though with a few little differences.

if 2.even?                             # methods ending in ? return booleans!
  puts "Even!"
elsif 2.zero?
  puts "Something is wrong..."
else
  puts "Odd?!"
end

x = 0
while x < 10
  x = x + 1
end

print "Enter your favourite color: "

case gets.chomp.downcase
when "red"
  puts "Red as Rubies!"
when "green"
  puts "Green as grass!"
when "blue"
  puts "Blue as the sky!"
else                                    # default case
  puts "I only like primary colours..."
end

All pretty standard stuff, you may notice the statements terminate in end - this is to delimit the statements and is simply intrinsic to the syntax. I personally find it preferable to the Python scheme of using whitespace for this. The case and when being at the same level of indentation is purely stylistic - but this style is the preferred one. Now for how these constructs differ in little in Ruby!

puts "Statements work inline!" if 2.even?

result = if 2.even?
           ["Ifs are expressions, ",
            "they return the last ",
            "expression that is evaluated."]
         else 2.odd?
           "Will never eval"
         end
puts result.join

puts x = x + 1 while x < 10

# Opposite of while
until x.zero?
  puts x = x - 1
end

# Opposite of if
unless 2.odd?
  puts "Even!"
end

Sometimes these can get confusing, sometimes they can make code more readable or concise. When you use them is up to you.

Task 2

The program Greetings should loop through names taken from standard input and print a greeting to each person.

It should work like this:

Enter a name: Charlie

Hello Charlie!

Enter a name: Lewis

Hello Lewis!

Enter a name: Nathan

Hello Nathan!

Where's my For loop?!?

Before I mentioned that Ruby has no For loop. This might seem very bizarre, but many of you will already be familiar with the styles of iteration that are utilised in Ruby (though perhaps not how they are implemented). Ruby extensively uses enumerables - these are objects which move successively through states. They can be used in multiple ways, and we can even define our own though for now we will just look at basic ones.

10.times # Not very useful at the moment...

10.times { puts "Loops!" }

do end, and { } are (very nearly) exactly the same thing. { } is preferred for single line use, and do end for multi. They are called blocks and though they may seem simple, they are one of Ruby's most powerful assets. They are NOT the same as compound statements in C-like languages.

1..100   # A range from 1-100, a type of collection
1...100  # A range from 1-99

(1..10).each {|x| puts x*x*x} # Print the cube of all numbers from 1-10

each takes a collection, such as a range, an array or a hash and passes each item in the collection successively to the block. How to refer to the items passed from the collection is specified between the pipes | |. Let's see something using a few of the things we've seen so far.

animals = ["dog", "cat", "hamster"]
people = ["charlie", "mark", "dave"]
petHash = Hash[people.zip(animals)]

petHash.each do |person, pet| 
  puts "#{person.capitalize} loves their #{pet}"
end

Task 3

Modify the program Greetings to take a list of names passed as command line arguments like this: ruby greetings.rb charlie lewis nathan

It should print out:

Hello Charlie!

Hello Lewis!

Hello Nathan!

You will need:

ARGV         # The array containing all your command line arguments.

Task 4

The program FizzBuzz should take as an argument an integer to be passed via the command line like this: ruby fizzbuzz.rb 20

It should print out Fizz if the number is divisible by 3, Buzz if it is divisible by 5, FizzBuzz if it is divisible by 15 and the number itself if it is divisible by neither 3 nor 5. This should be doable with what we've seen already, plus a couple of things I'll demonstrate below. Feel free to try to write this in pairs.

x.modulo(y)  # The remainder of dividing x by y
"3".to_i     # String to integer

Modifying collections

Collections can be operated on using some wonderful methods influenced by functional programming.

anArray = (1..100).to_a

# The line below is just showing what will be returned from the line above.
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]

anArray.select {|x| x.even? }
=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100]

anArray
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]

Here we can see the select method, it takes a collection and returns another collection depending on the result of the block. Every element (x), where x.even? is true is part of the new collection.

Notice how when we looked at the array again, it hadn't changed. We can update the collection to reflect the change it in two ways.

anArray = anArray.select {|x| x.even? }

anArray
=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100]

Now we can see the new collection has been assigned to the variable which used to hold the reference to the former collection.

anArray = (1..100).to_a
anArray.select! {|x| x.even? }

anArray
=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100]

Appending the ! to select actually called a different method. In Ruby not only are ?s allowed in method names, but also !. It is conventioned that methods ending in ! have a normal version, and a version which does something unexpected. In this case the unexpected thing is that it modifies the initial array. A method ending in ! is called a bang method.

Now let's look at a couple of other functional methods.

(1..10).map{|x| x*x}
=> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

(1..10).reduce(0){|sum, elem| sum + elem}
=> 55

Map creates a new collection based on the execution of the block on each element.

Reduce takes an argument, a seed to set the first parameter to, in this case it is zero. The first argument to the block (the seed) gets passed to each new iteration, at the end this argument is returned not the collection.

Because select and map take a collection, and return a collection we can chain them together to perform multiple operations in a row.

array.map{|x| x.something}.select{|x| x.condition?}.reduce(1){|x, y| x.somethingElse(y)}

Try combining some of these things, along with trying to use some of their bang methods and see what happens!

Task 5

Take two integers from the command line. First make a collection from 1 up to the first argument. Remove all odd numbers. Square all remaining numbers, remove all numbers which are not divisible by the second argument then finally print the sum of the collection.

Try to do this in as few lines as you can.

ruby sumthing.rb 1000 78
3954600