I need to remove duplicates from an array in Ruby and perform an operation on a specific index while removing duplicates. Let me explain with an example:
arr = [["A", "Red", 7], ["A", "Red", 8], ["B", "Red", 3],["B", "Blue", 2],
["B", "Blue", 3], ["C", "Blue", 3], ["C", "Black", 1], ["D", nil, 4],
["D", nil, 5]]
I need to check if first and second indexes are the same, and if they are same, take a sum of third indexes while removing the duplicates.
So I need the output as:
[["A", "Red", 15], ["B", "Red", 3],["B", "Blue", 5], ["C", "Blue", 3],
["C", "Black", 1], ["D", nil, 9]]
You can use the Enumerable#group_by
method in combination with Enumerable#map
and Array#sum
to do this.
arr = [["A", "Red", 7], ["A", "Red", 8], ["B", "Red", 3],["B", "Blue", 2], ["B", "Blue", 3], ["C", "Blue", 3], ["C", "Black", 1], ["D", nil, 4], ["D", nil, 5]]
result = arr.group_by { |*keys, _number| keys }
.map { |keys, items| keys << items.sum(&:last) }
#=> [["A", "Red", 15], ["B", "Red", 3], ["B", "Blue", 5], ["C", "Blue", 3], ["C", "Black", 1], ["D", nil, 9]]
You could use each_with_object
and push the first 2 elements as key to the hash object:
new_arr = arr.each_with_object(Hash.new{ |h,k| h[k] = 0 }) do |val, hash|
hash[val[0..1]] += val[-1]
end.map(&:flatten)
new_arr #=> [["A", "Red", 15],
#=> ["B", "Red", 3],
#=> ["B", "Blue", 5],
#=> ["C", "Blue", 3],
#=> ["C", "Black", 1],
#=> ["D", nil, 9]]
or just the each
with a Hash
object defined with a default proc: Hash.new{ |h,k| h[k] = 0 }
Hash.new{ |h,k| h[k] = 0 }
This will, of course, do not work if your values in subarrays differ, for example ["A", "Red", 5]
and ["A", "red", 7]
will be treated differently.
arr.each_with_object(Hash.new(0)) { |(*a,n),h| h[a] += n }.map(&:flatten)
#=> [["A", "Red", 15], ["B", "Red", 3], ["B", "Blue", 5], ["C", "Blue", 3],
# ["C", "Black", 1], ["D", nil, 9]]
The first step of the calculation is:
h = arr.each_with_object(Hash.new(0)) { |(*a,n),h| h[a] += n }
#=> {["A", "Red"]=>15, ["B", "Red"]=>3, ["B", "Blue"]=>5,
# ["C", "Blue"]=>3, ["C", "Black"]=>1, ["D", nil]=>9}
This uses the form of Hash::new that takes an argument called the default value. All that means is that when Ruby's parser expands h[a] += 1
to
h[a] = h[a] + n
h[a]
on the right returns h
's default value, 0
, if h
does not have a key a
. For example, when h
is empty,
h[["A", "Red"]] = h[["A", "Red"]] + 7 #=> 0 + 7 => 7
h[["A", "Red"]] = h[["A", "Red"]] + 8 #=> 7 + 8 => 15
h
does not have a key ["A", "Red"]
in the first expression, so h[["A", "Red"]]
on the right returns the default value, 0
, whereas h
does have that key in the second expression so the default value does not apply.
h.map(&:flatten)
is shorthand for
h.map { |a| a.flatten }
When the block variable a
is set equal to first key-value pair of h
,
a #=> [["A", "Red"], 15]
So
a.flatten
#=> ["A", "Red", 15]
To understand |(*a,n),h|
we need to construct the enumerator
enum = arr.each_with_object(Hash.new(0))
#=> #<Enumerator: [["A", "Red", 7], ["A", "Red", 8], ["B", "Red", 3],
# ["B", "Blue", 2], ["B", "Blue", 3], ["C", "Blue", 3],
# ["C", "Black", 1], ["D", nil, 4], ["D", nil, 5]]
# :each_with_object({})>
We now generate the first value from the enumerator (using Enumerator#next ) and assign values to the block variables:
(*a,n),h = enum.next
#=> [["A", "Red", 7], {}]
a #=> ["A", "Red"]
n # => 7
h #=> {}
The way in which the array returned by enum.next
is broken up into constituent elements that are assigned to the block variables is called array decomposition . It is a powerful and highly useful techique.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.