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:
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:
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).
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:
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
(ornil
) – 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:
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 ;)
Sohan | July 05, 2010
DrinkRails.com linked to your post as one of the top ruby on rails related blogs of the day.
J-_-L | July 05, 2010
Thank you, Sohan.
zhan | July 06, 2010
cool!!
Michael | July 06, 2010
I believe Listing 5 line 2 should read:
puts "#{result}: #{elements*' - '}"
For sake of perfect code clarity. :-)
J-_-L | July 06, 2010
thx, fixed it.
Anonymous | July 06, 2010
Are 1.9.2 docs online anywhere or do you have to generate it from the installed code? https://ruby-doc.org/ruby-1.9/index.html is outdated.
J-_-L | July 06, 2010
Me neither. I have used ri for my researches. As you have mentioned, you can easily create an offline version with one command: https://rbjl.janlelis.com/4-create-an-offline-version-of-the-ruby-docs
Jeremy Voorhis | July 06, 2010
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. https://www.haskell.org/ghc/docs/6.12.1/html/libraries/base/Data-List.html#v%3AconcatMap
Anonymous | July 13, 2010
Good post. Thanks.
Stefan Kanev | July 13, 2010
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.
Juan | April 29, 2011
Thank you, It's a very interesting article. I've finally understood the chunk method