Iterators in Ruby - 7.7 Writing Block Methods That Classify or Collect
(Page 4 of 4 )
Problem
The basic block methods that come with the Ruby standard library aren’t enough for you. You want to define your own method that classifies the elements in an enumeration (like Enumerable#detect andEnumerable#find_all), or that does a transformation on each element in an enumeration (likeEnumerable#collect).
Solution
You can usually use inject to write a method that searches or classifies an enumeration of objects. With injectyou can write your own versions of methods such asdetectandfind_all:
module Enumerable
def find_no_more_than(limit)
inject([]) do |a,e|
a << e if yield e
return a if a.size >= limit
a
end
end
end
This code finds at most three of the even numbers in a list:
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a.find_no_more_than(3) { |x| x % 2 == 0 } # => [2, 4, 6]
If you find yourself needing to write a method likecollect, it’s probably because, for your purposes,collectitself yields elements in the wrong order. You can’t useinject, because that yields elements in the same order ascollect.
You need to find or write an iterator that yields elements in the order you want. Once you’ve done that, you have two options: you can write acollect equivalent on top of the iterator method, or you can use the iterator method to build anEnumerable object, and call itscollectmethod (as seen in Recipe 7.6).
Discussion
We discussed these block methods in more detail in Chapter 4, because arrays are the simplest and most common enumerable data type, and the most common. But almost any data structure can be enumerated, and a more complex data structure can be enumerated in more different ways.
As you’ll see in Recipe 9.4, theEnumerablemethods, likedetect andinject, are actually implemented in terms ofeach. Thedetectandinjectmethods yield to the code block every element that comes out ofeach. The value of theyieldstatement is used to determine whether the element matches some criteria.
In a method like detect, the iteration may stop once it finds an element that matches. In a method like find_all, the iteration goes through all elements, collecting the ones that match.
Methods likecollectwork the same way, but instead of returning a subset of elements based on what the code block says, they collect the values returned by the code block in a new data structure, and return the data structure once the iteration is completed.
If you’re using a particular object and you wish itscollectmethod used a different iterator, then you should turn the object into anEnumeratorand call itscollectmethod. But if you’re writing a class and you want to expose a newcollect-like method, you’ll have to define a new method.* In that case, the best solution is probably to expose a method that returns a customEnumerator: that way, your users can use all the methods ofEnumerable, not justcollect.
See Also
- Recipe 4.5, “Sorting an Array”
- Recipe 4.11, “Getting the N Smallest Items of an Array”
- Recipe 4.15, “Partitioning or Classifying a Set”
- Recipe 7.6, “Changing the Way an Object Iterates”
- If all you want is to make your custom data structure support the methods of Enumerable, see Recipe 9.4, “Implementing Enumerable: Write One Method, Get 22 Free”
Please check back next week for the conclusion of this article.
| DISCLAIMER: The content provided in this article is not warranted or guaranteed by Developer Shed, Inc. The content provided is intended for entertainment and/or educational purposes in order to introduce to the reader key ideas, concepts, and/or product reviews. As such it is incumbent upon the reader to employ real-world tactics for security and implementation of best practices. We are not liable for any negative consequences that may result from implementing any information covered in our articles or tutorials. If this is a hardware review, it is not recommended to open and/or modify your hardware. |
|
This article is excerpted from chapter eight of the Ruby Cookbook, written by Lucas Carlson and Leonard Richardson (O'Reilly, 2006; ISBN: 0596523696). Check it out today at your favorite bookstore. Buy this book now.
|
|