1
\$\begingroup\$

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?

\$\endgroup\$
1
  • 1
    \$\begingroup\$ create an empty array. Iterate price_params['items'] (Attempt to) insert each into the empty array. If item does not exist there, insert it, otherwise add one to the existingitem['quantity']. Set price_params['items'] to this new array. \$\endgroup\$
    – radarbob
    Dec 4, 2019 at 2:43

2 Answers 2

2
\$\begingroup\$

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 }
\$\endgroup\$
1
  • \$\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\$ Dec 9, 2019 at 4:02
1
\$\begingroup\$

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
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.