Iterating Over an Array

Problem

You want to perform some operation on each item in an array.

Solution

Iterate over the array with Enumerable#each. Put into a block the code you want to execute for each item in the array.

	[1, 2, 3, 4].each { |x| puts x }
	# 1
	# 2
	# 3
	# 4

If you want to produce a new array based on a transformation of some other array, use Enumerable#collect along with a block that takes one element and transforms it:

	[1, 2, 3, 4].collect { |x| x ** 2 } # => [1, 4, 9, 16]

 

Discussion

Ruby supports for loops and the other iteration constructs found in most modern programming languages, but its prefered idiom is a code block fed to an method like each or collect.

Methods like each and collect are called generators or iterators: they iterate over a data structure, yielding one element at a time to whatever code block you've attached. Once your code block completes, they continue the iteration and yield the next item in the data structure (according to whatever definition of "next" the generator supports). These methods are covered in detail in Chapter 7.

In a method like each, the return value of the code block, if any, is ignored. Methods like collect take a more active role. After they yield an element of a data structure to a code block, they use the return value in some way. The collect method uses the return value of its attached block as an element in a new array.

Although commonly used in arrays, the collect method is actually defined in the Enumerable module, which the Array class includes. Many other Ruby classes (Hash and Range are just two) include the Enumerable methods; it's a sort of baseline for Ruby objects that provide iterators. Though Enumerable does not define the each method, it must be defined by any class that includes Enumerable, so you'll see that method a lot, too. This is covered in Recipe 9.4.

If you need to have the array indexes along with the array elements, use Enumerable#each_with_index.

	['a', 'b', 'c'].each_with_index do |item, index|
	 puts "At position #{index}: #{item}"
	end
	# At position 0: a
	# At position 1: b
	# At position 2: c

Ruby's Array class also defines several generators not seen in Enumerable . For instance , to iterate over a list in reverse order, use the reverse_each method:

	[1, 2, 3, 4]. 
reverse_each { |x| puts x }
	# 4
	# 3
	# 2
	# 1

Enumerable#collect has a destructive equivalent: Array# collect!, also known as Arary#map! (a helpful alias for Python programmers). This method acts just like collect, but instead of creating a new array to hold the return values of its calls to the code block, it replaces each item in the old array with the corresponding value from the code block. This saves memory and time, but it destroys the old array:

	array = ['a', 'b', 'c']
	array.collect! { |x| x.upcase }
	array # => ["A", "B", "C"]
	array.map! { |x| x.downcase }
	array # => ["a", "b", "c"]

If you need to skip certain elements of an array, you can use the iterator methods Range#step and Integer#upto instead of Array#each. These methods generate a sequence of numbers that you can use as successive indexes into an array.

	array = ['junk', 'junk', 'junk', 'val1', 'val2']
	3.upto(array.length-1) { |i| puts "Value #{array[i]}" }
	# Value val1
	# Value val2

	array = ['1', 'a', '2', 'b', '3', 'c']
	(0..array.length-1).step(2) do |i|
	 puts "Letter #{array[i]} is #{array[i+1]}"
	end
	# Letter 1 is a
	# Letter 2 is b
	# Letter 3 is c

Like most other programming languages, Ruby lets you define for, while, and until loopsbut you shouldn't need them very often. The for construct is equivalent to each, whether it's applied to an array or a range:

	for element in ['a', 'b', 'c']
	 puts element
	end
	# a
	# b
	# c

	for element in (1..3)
	 puts element
	end
	# 1
	# 2
	# 3

The while and until constructs take a boolean expression and execute the loop while the expression is true (while)or until it becomes true (until). All three of the following code snippets generate the same output:

	array = ['cherry', 'strawberry', 'orange']

	for index in (0…array.length)
	 puts "At position #{index}: #{array[index]}"
	end

	index = 0
	while index < array.length
	 puts "At position #{index}: #{array[index]}"
	 index += 1
	end

	index = 0
	until index == array.length
	 puts "At position #{index}: #{array[index]}"
	 index += 1
	end

	# At position 0: cherry
	# At position 1: strawberry
	# At position 2: orange

These constructs don't make for very idiomatic Ruby. You should only need to use them when you're iterating over a data structure in a way that doesn't already have an iterator method (for instance, if you're traversing a custom tree structure). Even then, it's more idiomatic if you only use them to define your own iterator methods.

The following code is a hybrid of each and each_reverse. It switches back and forth between iterating from the beginning of an array and iterating from its end.

	array = [1,2,3,4,5]
	new_array = []
	front_index = 0

	back_index = array.length-1
	while front_index <= back_index
	 new_array << array[front_index]
	 front_index += 1
	 if front_index <= back_index
	 new_array << array[back_index]
	 back_index -= 1
	 end
	end
	new_array # => [1, 5, 2, 4, 3]

That code works, but it becomes reusable when defined as an iterator. Put it into the Array class, and it becomes a universally accessible way of doing iteration, the colleague of each and reverse_each:

	class Array
	 def each_from_both_sides
	 front_index = 0
	 back_index = self.length-1
	 while front_index <= back_index
	 yield self[front_index]
	 front_index += 1
	 if front_index <= back_index
	 yield self[back_index]
	 back_index -= 1
	 end
	 end
	 end
	end

	new_array = []
	[1,2,3,4,5].each_from_both_sides { |x| new_array << x }
	new_array # => [1, 5, 2, 4, 3]

This "burning the candle at both ends" behavior can also be defined as a collecttype method: one which constructs a new array out of multiple calls to the attached code block. The implementation below delegates the actual iteration to the each_ from_both_sides method defined above:

	class Array
	 def collect_from_both_sides
	 new_array = []
	 each_from_both_sides { |x| new_array << yield(x) }
	 return new_array
	 end
	end

	["ham", "eggs", "and"].collect_from_both_sides { |x| x.capitalize }
	# => ["Ham", "And", "Eggs"]

 

See Also

  • Chapter 7, especially Recipe 7.5, "Writing an Iterator Over a Data Structure," and Recipe 7.9, "Looping Through Multiple Iterables in Parallel"


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