with<Programming Ruby>
Chapter 4
Containers, Blocks, and Iterators
Containers
Arrays
a = [ 3.14159, "pie", 99 ]
a.class Array
→
a.length 3
→
a[0] 3.14159
→
b = Array.new
b.length →0
b[0] = "second"
b[1] = "array"
b → ["second", "array"]
Positive → 0 Negative
1 2 3 4 5 6
indices −1 ← indices
−7 −6 −5 −4 −3 −2
a = “ant” “bat” “cat” “dog” “elk” “?y” “gnu”
a[2] → “cat”
a[-3] → “elk”
a[1..3] → “bat” “cat” “dog”
a[-3..-1] → “elk” “?y” “gnu”
a[4..-2] → “elk” “?y”
the index to [ ]= is two numbers (a start and a length) or a range
a = [ 1, 3, 5, 7, 9 ] [1, 3, 5, 7, 9]
→
a[2, 2] = ’cat’ [1, 3, "cat", 9]
→
[1, 3, "dog", "cat", 9]
a[2, 0] = ’dog’ →
[1, 9, 8, 7, "dog", "cat", 9]
a[1, 1] = [ 9, 8, 7 ] →
a[0..3] = [] ["dog", "cat", 9]
→
a[5..6] = 99, 98 ["dog", "cat", 9, nil, nil, 99, 98]
→
Hashes (associative arrays, maps, or dictionaries)
h = { 'dog' => 'canine', 'cat' => 'feline', 'donkey' => 'asinine' }
h.length →3
h['dog'] → "canine"
h['cow'] = 'bovine'
h[12] = 'dodecine'
h['cat'] = 99
h → {"cow"=>"bovine", "cat"=>99, 12=>"dodecine",
"donkey"=>"asinine", "dog"=>"canine"}
Implementing a SongList Container
class SongList
def append(song)
@songs.push(song)
self
end
end
class SongList
def delete_first
@songs.shift
end
def delete_last
@songs.pop
end
end
class SongList
def [](index)
@songs[index]
end
end
Blocks and Iterators
class SongList
def with_title(title)
@songs.find {|song| title == song.name }
end
end
find is an iterator
iterator: a method that invokes a block of code repeatedly
def three_times
yield
yield
yield
end
three_times { puts "Hello" }
produces:
Hello
Hello
Hello
i1, i2 = 1, 1 # parallel assignment (i1 = 1 and i2 = 1)
def fib_up_to(max)
i1, i2 = 1, 1 # parallel assignment (i1 = 1 and i2 = 1)
while i1 <= max
yield i1
i1, i2 = i2, i1+i2
end
end
fib_up_to(1000) {|f| print f, " " }
if they appear for the ?rst time in the block, they’re local to the block. If instead they ?rst appeared outside the block, the variables will be shared between the block and the surrounding environment.
a = [1, 2]
b = 'cat'
a.each {|b| c = b * a[1] }
a [1, 2]
→
b 2
→
defined?(c) nil
→
class Array
def find
for i in 0…size
value = self[i]
return value if yield(value)
end
return nil
end
end
[1, 3, 5, 7, 9].find {|v| v*v > 30 } 7
→
[ 1, 3, 5, 7, 9 ].each {|i| puts i }
["H", "A", "L"].collect {|x| x.succ } ["I", "B", "M"]
[1,3,5,7].inject(0) {|sum, element| sum+element} 16
→
[1,3,5,7].inject(1) {|product, element| product*element} 105
→
if inject is called with no parameter,it uses the ?rst element of the collection as the initial value and starts the iteration with the second value.
[1,3,5,7].inject {|sum, element| sum+element} 16
→
[1,3,5,7].inject {|product, element| product*element} 105
→
Blocks for Transactions
class File
def File.open_and_process(*args)
f = File.open(*args)
yield f
f.close()
end
end
*args, meaning “collect the actual parameters passed to the method into an array named args
Blocks Can Be Closures
songlist = SongList.new
class JukeboxButton < Button
def initialize(label, &action)
super(label)
@action = action
end
def button_pressed
@action.call(self)
end
end
start_button = JukeboxButton.new("Start") { songlist.start }
pause_button = JukeboxButton.new("Pause") { songlist.pause }
&action,Ruby looks for a code block whenever that method is called. That code block is converted to an object of class Proc and assigned to the parameter. You can then treat the parameter as any other variable. In our example, we assigned it to the instance variable @action. When the callback method button_pressed is invoked, we use the Proc#call method on that object to invoke the block.
method lambda, which converts a block to a Proc object.
def n_times(thing)
return lambda {|n| thing * n }
end
p1 = n_times(23)
p1.call(3) → 69
p1.call(4) → 92
p2 = n_times("Hello ")
p2.call(3) → "Hello Hello Hello "