Как преобразовать массив хэшей в один хэш и подсчитать повторяющиеся элементы?

#ruby

#ruby

Вопрос:

У меня есть массив хэшей, который я в основном хочу объединить и преобразовать в один хэш, и в то же время я хочу подсчитать, сколько раз встречается пара ключ: значение.

Исходный массив

 cart_items = [
  {"AVOCADO" => {:price => 3.0, :clearance => true }},
  {"AVOCADO" => {:price => 3.0, :clearance => true }},
  {"KALE"    => {:price => 3.0, :clearance => false}}
]
  

Я пробовал это, однако я не получаю то, что хочу. Моя попытка сделать это приведена ниже, если кто-нибудь может объяснить, где я ошибаюсь, это было бы здорово.

Моя попытка решить эту проблему заключается в следующем.

 def consolidate_cart(items)
  ### the cart starts as an array of items
  ## convert the array into a hash`

 hashed_items = items.inject(:merge!)

 hashed_items.map{|k,v| {k => v, :count => v.length}}

end

consolidate_cart(cart_items)
  

Я ожидаю, что результат будет

 {
  "AVOCADO" => {:price => 3.0, :clearance => true, :count => 2},
  "KALE"    => {:price => 3.0, :clearance => false, :count => 1}
}
  

Но я получаю вывод

 [{"AVOCADO"=>{:price=>3.0, :clearance=>true}, :count=>2}, {"KALE"=>{:price=>3.0, :clearance=>false}, :count=>2}]
  

Комментарии:

1. Hash#merge в этом случае теряется информация. Попробуйте group_by .

2. Также v.length в вашем фрагменте говорится о двух парах ключ-значение в хэше {:price => 3.0, :clearance => true } . Все хэши там состоят из двух элементов, так что вы получаете 2 в обоих случаях.

Ответ №1:

Вы можете объединить v (внутри map вызова) значение count ( v.merge(:count => v.length) ) , так что это добавит ключ count к v хэшу, вы получите что-то вроде:

 [
  {"AVOCADO"=>{:price=>3.0, :clearance=>true, :count=>2},
  {"KALE"=>{:price=>3.0, :clearance=>false, :count=>2}
]
  

Но в любом случае значения для :count будут неверными.

С другой стороны, вы можете получить все ключи из каждого хэша в cart_items, объединить хэши, а затем объединить новый ключ с количеством этого ключа в массиве сохраненных ключей:

 def consolidate_cart(items)
  items_keys = items.flat_map(amp;:keys)
  items.inject(:merge).map do |key, value|
    { key => value.merge(count: items_keys.count(key)) }
  end
end

p consolidate_cart(cart_items)
# [{"AVOCADO"=>{:price=>3.0, :clearance=>true, :count=>2}}, {"KALE"=>{:price=>3.0, :clearance=>false, :count=>1}}]
  

Пошаговое представление функционирования метода:

Вы сопоставляете ключи каждого элемента хэша ( items.flat_map(amp;:keys) ):

 ["AVOCADO", "AVOCADO", "KALE"]
  

Вы объединяете хэш внутри items ( items.inject(:merge) ):

 {"AVOCADO"=>{:price=>3.0, :clearance=>true}, "KALE"=>{:price=>3.0, :clearance=>false}}
  

Когда вы перебираете предыдущий сгенерированный хэш, вы объединяете с каждым значением хэша ключ count ( { key => value.merge(count: items_keys.count(key)) } ):

 # {:price=>3.0, :clearance=>true}
# {:count=>2}
# => {:price=>3.0, :clearance=>true, :count => 2}
  

Я уже видел, что мой ответ не соответствует ожидаемому результату. Это делает:

 def consolidate_cart(items)
  items.inject(:merge).each_with_object(items: items.flat_map(amp;:keys)) do |(k, v), hash|
    hash[k] = v.merge(count: hash[:items].count(k))
  end.reject { |k, _| k == :items }
end
  

Комментарии:

1. не могли бы вы объяснить, что делает каждая часть вашего кода, пожалуйста?

2. Я добавил кое-что, чтобы объяснить, что @ZubairMaqsood

3. Это имеет тот недостаток, что count выполняется для каждого ключа (вычислительная сложность ( O(n**2) ), тогда как эти подсчеты могут быть получены путем выполнения одного прохода через эти ключи ( O(n) ).

Ответ №2:

Я хотел бы предложить способ рассмотреть также случай, когда price или clearance одного и того же продукта ( String ) может отличаться (поскольку вы не имеете дело с идентификаторами базы данных):

 cart_items = [
  {"AVOCADO" => {:price => 3.0, :clearance => true }},
  {"AVOCADO" => {:price => 4.0, :clearance => false }},
  {"AVOCADO" => {:price => 3.0, :clearance => true }},
  {"AVOCADO" => {:price => 4.0, :clearance => true }},
  {"KALE"    => {:price => 3.0, :clearance => false}},
  {"AVOCADO" => {:price => 4.0, :clearance => true }},
  {"AVOCADO" => {:price => 4.0, :clearance => true }}
]
  

В этом случае это возможный способ консолидации:

 cart_items.map{ |h| h.values.first.merge(product: h.keys.first) }
  .group_by(amp;:itself)
  .transform_values { |v| v.first.merge(count: v.size)}.values
  

Он возвращает:

 #=> [{:price=>3.0, :clearance=>true, :product=>"AVOCADO", :count=>2}, {:price=>4.0, :clearance=>false, :product=>"AVOCADO", :count=>1}, {:price=>4.0, :clearance=>true, :product=>"AVOCADO", :count=>3}, {:price=>3.0, :clearance=>false, :product=>"KALE", :count=>1}]
  

Вы всегда можете добавить .group_by{ |h| h[:product] } , чтобы получить

 #=> {"AVOCADO"=>[{:price=>3.0, :clearance=>true, :product=>"AVOCADO", :count=>2}, {:price=>4.0, :clearance=>false, :product=>"AVOCADO", :count=>1}, {:price=>4.0, :clearance=>true, :product=>"AVOCADO", :count=>3}], "KALE"=>[{:price=>3.0, :clearance=>false, :product=>"KALE", :count=>1}]}
  

Или для корзины в вашем сообщении:

 #=> {"AVOCADO"=>[{:price=>3.0, :clearance=>true, :product=>"AVOCADO", :count=>2}], "KALE"=>[{:price=>3.0, :clearance=>false, :product=>"KALE", :count=>1}]}
  

Не совсем тот результат, который требуется, но, возможно, это может быть полезно. Или нет.

Ответ №3:

 cart_items.each_with_object(Hash.new(0)) { |g,h| h[g]  = 1 }.
  map { |g,cnt| { g.keys.first=>g.values.first.merge(count: cnt) } }

  #=> [{"AVOCADO"=>{:price=>3.0, :clearance=>true, :count=>2}},
  #    {"KALE"=>{:price=>3.0, :clearance=>false, :count=>1}}]           
  

Hash.new(0) иногда называется подсчетным хэшем. Посмотрите на форму Hash::new, которая принимает аргумент, равный значению хэша по умолчанию. Мы получаем:

 cart_items.each_with_object(Hash.new(0)) { |g,h| h[g]  = 1 }
  #=> {{"AVOCADO"=>{:price=>3.0, :clearance=>true}}=>2,
  #    {"KALE"=>{:price=>3.0, :clearance=>false}}=>1} 
  

Ответ №4:

 cart_items.group_by(amp;:itself).map{ |item, group| item[item.keys.first][:count] = group.size; item} 
  

Для демонстрации https://rextester.com/MFH44079