Ruby, Day 2
July 15, 2012
My notes on day 2 of Ruby from Seven Languages in Seven Weeks by Bruce Tate.
Most of the section focuses elsewhere, but my curiosity was piqued by the brief note about functions at the beginning. It looks like Ruby has the equivalent of C's function pointers. Also, like everything else functions are first class objects in Ruby with methods, etc.
The convention with code blocks is too use {} for one-liner and do-end otherwise. There's no do-end example, let's try to make one:
In case you haven't realized, I'm showing how the sausage gets made by leaving in my mistaken guesses.
The yield feature looks pretty interesting, let's try to do something with it. I ran into some issues by putting an extraneous do after my whiles but there's the end result:
Figure 2.1 could use a little work, it is not exactly kosher UML. But if you are confused, the lefts leading left are the "is a" relationship, i.e. Numeric is a Class and the arrows leading up are the "extends" relationship, i.e. Fixnum extends Integer. Which begs the question, what about floating point numbers?
Of course upon further reading I see that there is a simple way to do what I tried to do with the calculate example above using inject. I suppose that's the point of Ruby, there's always an elegant way.
Time to take on the exercises. First, accessing files with and without code blocks. Using File.open with a code block has the clear advantage of closing the file when the block completes. An elegant solution for Ruby again.
Given that the Hash class has a to_a method for converting it to an array, that might be the way to go. There is also Hash.keys and Hash.values. In the other direction, I don't see an obvious method on Array so how about:
Doing 4 at a time using each_slice is doable:
But I'm not sure I see how to do it with just Array.each. With Array.each_index it is not hard:
I suppose I could do it if I kept track of the index manually but then why am I using each?
In any case, here's my modified Tree initializer, this was pretty straightforward:
A simple grep clone wasn't too bad either, although I wouldn't call it robust in its current form:
Most of the section focuses elsewhere, but my curiosity was piqued by the brief note about functions at the beginning. It looks like Ruby has the equivalent of C's function pointers. Also, like everything else functions are first class objects in Ruby with methods, etc.
The convention with code blocks is too use {} for one-liner and do-end otherwise. There's no do-end example, let's try to make one:
irb(main):006:0> 3.times
=> #
irb(main):007:0> 3.times do
irb(main):008:1* puts 'tastes great'
irb(main):009:1> puts 'less filling'
irb(main):010:1> end
tastes great
less filling
tastes great
less filling
tastes great
less filling
=> 3
In case you haven't realized, I'm showing how the sausage gets made by leaving in my mistaken guesses.
The yield feature looks pretty interesting, let's try to do something with it. I ran into some issues by putting an extraneous do after my whiles but there's the end result:
irb(main):103:0> class Fixnum
irb(main):104:1> def calculate
irb(main):105:2> n = 0
irb(main):106:2> while n < self
irb(main):107:3> n = n + 1
irb(main):108:3> yield n
irb(main):109:3> end
irb(main):110:2> end
irb(main):111:1> end
=> nil
irb(main):112:0> x = 0
=> 0
irb(main):113:0> 3.calculate {|n| x = x + n }
=> nil
irb(main):114:0> x
=> 6
irb(main):115:0> x = 0
=> 0
irb(main):116:0> 1000.calculate {|n| x = x + n }
=> nil
irb(main):117:0> x
=> 500500
irb(main):118:0> x = 1
=> 1
irb(main):119:0> 10.calculate {|n| x = x * n }
=> nil
irb(main):120:0> x
=> 3628800
Figure 2.1 could use a little work, it is not exactly kosher UML. But if you are confused, the lefts leading left are the "is a" relationship, i.e. Numeric is a Class and the arrows leading up are the "extends" relationship, i.e. Fixnum extends Integer. Which begs the question, what about floating point numbers?
irb(main):121:0> 3.14149.superclass
NoMethodError: undefined method `superclass' for 3.14149:Float
from (irb):121
from /usr/bin/irb:12:in `'
irb(main):122:0> 3.14149.class
=> Float
irb(main):123:0> 3.14149.class.superclass
=> Numeric
Of course upon further reading I see that there is a simple way to do what I tried to do with the calculate example above using inject. I suppose that's the point of Ruby, there's always an elegant way.
Time to take on the exercises. First, accessing files with and without code blocks. Using File.open with a code block has the clear advantage of closing the file when the block completes. An elegant solution for Ruby again.
Given that the Hash class has a to_a method for converting it to an array, that might be the way to go. There is also Hash.keys and Hash.values. In the other direction, I don't see an obvious method on Array so how about:
irb(main):124:0> a = ['b','e','m','u','s']
=> ["b", "e", "m", "u", "s"]
irb(main):125:0> h = {}
=> {}
irb(main):126:0> a.each_index {|i| h[i] = a[i]}
=> ["b", "e", "m", "u", "s"]
irb(main):127:0> h
=> {0=>"b", 1=>"e", 2=>"m", 3=>"u", 4=>"s"}
Doing 4 at a time using each_slice is doable:
irb(main):141:0> a.each_slice(4) do |x|
irb(main):142:1* x.each {|y| print y, ' '}
irb(main):143:1> puts
irb(main):144:1> end
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
But I'm not sure I see how to do it with just Array.each. With Array.each_index it is not hard:
irb(main):149:0> a.each_index do |i|
irb(main):150:1* print a[i], ' '
irb(main):151:1> puts if (i + 1) % 4 == 0
irb(main):152:1> end
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
I suppose I could do it if I kept track of the index manually but then why am I using each?
irb(main):003:0> i = 0
=> 0
irb(main):004:0> a.each do |x|
irb(main):005:1* print x, ' '
irb(main):006:1> puts if (i + 1) % 4 == 0
irb(main):007:1> i += 1
irb(main):008:1> end
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
In any case, here's my modified Tree initializer, this was pretty straightforward:
def initialize(name, nodes)
@node_name = name
@children = []
nodes.each {|key,value| @children.push(Tree.new(key, value))}
end
A simple grep clone wasn't too bad either, although I wouldn't call it robust in its current form:
#!/usr/bin/env ruby
re = Regexp.new(ARGV[0])
ARGV.slice(1,ARGV.length).each do |filename|
File.open(filename) do |file|
line_no = 1
file.readlines.each do |line|
puts "#{filename}(#{line_no}):#{line}" if re.match(line)
line_no += 1
end
end
end