Ryan Perera
Ryan Perera

Reputation: 115

Best way to merge key value pairs in a hash based on number of values for that key in Ruby

I have a hash of arrays in ruby as :

 @people = { "a" => ["john", "mark", "tony"], "b"=> ["tom","tim"], 
            "c" =>["jane"], "others"=>["rob", "ryan"] }

I would like to merge all key value pairs where there are less than 3 items in the array for a particular keys values. They should be merged into the key called "others" to give roughly the result of

 @people = { "a" => ["john", "mark", "tony"], 
           "others"=> ["rob", "ryan", "tom", "tim", "jane"] }

Using the following code is problematic as duplicate key values in a hash cannot exist:

 @people = Hash[@people.map{|k,v| v.count<3 ? ["others",v] : [k,v]} ] %>

Whats the best way to elegantly solve this?

Upvotes: 1

Views: 974

Answers (6)

mu is too short
mu is too short

Reputation: 434755

You almost have it, the problem is, as you notice, that you can't build the Hash's key/value pairs on the fly because of duplicates. One way around the problem is to start out with the skeleton of what you're trying to build:

@people = @people.each_with_object({ 'others' => [ ] }) do |(k,v), h|
    if(v.length >= 3)
        h[k] = v
    else
        h['others'] += v
    end
end

Or, if you don't like each_with_object, you could:

h = { 'others' => [ ] }
@people.each do |k, v|
    # as above
end
@people = h

Or you could use pretty much the same structure with inject (taking care, as usual, to return the right thing from the block).

There are certainly other ways to do this but these approaches are pretty clear and easy to understand; IMO clarity should be your first goal.

Upvotes: 2

dbenhur
dbenhur

Reputation: 20398

Hash[ @people.group_by { |k,v| v.size < 3 ? 'others' : k }.
              map { |k,v| [k, v.flat_map(&:last)] } ]

=> {"a"=>["john", "mark", "tony"],
    "others"=>["tom", "tim", "jane", "rob", "ryan"]}

Upvotes: 1

shweta
shweta

Reputation: 8169

try:

>> @people = { "a" => ["john", "mark", "tony"], "b"=> ["tom","tim"], 
        "c" =>["jane"], "others"=>["rob", "ryan"] }

>> @new_people = {"others" => []}

>> @people.each_pair {|k,v| (v.size >= 3 && k!="others") ? @new_people.merge!(k=>v) : @new_people['others']+= v}    

>> @new_people
=> {"others"=>["rob", "ryan", "jane", "tom", "tim"], "a"=>["john", "mark", "tony"]}

Upvotes: 1

DigitalRoss
DigitalRoss

Reputation: 146123

@people.inject({}) do |m, (k, v)|
  m[i = v.size >= 3 ? k : 'others'] = m[i].to_a + v
  m
end

Upvotes: 0

oldergod
oldergod

Reputation: 15010

@people[:others] = []
@people.each do |k, v|
  @people[:others] |= @people.delete(k) if v.size < 3
end

Upvotes: 0

Stuart M
Stuart M

Reputation: 11588

What about this:

> less_than_three, others = @people.partition {|(key, values)| values.size >= 3 }
> Hash[less_than_three]
# => {"a"=>["john", "mark", "tony"]}
> Hash["others" => others.map {|o| o.last}.flatten]
# => {"others"=>["tom", "tim", "jane", "rob", "ryan"]}

Upvotes: 0

Related Questions