Seven Languages Seven Weeks: Ruby

Ruby

Installation

To install ruby on a debian machine just run

sudo apt-get install ruby

To run the interpreter and develop in an interactive way you could run

user@machine> irb

And start to type some code

irb(main):001:0> a=12
=> 12

Ruby File

To run the file from the shell you can do

ruby file.rb

Or you can tell the shell to prepend the ruby interpreter with

#!/usr/bin/env ruby

on the beginning of the file

And then set the file to executable mode

chmod +x file.rb

Exercises

Day 1
  • Print the string hello world

def hello_world()
    print "Hello World\n"
end

  • For the string Hello Ruby find the index of the word Ruby

def find_ruby
    "Hello Ruby".index("Ruby").to_s+"\n"
end

  • Print your name ten times

Considering the functional method

def times_n(f,e)
    i=0
    until i==e
        f.call(i)
        i=i+1
    end
end

And the callback

def print_name(i)
    print "Seven Languages Seven Weeks\n"
end

We can do

times_n(method(:print_name),10)

  • Print the string This is sentence number 1, where the number 1 changes from 1 to 10.

Defining the callback

def print_num(i)
    print "This is sentence number: "+i.to_s+"\n"
end

we can call

times_n(method(:print_num),10)

  • Bonus problem: If you’re feeling the need for a little more, write a program that picks a random number. Let a player guess the

    number, telling the player whether the guess is too low or too high.

def guessWhat()
    guess=rand(10)
    solution="-1"
    puts "Find the number between 0 and 9"
    until solution.to_i==guess
        solution=gets.chomp
        if solution.to_i < guess
            puts "Too low"
        else
            puts "Too high"
        end
    end
    puts "Congratulation you find it"
end

And then call guessWhat()


Day 2
  • Find out how to access files with and without code blocks. What is the benefit of the code block?

Without the code block the code look like this

def saveNoCodeBlock
    file=File::open("data/nocodeblock.txt","w")
    file << "Data in no code block mode\n"
    file.close
end

The same could be obtained with code blocks

def saveWithCodeBlock
      File::open("data/codeblock.txt","w") do |file|
            file << "Data in code block mode\n"
      end
end

  • How would you translate a hash to an array? Can you translate arrays to hashes?

The following code examples the conversion between the two structures

def hashVsArray
    hash1={"pt"=>"Portugal","en"=>"England"}
    arr=hash1.to_a      
    arr.each do |el|
        puts el[0].to_s+"-"+el[1].to_s
    end
      hash2=Hash[arr.map {|x| [x[0], x[1]]}]
      hash2.each do |k,v|
            puts k.to_s+"-"+v.to_s
      end
end

  • You can use Ruby arrays as stacks. What other common data structures do arrays support?

Besides a stack Last in First Out mechanism we've got the deque

def arrayAsDeque
      arr=(1..10).to_a
      until array.size ==0
            puts array.shift
      end
end

Here we use the shift (method of array class) to pop from the other side of the stack and therefore making this a double ended queue


  • Print the contents of an array of sixteen numbers, four numbers at a time, using just each . Now, do the same with each_slice in Enumerable .

Without the each_slice method we must control the iteration by blocks manually as you find here

def array16NumbersEach
      i=0
      a=(1..16).to_a
      a.each do |x|
            if i<=3
                  puts a[i*4].to_s+","+a[i*4+1].to_s+","+a[i*4+2].to_s+","+a[i*4+3].to_s
            end
            i=i+1
      end
end

While this works the mechanism with each_slice is much less verbose

def array16NumbersEachSlice
      (1..16).each_slice(4) do |el|
            p el
      end
end

  • The Tree class was interesting, but it did not allow you to specify a new tree with a clean user interface. Let the initializer accept a nested structure with hashes and arrays. You should be able to specify a tree like this: {’grandpa’ => { ’dad’ => {’child 1’ => {}, ’child 2’ => {} }, ’uncle’ => {’child 3’ => {}, ’child 4’ => {} } } }.

The only change needed to be able to specify the Tree as a dictionary is on the initializer so it becomes

