2

Let's say I have an array of product IDs and Quantities, like this:

records = [[1, 10], [1, 30], [4, 10], [4, 100], [5, 45]]

What's the easiest/most efficient way in Ruby to achieve a hash of the combined products and quantities, like this?

products_needed = [{id: 1, count:40}, {id: 4, count: 110}, {id:5, count:45}]
2
  • Possible duplicate of What is the best way to convert an array to a hash in Ruby Commented Aug 23, 2017 at 17:45
  • Is there a particular reason for wanting an array of hashes rather than one hash? If you did the latter you would do without the labels but it would be fast searchable by ID and take up less memory. Commented Aug 23, 2017 at 20:29

4 Answers 4

4

Try this:

records.group_by(&:first).map do |id, records_for_id|
  {
     id: id,
     count: records_for_id.sum(&:last)
  }
end
Sign up to request clarification or add additional context in comments.

Comments

3

If you're in Ruby 2.4+, you can use group_by followed by transform_values:

records.group_by(&:first) # => {1=>[[1, 10], [1, 30]], 4=>[[4, 10], [4, 100]], 5=>[[5, 45]]}

records.group_by(&:first).transform_values do |values|
  values.sum(&:last)
end # => {1=>40, 4=>110, 5=>45}

1 Comment

transform_values requires Ruby 2.4+ (or Rails)
1
records
.each_with_object(Hash.new(0)){|(k, v), h| h.merge!(k => v){|_, v1, v2| v1 + v2}}
# => {1=>40, 4=>110, 5=>45}

records
.each_with_object(Hash.new(0)){|(k, v), h| h.merge!(k => v){|_, v1, v2| v1 + v2}}
.map{|k, v| {id: k, count: v}}
# => [{:id=>1, :count=>40}, {:id=>4, :count=>110}, {:id=>5, :count=>45}]

Comments

0

You don't have an array of product IDs and Quantities. You have an array of arrays of integers. The easiest way to deal with this array of arrays of integers is to not have an array of arrays of integers but an Order:

class Product
  def to_s; 'Some Product' end
  alias_method :inspect, :to_s
end

class LineItem
  attr_reader :product, :count

  def initialize(product, count)
    self.product, self.count = product, count
  end

  def to_s; "#{count} x #{product}" end
  alias_method :inspect, :to_s

  private

  attr_writer :product, :count
end

class Order
  include Enumerable

  def initialize(*line_items)
    self.line_items = line_items
  end

  def each(&blk) line_items.each(&blk) end

  def items
    group_by(&:product).map {|product, line_items| LineItem.new(product, line_items.sum(&:count)) }
  end

  def to_s; line_items.map(&:to_s).join(', ') end
  alias_method :inspect, :to_s

  private

  attr_accessor :line_items
end

Now, assuming that you receive your data in the form of an Order, instead of an array of arrays of integers, like this:

product1 = Product.new
product4 = Product.new
product5 = Product.new

order = Order.new(
  LineItem.new(product1, 10), 
  LineItem.new(product1, 30), 
  LineItem.new(product4, 10), 
  LineItem.new(product4, 100), 
  LineItem.new(product5, 45)
)

All you need to do is:

order.items
#=> [40 x Some Product, 110 x Some Product, 45 x Some Product]

The bottom line is: Ruby is an object-oriented language, not an array-of-arrays-of-integers-oriented language, if you use rich objects instead of arrays of arrays of integers, your problem will become much simpler.

Note: I used order processing as an example. If your problem domain is warehouse management or something else, there will be a similar solution.

3 Comments

Why would you assume he/she had any choice about the source data? I would assume this array of arrays came from something like a text file parser.
Then the OP can convert it to a proper object model at the place where it enters the system.
Yes but do you genuinely think your answer is a wiser choice than the simple accepted answer? This is a question of philosophy. Mine is that no philosophy suits all problem domains. If this part of the code is business-logic rich and likely to be revisited a lot and keep growing in complexity then a purist OOP approach becomes justified.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.