Extracting Portions of Arrays

Problem

Given an array, you want to retrieve the elements of the array that occupy certain positions or have certain properties. You might to do this in a way that removes the matching elements from the original array.

Solution

To gather a chunk of an array without modifying it, use the array retrieval operator Array#[], or its alias Array#slice.

The array retrieval operator has three forms, which are the same as the corresponding forms for substring accesses. The simplest and most common form is array[index].It takes a number as input, treats it as an index into the array, and returns the element at that index. If the input is negative, it counts from the end of the array. If the array is smaller than the index, it returns nil. If performance is a big consideration for you, Array#at will do the same thing, and it's a little faster than Array#[]:

	a = ("a".."h").to_a # => ["a", "b", "c", "d", "e", "f", "g", "h"]

	a[0] # => "a"
	a[1] # => "b"

	a.at(1) # => "b" 
	a.slice(1) # => "b"
	a[-1] # => "h"
	a[-2] # => "g"
	a[1000] # => nil 
	a[-1000] # => nil

The second form is array[range]. This form retrieves every element identified by an index in the given range, and returns those elements as a new array.

A range in which both numbers are negative will retrieve elements counting from the end of the array. You can mix positive and negative indices where that makes sense:

	a[2..5] # => ["c", "d", "e", "f"]
	a[2…5] # => ["c", "d", "e"]
	a[0..0] # => ["a"]
	a[1..-4] # => ["b", "c", "d", "e"]
	a[5..1000] # => ["f", "g", "h"]

	a[2..0] # => []
	a[0…0] # => []

	a[-3..2] # => []

The third form is array[start_index, length]. This is equivalent to array[range. new(start_index…start_index+length)].

	a[2, 4] # => ["c", "d", "e", "f"]
	a[2, 3] # => ["c", "d", "e"]
	a[0, 1] # => ["a"]
	a[1, 2] # => ["b", "c"] 
	a[-4, 2] # => ["e", "f"] 
	a[5, 1000] # => ["f", "g", "h"]

To remove a slice from the array, use Array#slice!. This method takes the same arguments and returns the same results as Array#slice, but as a side effect, the objects it retrieves are removed from the array.

	a.slice!(2..5) # => ["c", "d", "e", "f"]
	a # => ["a", "b", "g", "h"]

	a.slice!(0) # => "a"
	a # => ["b", "g", "h"]

	a.slice!(1,2) # => ["g", "h"]
	a # => ["b"]

 

Discussion

The Array methods [], slice, and slice! work well if you need to extract one particular elements, or a set of adjacent elements. There are two other main possibilities: you might need to retrieve the elements at an arbitrary set of indexes, or (a catch-all) you might need to retrieve all elements with a certain property that can be determined with a code block.

To nondestructively gather the elements at particular indexes in an array, pass in any number of indices to Array#values_at. Results will be returned in a new array, in the same order they were requested.

	a = ("a".."h").to_a # => ["a", "b", "c", "d", "e", "f", "g", "h"]
	a.values_at(0) # => ["a"]
	a.values_at(1, 0, -2) # => ["b", "a", "g"]
	a.values_at(4, 6, 6, 7, 4, 0, 3)# => ["e", "g", "g", "h", "e", "a", "d"]

Enumerable#find_all finds all elements in an array (or other class with Enumerable mixed in)for which the specified code block returns true. Enumerable#reject will find all elements for which the specified code block returns false.

	a.find_all { |x| x < "e" } # => ["a", "b", "c", "d"]
	a.reject { |x| x < "e" } # => ["e", "f", "g", "h"]

To find all elements in an array that match a regular expression, you can use Enumerable#grep instead of defining a block that does the regular expression match:

	a.grep /[aeiou]/ # => ["a", "e"]
	a.grep /[^g]/ # => ["a", "b", "c", "d", "e", "f", "h"]

It's a little tricky to implement a destructive version of Array#values_at, because removing one element from an array changes the indexes of all subsequent elements. We can let Ruby do the work, though, by replacing each element we want to remove with a dummy object that we know cannot already be present in the array. We can then use the C-backed method Array#delete to remove all instances of the dummy object from the array. This is much faster than using Array#slice! to remove elements one at a time, because each call to Array#slice! forces Ruby to rearrange the array to be contiguous.

If you know that your array contains no nil values, you can set your undesired values to nil, then use use Array#compress! to remove them. The solution below is more general.

	class Array
	 def strip_values_at!(*args)
	 #For each mentioned index, replace its value with a dummy object.
	 values = []
	 dummy = Object.new 
	 args.each do |i|
	 if i < size
	 values << self[i] 
	 self[i] = dummy
	 end
	 #Strip out the dummy object.
	 delete(dummy) 
	 return values 
	 end
	 end
	end

	a = ("a".."h").to_a
	a.strip_values_at!(1, 0, -2) # => ["b", "a", "g"]
	a # => ["c", "d", "e", "f", "h"]

	a.strip_values_at!(1000) # => [] 
	a # => ["c", "d", "e", "f", "h"]

Array#reject! removes all items from an array that match a code block, but it doesn't return the removed items, so it won't do for a destructive equivalent of Enumerable#find_all. This implementation of a method called exTRact! picks up where Array#reject! leaves off:

	class Array
	 def extract! 
	 ary = self.dup
	 self.reject! { |x| yield x }
	 ary - self
	 end
	end

	a = ("a".."h").to_a
	a.extract! { |x| x < "e" && x != "b" } # => ["a", "c", "d"]
	a # => ["b", "e", "f", "g", "h"]

Finally, a convenience method called grep_extract! provides a method that destructively approximates the behavior of Enumerable#grep.

	class Array
	 def grep_extract!(re)
	 extract! { |x| re.match(x) }
	 end
	end

	a = ("a".."h").to_a
	a.grep_extract!(/[aeiou]/) # => ["a", "e"]
	a # => ["b", "c", "d", "f", "g", "h"]

 

See Also

  • Strings support the array lookup operator, slice, slice!, and all the methods of Enumerable, so you can treat them like arrays in many respects; see Recipe 1.13, "Getting the Parts of a String You Want"


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