From: Adam Prescott Date: 2013-03-04T23:42:41+09:00 Subject: Re: Efficient Ruby equivalent to Excel's "SUMIF"? --20cf3079b5ea56f76404d71a5e9f Content-Type: text/plain; charset=UTF-8 I think part of the problem is you're using arrays quite heavily. You can do it in one pass if you pick a Hash that you later convert to a more appropriate structure, or simply define your data as a class and instantiate relevant objects, etc. Anyway, here's an example: data = [ [ 'Ref', 'Count', 'Locn' ], [ 'Part1', '3', 'Here' ], [ 'Part2', '1', 'Here' ], [ 'Part1', '2', 'There' ], ] desired_output = [ ["Ref", "Total", "Here", "There"], ["Part1", 5, 3, 2], ["Part2", 1, 1, 0] ] # bump off the first element headers = data.shift references = {} data.each do |reference, count, location| # if the references hash hasn't "seen" this reference yet, # create a hash to keep track of totals and locations. references[reference] ||= { :total => 0, :locations => {} } r = references[reference] # contribute to the total for this reference. r[:total] += count.to_i # if this location hasn't been seen for this reference yet, # start it off at zero. could also use Hash.new(0) in the # above definition of references[reference] r[:locations][location] ||= 0 # now contribute to the location's count r[:locations][location] += count.to_i end # references now looks something like: # # { # "Part1" => { :total => 5, # :locations => { "Here" => 3, "There" => 2 } }, # "Part2" => { :total => 1, # :locations => { "Here" => 1 } } # } # This corresponds to desired_output, just in a different way. # # You can convert this to the original desired array if you wish, by # iterating over references, or you can pick a more appropriate Hash # structure and just use that. --20cf3079b5ea56f76404d71a5e9f Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable
I think part of the problem is you're using arrays qui= te heavily. You can do it in one pass if you pick a Hash that you later con= vert to a more appropriate structure, or simply define your data as a class= and instantiate relevant objects, etc.

Anyway, here's an example:


=
data =3D [
=C2=A0 [ 'Ref', 'Count', = 9;Locn' ],
=C2=A0 [ 'Part1', '3', 'Here&#= 39; ],
=C2=A0 [ 'Part2', '1', 'Here' ],
=C2= =A0 [ 'Part1', '2', 'There' ],
]

desired_output =3D [
=C2=A0 ["Ref", &q= uot;Total", "Here", "There"],
=C2=A0 ["Part1", 5, 3, 2],
=C2=A0 ["Part2&quo= t;, 1, 1, 0]
]

# bump off the firs= t element
headers =3D data.shift

referen= ces =3D {}

data.each do |reference, count, location|
=C2= =A0 # if the references hash hasn't "seen" this reference yet= ,
=C2=A0 # create a hash to keep track of totals and locations.
=C2=A0 references[reference] ||=3D { :total =3D> 0, :locations =3D> {= } }
=C2=A0 r =3D references[reference]

= =C2=A0 # contribute to the total for this reference.
=C2=A0 r[:to= tal] +=3D count.to_i

=C2=A0 # if this location hasn't been seen for this refe= rence yet,
=C2=A0 # start it off at zero. could also use Hash.new= (0) in the
=C2=A0 # above definition of references[reference]
=C2=A0 r[:locations][location] ||=3D 0

=C2=A0 # now contribute to the location's count
=C2=A0 r[:locations][location] +=3D count.to_i
end

# references now looks something like:
#
<= div># {
# =C2=A0"Part1" =3D> { :total =C2=A0 =C2=A0 =C2=A0=3D>= 5,
# =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 :locations= =3D> { "Here" =3D> 3, "There" =3D> 2 } },
# =C2=A0"Part2" =3D> { :total =C2=A0 =C2=A0 =C2=A0=3D&g= t; 1,
# =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 :locations =C2=A0= =3D> { "Here" =3D> 1 } }
# }

=
# This corresponds to desired_output, just in a different way.
#
# You can convert this to the original desired array i= f you wish, by
# iterating over references, or you can pick a more appropriate Hash
# structure and just use that.
--20cf3079b5ea56f76404d71a5e9f--