Is there a way to check if the hashes in an array contain similar key value pairs in ruby?

For example, I have

array = [ {name: 'robert', nationality: 'asian', age: 10},
          {name: 'robert', nationality: 'asian', age: 5},
          {name: 'sira', nationality: 'african', age: 15} ]

      

I want to get the result as

array = [ {name: 'robert', nationality: 'asian', age: 15},
          {name: 'sira', nationality: 'african', age: 15} ]

      

since there are 2 Roberts with the same citizenship.

Any help would be much appreciated.

I tried Array.uniq! {|e| e[:name] && e[:nationality] }

but I want to add both numbers to two hashes which is 10 + 5

PS: An array can have n number of hashes.

+3


source to share


3 answers


array.each_with_object(Hash.new(0)) { |g,h| h[[g[:name], g[:nationality]]] += g[:age] }.
      map { |(name, nationality),age| { name:name, nationality:nationality, age:age } }
  [{ :name=>"robert", :nationality=>"asian", :age=>15 },
   { :name=>"sira", :nationality=>"african", :age=>15 }]

      

The two steps are as follows.

a = array.each_with_object(Hash.new(0)) { |g,h| h[[g[:name], g[:nationality]]] += g[:age] }
  #=> { ["robert", "asian"]=>15, ["sira", "african"]=>15 }

      



This uses the class method Hash :: new to create a hash with a default null value (represented by a block variable h

). Once this hash-hen is obtained, simply construct the desired hash:

a.map { |(name, nationality),age| { name:name, nationality:nationality, age:age } }

      

+2


source


I would start with something like this:



array = [ 
  { name: 'robert', nationality: 'asian', age: 10 },
  { name: 'robert', nationality: 'asian', age: 5 },
  { name: 'sira', nationality: 'african', age: 15 } 
]
array.group_by { |e| e.values_at(:name, :nationality) }
     .map { |_, vs| vs.first.merge(age: vs.sum { |v| v[:age] }) }
#=> [
#     {
#       :name        => "robert",
#       :nationality => "asian",
#       :age         => 15
#     }, {
#       :name        => "sira",
#       :nationality => "african",
#       :age         => 15
#     }
#   ]

      

+3


source


Let's take a look at what you want to accomplish from there. You have a list of some objects and you want to combine some objects together if they have the same ethnicity and name. So, we have a key with which we will merge. Let it be in programming terms.

key = proc { |x| [x[:name], x[:nationality]] }

      

We have defined a procedure that takes a hash and returns a "key" value. If this procedure returns the same value (according to eql?

) for two hashes, then these two hashes must be combined. So what do we mean by "merge"? You want to add ages together, so write a merge function.

merge = proc { |x, y| x.dup.tap { |x1| x1[:age] += y[:age] } }

      

If we have two values x

, and y

such that key[x]

and key[y]

are the same, we want to combine them, making a copy x

, and adding to it age y

. This is exactly what this procedure does. Now that we have our building blocks, we can write an algorithm.

We want to create the array at the end, after the merge using the key routine we wrote. Fortunately, Ruby has a handy feature called each_with_object

that will do something very nice for us. The method each_with_object

will execute its own block for each element of the array, passing another argument in the given value. This will come in handy here.

result = array.each_with_object({}) do |x, hsh|
  # ...
end.values

      

Since we are using keys and values ​​for the merge, the most efficient way to do this would be a hash. Hence, we are passing in an empty hash as an additional object, which we will modify to copy the merge results. After all, we don't care about the keys anymore, so we write .values

to get only the objects themselves. Now for the final snippets.

if hsh.include? key[x]
  hsh[ key[x] ] = merge.call hsh[ key[x] ], x
else
  hsh[ key[x] ] = x
end

      

Let it break. If the hash already includes key[x]

which is the key for the object x

we are looking at, we want to concatenate x

with the value that is currently in key[x]

. Here we add ages together. This approach only works if the function merge

is what mathematicians call a semigroup, which is a fancy way of saying that an operation is associative. You don't need to worry too much about this; addition is a very good example of a semigroup, so it works here.

In any case, if the key does not exist in the hash, we want to put the current value in the hash at the key position. The resulting hash from the merge is returned and then we can get the values ​​from it to get the desired result.

key = proc { |x| [x[:name], x[:nationality]] }
merge = proc { |x, y| x.dup.tap { |x1| x1[:age] += y[:age] } }
result = array.each_with_object({}) do |x, hsh|
  if hsh.include? key[x]
    hsh[ key[x] ] = merge.call hsh[ key[x] ], x
  else
    hsh[ key[x] ] = x
  end
end.values

      

Now my complexity theory is a bit rusty, but if Ruby uses its hash type efficiently (which I'm pretty sure of that) then this is a merge algorithm O(n)

, which means it will take a linear amount of time to complete given the size of the problem as input ...

+3


source







All Articles