Changing the Way an Object Iterates

Problem

You want to use a data structure as an Enumerable, but the object's implementation of #each doesn't iterate the way you want. Since all of Enumerable's methods are based on each, this makes them all useless to you.

Discussion

Here's a concrete example: a simple array.

	array = %w{bob loves alice}
	array.collect { |x| x.capitalize }
	# => ["Bob", "Loves", "Alice"]

Suppose we want to call collect on this array, but we don't want collect to use each: we want it to use reverse_each. Something like this hypothetical collect_reverse method:

	array.collect_reverse { |x| x.capitalize }
	# => ["Alice", "Loves", "Bob"]

Actually defining a collect_reverse method would add significant new code and only solve part of the problem. We could overwrite the array's each implementation with a singleton method that calls reverse_each, but that's hacky and it would surely have undesired side effects.

Fortunately, there's an elegant solution with no side effects: wrap the object in an Enumerator. This gives you a new object that acts like the old object would if you'd swapped out its each method:

	require 'enumerator'
	reversed_array = array.to_enum(:reverse_each)
	reversed_array.collect { |x| x.capitalize }
	# => ["Alice", "Loves", "Bob"]

	reversed_array.each_with_index do |x, i|
	 puts %{#{i}=>"#{x}"}
	end
	# 0=>"alice"
	# 1=>"loves"
	# 2=>"bob"

Note that you can't use the Enumerator for our array as though it were the actual array. Only the methods of Enumerable are supported:

	reversed_array[0]
	# NoMethodError: undefined method '[]' for #

 

Discussion

Whenever you're tempted to reimplement one of the methods of Enumerable, try using an Enumerator instead. It's like modifying an object's each method, but it doesn't affect the original object.

This can save you a lot of work. Suppose you have a tree data structure that provides three different iteration styles: each_prefix, each_postfix, and each_infix. Rather than implementing the methods of Enumerable for all three iteration styles, you can let each_prefix be the default implementation of each, and call tree.to_enum(:each_postfix) or tree.to_enum(:each_infix) if you need an Enumerable that acts differently.

A single underlying object can have multiple Enumerable objects. Here's a second Enumerable for our simple array, in which each acts like each_with_index does for the original array:

	array_with_index = array.enum_with_index
	array_with_index.each do |x, i|
	 puts %{#{i}=>"#{x}"}
	end
	# 0=>"bob"
	# 1=>"loves"
	# 2=>"alice"

	array_with_index.each_with_index do |x, i|
	 puts %{#{i}=>#{x.inspect}}
	end
	# 0=>["bob", 0]
	# 1=>["loves", 1]
	# 2=>["alice", 2]

When you require 'enumerator', Enumerable sprouts two extra enumeration methods, each_cons and each_slice. These make it easy to iterate over a data structure in chunks. An example is the best way to show what they do:

	sentence = %w{Well, now I've seen everything!}

	two_word_window = sentence.to_enum(:each_cons, 2)
	two_word_window.each { |x| puts x.inspect }
	# ["Well,", "now"]
	# ["now", "I've"]
	# ["I've", "seen"]
	# ["seen", "everything!"]

	two_words_at_a_time = sentence.to_enum(:each_slice, 2)
	two_words_at_a_time.each { |x| puts x.inspect }
	# ["Well,", "now"]
	# ["I've", "seen"]
	# ["everything!"]

Note how any arguments passed into to_enum are passed along as arguments to the iteration method itself.

In Ruby 1.9, the Enumerable::Enumerator class is part of the Ruby core; you don't need the require statement. Also, each_cons and each_slice are built-in methods of Enumerable.

See Also

  • Recipe 7.9, "Looping Through Multiple Iterables in Parallel"
  • Recipe 20.6, "Running a Code Block on Many Objects Simultaneously"


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net