class Tree
    #Define attributes for the nodes
      attr_accessor :children,:node_name

    def initialize(hash={})
        #Forall the dictionary elements
            hash.each do |key,value|
              @node_name = key
            #Map the elements into a new Tree object recursively
                  @children = value.map{|k,v| Tree.new(k=>v)}
            end
      end

    def visit(&block)
        block.call self
    end

    def visit_all(&block)
        visit(&block)
        children.each {|c| c.visit_all &block}
    end
end

The following method example the use of the class with the hash object provided as exercise

def treeExample
    treeHash={'grandpa' => { 'dad' => {'child 1' => {}, 'child 2' => {} }, 'uncle' => {'child 3' => {}, 'child 4' => {}}}}
    tree=Tree.new(treeHash)
        tree.visit_all{|t| p t.node_name}
end

  • Write a simple grep that will print the lines of a file having any occurrences of a phrase anywhere in that line. You will need to do a simple regular expression match and read lines from a file. (This is surprisingly simple in Ruby.) If you want, include line numbers.

All that is needed is some way of recognize a pattern, this is done with the Regexp Ruby object, and to process line by line the document, the method is here

def fileGrep(pattern, file)
      lnum=0
      matches=[]
      lines=[]
      File.open(file).each do |l|
            if [l].grep(Regexp.new(pattern))!=[]
                  lines.push(lnum)
            end
        lnum=lnum+1
      end
    puts "#{lines.size} lines of #{lnum} have the pattern '#{pattern}'"
    puts "lines #{lines}"
end

Day 3
  • Modify the CSV application to support an each method to return a CsvRow object. Use method_missing on that CsvRow to return the value for the column for a given heading.

On day 3 we talk about metaprogramming. We were provided with a book example that build code from a csv file:

module ActsAsCsv
    def self.included(base)
        base.extend ClassMethods
    end

    module ClassMethods
        def acts_as_csv
            include InstanceMethods
        end
    end

    module InstanceMethods
        def read
            @csv_contents = []
            filename = self.class.to_s.downcase + '.txt'
            file = File.new(filename)
            @headers = file.gets.chomp.split(', ' )
            file.each do |row|
                @csv_contents << row.chomp.split(', ' )
            end
        end

        attr_accessor :headers, :csv_contents

        def initialize
            read
        end
    end
end

class RubyCsv # no inheritance! You can mix it in
    include ActsAsCsv
    acts_as_csv
end

m = RubyCsv.new
puts m.headers.inspect
puts m.csv_contents.inspect

From were we were asked to provide a way to iterate over rows in this way

m.each do |row|
    puts "#{row.Name} has #{row.Age} years old and is #{row.Sex}"
end

Note that we are assuming here the csv file with this contents

Name, Age, Sex
John,23,Male
Claire,19,Female
Arthur,30,Male
Jack,15,Male

The needed changes involved the creation of new class

class CsvRow

      attr_accessor :row

     def initialize(headers,row)
            @headers=headers
            @row=row
      end

      def method_missing name, *args
            col=name.to_s
            i=0
            num=-1
            pos=0
            eq=false
            while i < @headers.length
                  if @headers[i].to_s == col
                    num=i
                  end
                  i=i+1
            end

            if num!=-1
                  @row[num]
            else
                  nil
            end
      end
end

On the book read method we need to change row iteration into

file.each do |row|
    @csv_contents.push(CsvRow.new(@headers,row.chomp.split(',')))
end

Ruby Week Conclusions

Ruby is a very versatile language. With awesome features for metaprogramming and very adequate to build custom DSL. It is a fun language and clean.

Ruby is pure object oriented and virtually everything on the language can be overriden. Ruby is oriented towards productivity and this also means that the language is not so performant as other languages can be.

Some considerations must be done, however. Ruby object oriented model is based on a wrapping mechanism around state and the state can change. This is a very problematic situation for concurrent programming. Finally ruby is a duck typing language this able you to build more clean and readable code, but with a price. The missing of static typing make more difficult to build syntactic trees as those needed to IDE integration.


More at Balhau@Gitlab