简体   繁体   中英

Combine 2 hashes with the same key

I am wanting to combine 2 hashes that have the same keys.

@clean_by_hour = Sale.where(item_name: clean).group_by_hour_of_day(:complete_time, format: "%-l %P").count
=> {"12 am"=>0, "1 am"=>0, "2 am"=>0, "3 am"=>0, "4 am"=>0, "5 am"=>0, "6 am"=>0, "7 am"=>0, "8 am"=>4, "9 am"=>14, "10 am"=>19, "11 am"=>10, "12 pm"=>19, "1 pm"=>16, "2 pm"=>13, "3 pm"=>18, "4 pm"=>7, "5 pm"=>4, "6 pm"=>4, "7 pm"=>0, "8 pm"=>0, "9 pm"=>0, "10 pm"=>0, "11 pm"=>0}

@lube_by_hour = Sale.where(item_name: lube).group_by_hour_of_day(:complete_time, format: "%-l %P").count

=> {"12 am"=>0, "1 am"=>0, "2 am"=>0, "3 am"=>0, "4 am"=>0, "5 am"=>0, "6 am"=>0, "7 am"=>0, "8 am"=>3, "9 am"=>4, "10 am"=>10, "11 am"=>14, "12 pm"=>10, "1 pm"=>8, "2 pm"=>5, "3 pm"=>20, "4 pm"=>4, "5 pm"=>2, "6 pm"=>0, "7 pm"=>0, "8 pm"=>0, "9 pm"=>0, "10 pm"=>0, "11 pm"=>0}

I am wanting the new hash to look like:

{"12 am"=> 0, 0}

At least that is what I think I want. I am attempting to combine the 2 hashes so that I can display the data in a table.

I know I will need to alter this code, but this is what I am working with right now in the view.

    <% @clean_by_hour.each do |hour, count| %>
    
    <% if count != 0 %>
      <tr>
        <td><%= hour %></td>
        <td><%= count %></td>
      <% end %>
    <% end %>
    <% @lube_by_hour.each do |hour, count| %>
    <% if count != 0 %>
      <td><%= count %></td>
     <% end %>
    <% end %>
      </tr>

Thank you!

You could use #each_with_object for this. Not sure if it's the best way, but it gets you what you want.

@clean_by_hour.each_with_object({}) do |(k,v), acc|
  acc[k] ||= []
  acc[k] << v
  acc[k] << @lube_by_hour[k]
  acc[k].compact
end

will return {'12 am' => [0,0], '1 am' => [0,0], ... }

First, I think by {"12 am"=> 0, 0} you mean a Hash with each of its keys being an hour and each value an Array with two elements, one representing the number of cleans in that hour and the other the number of lubes. If that's the case it should be like this: {"12 am"=> [0, 0]} (with [ and ] around each value)

You can do that with something like this:

@count_by_hour = @clean_by_hour.keys.each_with_object({}) do |k, h|
  h[k] = [@clean_by_hour[k], @lube_by_hour[k]]
end

@count_by_hour # =>
{"12 am"=>[0, 0],
 "1 am"=>[0, 0],
 "2 am"=>[0, 0],
 "3 am"=>[0, 0],
 "4 am"=>[0, 0],
 "5 am"=>[0, 0],
 "6 am"=>[0, 0],
 "7 am"=>[0, 0],
 "8 am"=>[4, 3],
 "9 am"=>[14, 4],
 "10 am"=>[19, 10],
 "11 am"=>[10, 14],
 "12 pm"=>[19, 10],
 "1 pm"=>[16, 8],
 "2 pm"=>[13, 5],
 "3 pm"=>[18, 20],
 "4 pm"=>[7, 4],
 "5 pm"=>[4, 2],
 "6 pm"=>[4, 0],
 "7 pm"=>[0, 0],
 "8 pm"=>[0, 0],
 "9 pm"=>[0, 0],
 "10 pm"=>[0, 0],
 "11 pm"=>[0, 0]}

