I have an array of hashes, that I basically want to merge and convert to a single hash and at the same time I want to count the number of times a key:value pair occurs.
The original array is
cart_items = [
{"AVOCADO" => {:price => 3.0, :clearance => true }},
{"AVOCADO" => {:price => 3.0, :clearance => true }},
{"KALE" => {:price => 3.0, :clearance => false}}
]
I have tried this however I am not getting what I want. My attempt at this is below, if anyone could explain where I am going wrong, that would be great.
My attempt at this problem is this.
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)
I expect the output to be
{
"AVOCADO" => {:price => 3.0, :clearance => true, :count => 2},
"KALE" => {:price => 3.0, :clearance => false, :count => 1}
}
But I get an output of
[{"AVOCADO"=>{:price=>3.0, :clearance=>true}, :count=>2}, {"KALE"=>{:price=>3.0, :clearance=>false}, :count=>2}]
You can merge to v
(within the map
call) the value of count ( v.merge(:count => v.length)
), so this will add the count key to the v
hash, you'll get something like:
[
{"AVOCADO"=>{:price=>3.0, :clearance=>true, :count=>2},
{"KALE"=>{:price=>3.0, :clearance=>false, :count=>2}
]
But anyways the values for :count
are going to be wrong.
In the other hand, you can get all the keys from each hash in cart_items, merge the hashes, and then merge a new key with the count of that key in the stored keys array:
def consolidate_cart(items)
items_keys = items.flat_map(&: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}}]
A part by part view of the method functioning:
You map the keys of each hash item ( items.flat_map(&:keys)
):
["AVOCADO", "AVOCADO", "KALE"]
You merge the hash within items
( items.inject(:merge)
):
{"AVOCADO"=>{:price=>3.0, :clearance=>true}, "KALE"=>{:price=>3.0, :clearance=>false}}
When you iterate over the previous generated hash, you merge to each hash value the count key ( { key => value.merge(count: items_keys.count(key)) }
):
# {:price=>3.0, :clearance=>true}
# {:count=>2}
# => {:price=>3.0, :clearance=>true, :count => 2}
I've already seen my answer doesn't correspond with the expected output. This does:
def consolidate_cart(items)
items.inject(:merge).each_with_object(items: items.flat_map(&:keys)) do |(k, v), hash|
hash[k] = v.merge(count: hash[:items].count(k))
end.reject { |k, _| k == :items }
end
I would like to suggest a way to consider also the case where price
or clearance
of the same product ( String
) can be different (since you are not dealing with database ids):
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 }}
]
In this case this is a possible way to consolidate:
cart_items.map{ |h| h.values.first.merge(product: h.keys.first) }
.group_by(&:itself)
.transform_values { |v| v.first.merge(count: v.size)}.values
It is returning:
#=> [{: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}]
You can always append .group_by{ |h| h[:product] }
.group_by{ |h| h[:product] }
to get
#=> {"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}]}
Or for the cart in your post:
#=> {"AVOCADO"=>[{:price=>3.0, :clearance=>true, :product=>"AVOCADO", :count=>2}], "KALE"=>[{:price=>3.0, :clearance=>false, :product=>"KALE", :count=>1}]}
Not exactly the same output as required, but maybe this can be useful. Or not.
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)
is sometimes called a counting hash . See the form of Hash::new that takes an argument equal to the hash's default value . We obtain:
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}
cart_items.group_by(&:itself).map{ |item, group| item[item.keys.first][:count] = group.size; item}
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.