info | twitter | github
J-_-L | Writings on Ruby Programming
05.07.10 06.07.10
11 comments

New Array and Enumerable methods in Ruby 1.9.2: keep_if, chunk...

In Ruby, dealing with Arrays and similar objects is pretty fun. And we have gotten more possibilities with Ruby 1.9.2 :)

The NEWS file says:

  * Array
    * new method:
      * Array#keep_if
      * Array#repeated_combination
      * Array#repeated_permutation
      * Array#rotate
      * Array#rotate!
      * Array#select!
      * Array#sort_by!

    * extended methods:
      * Array#{uniq,uniq!,product} can take a block.
...
  * Enumerable
    * New methods:
      * Enumerable#chunk
      * Enumerable#collect_concat
      * Enumerable#each_entry
      * Enumerable#flat_map
      * Enumerable#slice_before

Let’s take a closer look!

select! and keep_if

The select method (only choose the elements for which the block evaluates to true) got a mutator version select!. It does almost the same like the new keep_if method – with one subtle difference: select! returns nil if no changes were made, keep_if always returns the object.

more combination and permutation

Ruby 1.9 introduced the useful methods combination and permutation. Now there are also repeated_combination and repeated_permutation:

 1
2
3
4
5
6
7
8
>> [1,2,3].combination(2).to_a
=> [[1, 2], [1, 3], [2, 3]]
>> [1,2,3].repeated_combination(2).to_a
=> [[1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3]]
>> [1,2,3].permutation(2).to_a
=> [[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]]
>> [1,2,3].repeated_permutation(2).to_a
=> [[1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], [3, 1], [3, 2], [3, 3]]

rotate / rotate!

This method removes the first element and appends it. You can pass an integer, how many steps it should “cycle” (negative values are possible). It does something like:

Listing 2
/27/rotate.rb ruby
 1
2
3
4
5
6
class Array
  def rotate2(n=1)
    new_start = n % self.size
    self[new_start..-1] + self[0...new_start]
  end
end

small changes: sort_by, uniq / uniq!, product

  • sort_by (the block defines, how the Array should be sorted) also got a mutator version: sort_by!.
  • product (combine all elements from both Arrays) now also accepts a block which yields every result, instead of returning it.
  • uniq (remove duplicates) can now take a block, example from the docs:
Listing 3
/27/uniq_block.rb ruby 1.9
 1
2
c = [ "a:def", "a:xyz", "b:abc", "b:xyz", "c:jkl" ]
c.uniq {|s| s[/^\w+/]} #=> [ "a:def", "b:abc", "c:jkl" ]

flat_map and its alias collect_concat

This method works like map, but if an element is an Enumerable itself, the applied block is also run for each of its child elements (but not recursively).

Listing 4
/27/flat_map.rb ruby 1.9
 1
[[1,2],[3,[4,5]]].flat_map{|i|i} #=> [1, 2, 3, [4,5]]

I do not know (yet), if this is useful (and if it wouldn’t be cooler if it did something like .flatten.map), but we will see…

each_entry

Each entry is like each, but it treats multiple yield arguments as a single array (so yield 1,2 implicitly becomes yield [1,2]). Mostly, the result is similar to each’s, but there are some occasions where it does matter.

chunk

This one is interesting, but it is a little bit strange to use. It splits self into multiple Enumerators, using the rule given in the block. It keeps together those parts that “match” in series. It passes the result of the “filter” rule and an Enumerator of the successive elements – Look at these two examples:

Listing 5
/27/chunk.rb ruby 1.9
 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(1..42).chunk{|n| n%11 == 0}.each{|result, elements|
  puts "#{result}: #{elements*' - '}"
}
# outputs:
# false: 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10
# true: 11
# false: 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21
# true: 22
# false: 23 - 24 - 25 - 26 - 27 - 28 - 29 - 30 - 31 - 32
# true: 33
# false: 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42

# you could (ab)use this to order numbers in quotient rings:
a=(0..4).map{[]}
(1..42).chunk{|n| n%5}.each{|remainder, elements| a[remainder] += elements}
p a
# outputs:
# [[5, 10, 15, 20, 25, 30, 35, 40], [1, 6, 11, 16, 21, 26, 31, 36, 41], [2, 7, 12, 17, 22, 27, 32, 37, 42], [3, 8, 13, 18, 23, 28, 33, 38], [4, 9, 14, 19, 24, 29, 34, 39]]

The chunk block can also return some special values: Symbols that begin with an underscore. Currently, there are two special symbols supported:

  • :_separator (or nil) – the element is dropped
  • :_alone – the element always gets its single chunk

Some advanced examples can be found in the docs.

slice_before

This method also lets you split the Enumerable. You simply specify a pattern/a block which has to match/be true and it splits before that element:

Listing 6
/27/slice_before.rb ruby 1.9
 1
2
3
4
5
6
7
8
9
10
11
%w|Ruby is 2 parts Perl, 1 part Python, and 1 part Smalltalk|.slice_before(/\d/).to_a
#=> [["Ruby", "is"], ["2", "parts", "Perl,"], ["1", "part", "Python,", "and"], ["1", "part", "Smalltalk"]]

# a more complex version from the docs (slightly modified):
a = [0,2,3,4,6,7,9]
prev = a[0]
a.slice_before {|cur|
  prev, prev2 = cur, prev  # one step further
  prev2 + 1 != prev        # two ago != one ago ? --> new slice
}.to_a
# => [[0], [2, 3, 4], [6, 7], [9]]

What’s next?

It is quite interesting to observe, in which direction Ruby moves with this methods. Some of them where just “missing” previously (e.g. select!). However, some others seem to be for a very special purpose only (chunk) – but maybe it is just a false interpretation on the face of it ;)

Creative Commons License

Comments

05.07.10

Sohan

DrinkRails.com linked to your post as one of the top ruby on rails related blogs of the day.

05.07.10

J-_-L

Thank you, Sohan.

06.07.10

zhan

cool!!

06.07.10

Michael

I believe Listing 5 line 2 should read:
puts "#{result}: #{elements*' - '}"

For sake of perfect code clarity. :-)

06.07.10

J-_-L

thx, fixed it.

06.07.10

anonym

Are 1.9.2 docs online anywhere or do you have to generate it from the installed code? http://ruby-doc.org/ruby-1.9/index.html is outdated.

06.07.10

J-_-L

Me neither. I have used ri for my researches. As you have mentioned, you can easily create an offline version with one command: http://rbjl.net/4-create-an-offline-version-of-the-ruby-docs

06.07.10

Jeremy Voorhis

flat_map / collect_concat seems to be equivalent to concatMap in Haskell. If you're familiar with Haskell types, the function type of concatMap might suggest some uses. http://www.haskell.org/ghc/docs/6.12.1/html/libraries/base/Data-List.html#v%3AconcatMap

13.07.10

anonym

Good post. Thanks.

13.07.10

Stefan Kanev

I've missed collect_concat quite a number of times. The problem with flatten.map is that it flattens nested arrays, i.e., would have different behavior with your example.

29.04.11

Juan

Thank you, It's a very interesting article. I've finally understood the chunk method