简体   繁体   中英

Remove duplicates from array in Ruby and perform an operation on a specific index

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM