#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