You could also use a Hash for each value to improve the readability and prevent bugs: (It's easier to remember which Hash key to use than which Array index.)

@count_by_hour = @clean_by_hour.keys.each_with_object({}) do |k, h|
  h[k] = { clean: @clean_by_hour[k], lube: @lube_by_hour[k] }
end

@count_by_hour # =>
{"12 am"=>{:clean=>0, :lube=>0},
 "1 am"=>{:clean=>0, :lube=>0},
 "2 am"=>{:clean=>0, :lube=>0},
 "3 am"=>{:clean=>0, :lube=>0},
 "4 am"=>{:clean=>0, :lube=>0},
 "5 am"=>{:clean=>0, :lube=>0},
 "6 am"=>{:clean=>0, :lube=>0},
 "7 am"=>{:clean=>0, :lube=>0},
 "8 am"=>{:clean=>4, :lube=>3},
 "9 am"=>{:clean=>14, :lube=>4},
 "10 am"=>{:clean=>19, :lube=>10},
 "11 am"=>{:clean=>10, :lube=>14},
 "12 pm"=>{:clean=>19, :lube=>10},
 "1 pm"=>{:clean=>16, :lube=>8},
 "2 pm"=>{:clean=>13, :lube=>5},
 "3 pm"=>{:clean=>18, :lube=>20},
 "4 pm"=>{:clean=>7, :lube=>4},
 "5 pm"=>{:clean=>4, :lube=>2},
 "6 pm"=>{:clean=>4, :lube=>0},
 "7 pm"=>{:clean=>0, :lube=>0},
 "8 pm"=>{:clean=>0, :lube=>0},
 "9 pm"=>{:clean=>0, :lube=>0},
 "10 pm"=>{:clean=>0, :lube=>0},
 "11 pm"=>{:clean=>0, :lube=>0}}

Let's start by simplifying your example:

clean_by_hour = { "12 am"=>0, "1 am"=>3, "2 am"=>0, "3 am"=>5 }
lube_by_hour  = { "12 am"=>0, "1 am"=>4, "2 am"=>0, "3 am"=>2 }

Presumably, the table has key-value pairs from each hash that ordered (by time of key insertion) by hour, so you wish to compute the following:

hours = clean_by_hour.keys
  #=> ["12 am", "1 am", "2 am", "3 am"]
clean_values = clean_by_hour.values
  #=> [0, 3, 0, 5]
lube_values = lube_by_hour.values
  #=> [0, 4, 0, 2]
arr = clean_values.zip(lube_values)
  #=> [[0, 0], [3, 4], [0, 0], [5, 2]]

and then use hours and arr to construct your table.


This requires, however, that the insertion order of the keys of each hash are in the order shown. It would be safer to write the following.

hours = ["12 am", "1 am", "2 am", "3 am"]
clean_values = clean_by_hour.values_at(*hours)
  #=> [0, 3, 0, 5]
lube_values = lube_by_hour.values_at(*hours)
  #=> [0, 4, 0, 2]
arr = clean_values.zip(lube_values)
  #=> [[0, 0], [3, 4], [0, 0], [5, 2]]

This works regardless of the order of the key-value pairs in the hashes. See Hash#values_at .


Stepping back, you might consider constructing your original hashes differently, using a 24-hour clock. For example:

clean_by_hour = { 0=>0, 1=>3, 12=>5, 13=>1, 23=>6 }
lube_by_hour  = { 0=>0, 1=>4, 12=>3, 13=>2, 23=>3 }

then

hours = [0, 1, 12, 13, 23]
clean_values = clean_by_hour.values_at(*hours)
  #=> [0, 3, 5, 1, 6]
lube_values = lube_by_hour.values_at(*hours)
  #=> [0, 4, 3, 2, 3]
arr = clean_values.zip(lube_values)
  #=> [[0, 0], [3, 4], [5, 3], [1, 2], [6, 3]]

If you wish to express the hours in your table as in your example, you could use a simple method:

def hours_to_str(hour)
  case hour
  when 0 then "12 am"
  when 1..11 then "#{hour} am"
  when 12 then "12 pm"
  else "#{hour-12} pm"
  end
end
hours_to_str(0)  #=> "12 am"
hours_to_str(3)  #=> "3 am"
hours_to_str(11) #=> "11" am"
hours_to_str(12) #=> "12 pm"
hours_to_str(13) #=> "1 pm"       
hours_to_str(23) #=> "11 pm"

Then use

am_pm_hours = hours.map { |hour| hour_to_str(hour) }
  #=> ["12 am", "1 am", "12 pm", "1 pm", "11 pm"]

to construct your table.

Suppose that later you decide you want am_pm_hours to be:

["12:00 am", "1:00 am", "12:00 pm", "1:00 pm", "11:00 pm"]

Then you need only change the method hours_to_str , rather than the construction of the hashes clean_by_hour and lube_by_hour .


Lastly, since you are obtaining values for all 24 hours of the day, in order, you might create arrays rather than hashes (my preference).

clean_by_hour = [0, 3, 5, 3, 1, 6, 2, 7, 1, 2, 3, 4,
                 5, 1, 8, 2, 6, 9, 1, 5, 8, 9, 3, 6]
lube_by_hour  = [0, 4, 6, 3, 1, 4, 2, 3, 4, 4, 1, 0,
                 3, 2, 4, 1, 4, 2, 7, 5, 3, 6, 9, 3]

Hash#merge

This question already has a lot of great answers, but it is a little bit strange to me that nobody has mentioned Ruby's built-in Hash#merge in its block form yet.

Please, have a look at the following examples:

clean_by_hour = { "12 am" => 0, "1 am" => 4, "2 am" => 21 }
lube_by_hour = { "12 am" => 0, "1 am" => 17, "2 am" => 12 }

clean_by_hour.merge(lube_by_hour) { |_key, old_value, new_value| [old_value, new_value] }
# => {"12 am"=>[0, 0], "1 am"=>[4, 17], "2 am"=>[21, 12]}

clean_by_hour.merge(lube_by_hour) do |_key, clean, lube|
  { clean: clean, lube: lube }
end
# {"12 am"=>{:clean=>0, :lube=>0}, "1 am"=>{:clean=>4, :lube=>17}, "2 am"=>{:clean=>21, :lube=>12}}

Sources :

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