From: Colin Bartlett Date: 2010-03-21T16:23:00+09:00 Subject: Re: Grouping elements of an array On Sun, Mar 21, 2010 at 12:57 AM, Tanaka Akira wrote: >> I think this kind of problems which slices consecutive elements in an array >> is not well supported in Ruby. On Sun, Mar 21, 2010 at 6:42 AM, Urabe Shyouhei wrote: > +1. There seems to be some real applications where that kind of tools are nifty. On Sun, Mar 21, 2010 at 12:57 AM, Tanaka Akira also wrote: >> Enumerable#each_slice is not usable because it slices for each fixed number >> of elements. >> Ruby 1.9.2 has Enumerable#slice_before and it is usable but not so elegant >> because it needs to maintain previous element. ... >> We may need Enumerable#slice_between. Is a really elegant method possible for this sort of thing? Some time ago I was trying to find duplicate files, and set up an array of files with elements (paths, sizes, checksums), and then wrote an Enumerable#each_group method. The only way I could think of differentiating a yield for "comparison" from a yield of the wanted array of "similar" objects was to have the first item of each yield to be a boolean with true meaning compare, and false meaning it's the wanted array. I'd be interested in seeing a better Enumerable#each_group method, if one is possible. In the meantime, below is what I wrote for Enumerable#each_group, with its application to Steve Wilhelm's problem. module Enumerable # Deeming successive enumerable objects to be "equivalent" # using a block compare, yields an array of the equivalent items. def each_group_use_block_compare() arr = nil ; obj_g = nil self.each do | obj | if arr then # first item in yield is "true" indicating a yield for comparison if ( yield true, obj_g, obj ) == 0 then arr << obj obj_g = obj # group by adjacent objects, not by first in group next end # first item in yield is "false" indicating a yield of the group yield false, arr end obj_g = obj ; arr = [ obj ] end if arr then # first item in yield is "false" indicating a yield of the group yield false, arr end return self end end # for the problem of Steve Wilhelm def group_for_sw( arr ) arrg = [] arr.each_group_use_block_compare do | q_compare, aa, bb | if q_compare then if bb[1] - aa[1] < 30 then 0 else -1 end else aa.map! { |cc| cc[0] } # to preserve A, B, C objects, omit this arrg << aa end end arrg end arr = [ [ :A, 0 ], [ :B, 15 ], [ :C, 35 ], [ :D, 100 ], [ :E, 205 ], [ :F, 215 ], [ :G, 300 ] ] arrg = group_for_sw( arr ) p arrg #=> [[:A, :B, :C], [:D], [:E, :F], [:G]]