mohnstrudel
mohnstrudel

Reputation: 649

Sum array values up to a certain total amount

I've got an array of hashes (sorted), something like this:

testArray = [{price: 540, volume: 12},
            {price: 590, volume: 18},
            {price: 630, volume: 50}]

Now I want to calculate the mean value up to certain total volume. Let's say someone wants to buy 40 pieces and he wants it the cheapest way. It would mean an average price of (540*12+590*18+630*50)/40 money units.

My first attempt is following:

testArray.each do |priceHash|
    @priceArray << priceHash.fetch(:price)
    @volumeArray << priceHash.fetch(:volume)
end


def calculateMiddlePrice(priceArray, volumeArray, totalAmount)
result = 0

# Here some crazy wild magic happens

(0...volumeArray.count).inject(0) do |r, i| 

    if (volumeArray[0..i].inject(:+)) < totalAmount

        r += volumeArray[i]*priceArray[i]
    elsif volumeArray[0..i-1].inject(:+) < totalAmount && volumeArray[0..i].inject(:+) >= totalAmount 

        theRest = volumeArray[i] - (volumeArray[0..i].inject(:+) - totalAmount)
        r += theRest * priceArray[i]
    elsif volumeArray[0] > totalAmount

        r = totalAmount * priceArray[0]
    end

    result = r      
end
result
end

Right now I'm not even sure why it works, but it does. However this absolutely ridiculous code in my eyes.

My second thought was to cut my testArray when the total amount is achieved. The code looks better

testAmount = 31

def returnIndexForSlice(array, amount)
 sum = 0

 array.each_index do |index|

  p sum += array[index][:volume]
  if sum >= amount
        return index+1
  end
 end
end

testArray.slice(0,returnIndexForSlice(testArray, testAmount))

Still, this just doesn't feel that right, "rubyish" if you could say so. I checked almost every method for array class, played around with bsearch, however I can't figure out a really elegant way of solving my problem.

What's crossing my mind is something like that:

amountToCheck = 31
array.some_method.with_index {|sum, index| return index if sum >= amountToCheck}

But is there such method or any other way?

Upvotes: 0

Views: 140

Answers (1)

Mark Thomas
Mark Thomas

Reputation: 37517

Given your prices array of hashes:

prices = [  {price: 540, volume: 12},
            {price: 590, volume: 18},
            {price: 630, volume: 50}]

You can calculate your result in 2 steps.

def calc_price(prices, amount)
  order = prices.flat_map{|item| [item[:price]] * item[:volume] } #step 1      
  order.first(amount).reduce(:+)/amount #step 2
end

Step 1: Create an array with each individual item in it (if the prices aren't sorted, you have to add a sort_by clause). In other words, expand the prices into a numeric array containing twelve 540's, 18 590's, etc. This uses Ruby's array repetition method: [n] * 3 = [n, n, n].

Step 2: Average the first n elements

Result:

calc_price(prices, 40)
=> 585

Upvotes: 3

Related Questions