Slides
Next: More Blocks >>

Iterators

Ref. WGR Section 6.3, "Iterators and code blocks"

Loops as Methods

  • An Iterator is a method that allows you to loop through all the members of a collection
  • Works like "for" or "while" but without any extra language keywords
  • It executes a block again and again
  • Usually this block is the default block
    • this lets you define the block during the method call
    • concise and readable (arguably)

times is on your side

potatoes = nil
3.times do |i|
  potatoes = i+1
  puts "#{potatoes} potato"
end
puts potatoes + 1

prints

1 potato
2 potato
3 potato
4

implementing times using until

def times x
  i = 0
  until i == x
    yield i
    i += 1
  end
  x
end

(times returns the number itself)

to each his own

["apple", "banana", "cherry"].each do |fruit|
  puts "I love #{fruit}!"
end

warning: each returns the collection, not the value

def count_chars a
  c = 0
  a.each do |s|
    c += s.length   # this returns c
  end               # but this returns a
  c                 # so this returns c (again)
end

count_chars ["apple", "banana", "cherry"]
=> 17

implementing each using until

def each a
  i = 0
  until i == a.size
    yield a[i]
    i += 1
  end
  a
end

the map is not the territory

["apple", "banana", "cherry"].map do |fruit|
  fruit.reverse
end
=> ["elppa", "ananab", "yrrehc"]

implementing map using each

def map input
  a = []
  input.each do |item|
    a << yield(item)
  end
  a
end

Other Awesome Iterators

  • select (alias find_all)
    • returns all items for which the block returns true(ish)
  • reject
    • returns all items for which the block returns false(ish)
  • collect (alias map)
    • makes a new array out of whatever the block returns
  • detect (alias find)
    • returns a single item, not an array
    • returns the first item which makes the block return true(ish)
  • inject
    • accumulates (huh?) -- more on this later

Ref. Using Select Etc.

lethal injection

  • inject is a really fun iterator, but it's really weird
  • it passes a persistent "accumulator" to each iteration
  • the return value of the block becomes the next accumulator

  • "inject" is also called "reduce", "fold", and "accumulate" in other languages

inject example

  class Array
    def sum
      self.inject(0) do |total, current| 
        total + current
      end
    end
  end

  [1,2,3].sum #=> 6
  • To help understand this, write out a table with the values of total, current, and the return value for each iteration.

inject reduced

You can also send inject (or reduce) the name of a method only:

class Array
  def sum
    self.reduce(:+)
  end
end

[1,2,3].sum #=> 6

Here we are "reducing" the array by calling + on all its elements in succession.

more help

comments powered by Disqus