Populate new_hash where the unique values from old_hash are the keys, and the keys from old_hash are values, grouped into arrays.

StackOverflow https://stackoverflow.com/questions/23121413

  •  04-07-2023
  •  | 
  •  

質問

I am new to programing and am starting with Ruby. Using .each ONLY, my challenge is to turn this:

animals = {
  'leopard'   => 1,
  'gorilla'   => 3,
  'hippo'     => 4,
  'zebra'     => 1,
  'lion'      => 2,
  'eagle'     => 3,
  'ostrich'   => 2,
  'alligator' => 6
}

Into this hash where the animals are grouped by count:

animals_by_count = {
   1 => ['leopard', 'zebra'],
   2 => ['lion', 'ostrich'],
   3 => ['gorilla', 'eagle'],
   4 => ['hippo'],
   6 => ['alligator']
}

This is my code, it is not working properly. Again, I must use .each ONLY, I my NOT use .map .inject .select or any another method.

animals_by_count = {}
animals.each do |animal, count|
  animals_array = []
  if !animals_by_count.has_key?(count)
    animals_by_count[count] = animals_array << animal
  elsif animals_by_count.has_key?(count)
    animals_by_count[count] = animals_array << animal
  end

 end
役に立ちましたか?

解決

You can do as below :-

animals = {
  'leopard'   => 1,
  'gorilla'   => 3,
  'hippo'     => 4,
  'zebra'     => 1,
  'lion'      => 2,
  'eagle'     => 3,
  'ostrich'   => 2,
  'alligator' => 6
}

animals_by_count = {}
animals.each do |animal, count|
  if animals_by_count.has_key?(count)
    animals_by_count[count].push(animal)
  else
    animals_by_count[count] = [animal]
  end
end

animals_by_count
# => {1=>["leopard", "zebra"],
#     3=>["gorilla", "eagle"],
#     4=>["hippo"],
#     2=>["lion", "ostrich"],
#     6=>["alligator"]}

Why not worked your one ?

animals.each do |animal, count|
  animals_array = []
# see here at every iteration, you are creating a new empty array and using it.
# animals_by_count key thus holding the last array, loosing the previous one.

他のヒント

@Arup has explained why your code is not working, and showed you have to fix it. I would like show you some variations and alternatives that an experienced Rubiest might used. These fall into two categories:

Build a new hash

This is the approach you have taken, which @Arup fixed. Let me change Arup's code slightly:

h = {}
animals.each do |(name, num)|
  h[num] = [] unless h.key?(num)
  h[num] << name
end
h
  #=> {1=>["leopard", "zebra"],
  #    3=>["gorilla", "eagle"],
  #    4=>["hippo"],
  #    2=>["lion", "ostrich"], 6=>["alligator"]}

Whenever h does not contain a key num, we add the element num => [] to the hash. Then name is appended to the array h[num]. The statement:

h[num] = [] unless h.key?(num)

is the same as

h[num] = h[num] || []

which can be combined with the next line:

h[num] = (h[num] || []) << name

and optionally written

(h[num] ||= []) << name

If we use the last of these, we would write:

h = {}
animals.each { |(name,num)| (h[num] ||= []) << name }
h

Rather than creating the empty hash, building it in the next step and the returning it after it's built, that can all be done in one line:

animals.each_with_object({}) { |(name,num),h| (h[num] ||= []) << name }

A variant of this is to replace the empty hash with the form of Hash#new that takes a block:

Hash.new { |h,k| h[k] = [] }

If this hash is accessed by a key that doesn’t correspond to a hash entry, h and k are passed to the block to allow you to create a value for the key. Here we want that to be an empty array. This allows us to write:

animals.each_with_object(Hash.new { |h,k| h[k] = [] }) { |(name,num),h|
  h[num] << name }

I'm not suggesting which of these approaches you should use. They all work. The choice is yours.

Enumerable#each_with_object was introduced in Ruby v1.9. Before that, and still now, Enumerable#reduce (aka inject) would or can be used for this task:

animals.reduce({}) { |h,(name,num)| (h[num] ||= []) << name; h }

Notice the order of the block parameters: |(name,num),h| for each_with_object, |h,(name,num)| for reduce. Also, when using reduce, the object (here h) must be returned to reduce. That's the reason for the lonely h as the last statement in reduce's block.

Use Enmerable#goup_by

The second common way to attack this kind of problem is to use group_by, which returns the following hash:

h = animals.group_by { |_,v| v }
  #=> {1=>[["leopard", 1], ["zebra", 1]],
  #    3=>[["gorilla", 3], ["eagle", 3]],
  #    4=>[["hippo", 4]],
  #    2=>[["lion", 2], ["ostrich", 2]],
  #    6=>[["alligator", 6]]}

Now all we need to do is to manipulate the hash values (e.g., change [["leopard", 1], ["zebra", 1]] to ["leopard", "zebra"].

There are a couple of ways to do this. One is to enumerate over the hash keys and modify the values:

h.keys.each { |k| h[k] = h[k].map(&:first) }
h

Another is to build a new hash:

g = {}
h.each { |k,v| g[k] = v.map(&:first) }
g

which can also be written:

h.each_with_object({}) { |(k,v),g| g[k] = v.map(&:first) }
animals.each{|name,num|(h[num]||=[])<<name}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top