Reputation: 649
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
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