I have an array of hashes (price_params['items']
) where each item has a key called quantity
what I'm trying to do is to clean every duplicate of each item but keeping the count of the times the item was found in the array in the unique one that I'm leaving (using the quantity
key to keep that value)
This is my code so far:
def clean_duplicated_offers
price_params['items'].each do |item|
max_count = price_params['items'].count(item)
next unless max_count > 1
price_params['items'].delete(item)
item['quantity'] = max_count
price_params['items'] << item
end
end
It works well, but it feels weird to delete every occurrence of the item in the array just to add it back to it.
Is there any better way to achieve this?
2 Answers 2
Does it have to be an Array
? I.e. is the order important?
If not, then there is actually a data structure that does exactly what you want: the multiset. A multiset is just like a set, except that its elements have a multiplicity. In other words, a set can tell you whether or not an element is a member, a multiset in addition can tell you how often it is a member.
Basically, if you use a multiset, you will not have to do anything, since the multiset keeps track of the multiplicity (i.e. your quantity
) for you.
There is no multiset in the Ruby core library or standard library, but there are a couple of third-party libraries and gems. I'll just grab one randomly, it doesn't really matter which one; their APIs are fairly similar.
require 'multiset'
price_params_items = %w[item2 item1 item3 item2 item3 item3]
result = Multiset[*price_params_items]
#=> #<Multiset:#2 "item2", #1 "item1", #3 "item3">
And that's it! You might ask yourself, where is the algorithm gone? That is a general property of programming: if you find the right data structure(s), the algorithm(s) become(s) much simpler, or in this case, even vanishes completely.
Unfortunately, for this specific implementation of multiset, there is no direct way to retrieve the multiplicity of an element, but you can convert it to a hash, and then you get what you need:
result.to_hash
#=> { "item2" => 2, "item1" => 1, "item3" => 3 }
-
\$\begingroup\$ Very interesting! In this case I'm not able to use third party libraries for this matter, but I will keep an eye on Multisets for future functionalities. Thanks! \$\endgroup\$Sebastian Delgado– Sebastian Delgado2019年12月09日 04:02:26 +00:00Commented Dec 9, 2019 at 4:02
I ended up doing something like this:
def clean_duplicated_offers
price_params['items'] = price_params['items'].each_with_object([]) do |item, items_array|
if items_array.include? item
items_array.detect { |i| i == item }['quantity'] += 1
else
items_array << item
end
end
end
price_params['items']
(Attempt to) insert each into the empty array. Ifitem
does not exist there, insert it, otherwise add one to the existingitem['quantity']
. Setprice_params['items']
to this new array. \$\endgroup\$