`flat_map`在大数组上运行缓慢

时间:2016-02-23 14:21:41

标签: arrays ruby sum

我有:

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

我想计算一定总量的平均值。假设有人想购买40件,他希望这是最便宜的方式。这意味着平均价格

(540 * 12 + 590 * 18 + 630 * (40-18-12)) / 40 = 585
货币单位。

我已经问了问题here,并得到了答案。但是,在非常大的数组上,此方法很慢:

def self.calculateMiddlePrice(prices, amount)    
  @order = prices.flat_map{|item| [item[:price].to_i] * item[:volRemain].to_i }
  unless amount > @order.count
    @order[1..amount].reduce(:+) / amount
  else
    @order.reduce(:+) / @order.count
  end
end

我尝试了懒惰的算子,但没有任何成功:

@order = prices.lazy.flat_map{|item| [item[:price].to_i] * item[:volRemain].to_i }.force    

有没有更有效的方法来解决我的初始问题?

2 个答案:

答案 0 :(得分:1)

这样可以在没有冗余数据复制的情况下一次性提高性能:

编辑:在OP的评论和orig的变化后更新答案。问题

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

to_buy = 40
wp, rem = testArray.inject({wp: 0, ct: to_buy}) do |m,h|
  unless m[:ct].zero?
    delta = [h[:volume], m[:ct]].min
    m[:wp] += h[:price] * delta
    m[:ct] -= delta
  end
  m
end.values

p wp / to_buy, rem    #  585 0

使用Enumerable#inject Array混合的数据结构折叠

答案 1 :(得分:1)

上述评论者有正确的想法。这是一个简单的reduce操作 - 无需构建长数组:

def calc_price(prices, volume)
  volume_remaining = volume

  total = prices.reduce(0) do |sum, item|
    if item[:volume] > volume_remaining
      break sum + item[:price] * volume_remaining
    end
    volume_remaining -= item[:volume]
    sum + item[:price] * item[:volume]
  end

  total / volume
end

此方法使用pricesEnumerable#reduce中的项进行迭代,并保持正在运行的sum。在每次迭代中,它会检查item[:volume]是否大于volume_remaining。如果是,则将item[:price] * volume_remaining添加到正在运行的sum并返回它。如果不是,则会从item[:volume]中减去volume_remaining,然后将item[:price] * item[:volume]添加到正在运行的sum,然后转到下一个项目。

它有效,甚至:

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

puts calc_price(prices, 40)
# => 585

P.S。您可能希望对此进行两项修改:

  1. 如果您需要小数结果(即如果平均值不是整数),请将total / volume更改为total.to_f / volume

  2. 如果您想在无法满足音量时提出错误,那么这只是一个很小的改变:

    def calc_price(prices, volume)
      volume_remaining = volume
    
      total = prices.reduce(0) do |sum, item|
        if item[:volume] > volume_remaining
          sum, volume_remaining = sum + item[:price] * volume_remaining, 0
          break sum
        end
        volume_remaining -= item[:volume]
        sum + item[:price] * item[:volume]
      end
    
      return total / value unless volume_remaining > 0
      raise "Required volume of #{volume} could not be satisfied!"
    end
    

    然后:

    puts calc_price(prices, 100)
    # => RuntimeError: Required volume of 100 could not be satisfied!