这是为了处理库存数据;数据采用以下格式:
public class A
{
public int Price;
public int Available;
}
以以下数据为例:
var items = new List<A>
{
new A { Price = 10, Available = 1000 },
new A { Price = 15, Available = 500 },
new A { Price = 20, Available = 2000 },
};
我的查询返回特定数量的平均价格,例如:
如果我请求的数量为100,我的平均价格为10
如果我的需求量为1200,则我以10的价格获取第一个1000,然后以15的价格获取下一个200 等等
我已经在C#中实现了这一点,但是我试图找出是否可以直接使用LINQ与数据库迭代器一起完成。
我得到的价格已经按价格排序,但是我不知道如何解决这个问题。
编辑:
这是代码:
public static double PriceAtVolume(IEnumerable<A> Data, long Volume)
{
var PriceSum = 0.0;
var VolumeSum = 0L;
foreach (var D in Data)
{
if (D.Volume < Volume)
{
PriceSum += D.Price * D.Volume;
VolumeSum += D.Volume;
Volume -= D.Volume;
}
else
{
PriceSum += D.Price * Volume;
VolumeSum += Volume;
Volume = 0;
}
if (Volume == 0) break;
}
return PriceSum / VolumeSum;
}
和测试代码:
var a = new List<A>
{
new A { Price = 10, Volume = 1000 },
new A { Price = 15, Volume = 500 },
new A { Price = 20, Volume = 2000 }
};
var P0 = PriceAtVolume(a, 100);
var P1 = PriceAtVolume(a, 1200);
说明:
上面我说过我想将其移至LINQ以使用数据库迭代器,因此我想避免扫描整个数据并在计算出答案时停止迭代。数据已经在数据库中按价格排序了。
答案 0 :(得分:2)
这可能是您可以获得的最多的Linqy。它使用Aggregate
方法,特别是Aggregate
的三个重载版本中最复杂的一种,它接受三个参数。第一个参数是种子,并使用置零的ValueTuple<long, decimal>
进行初始化。第二个参数是累加器函数,具有将种子和当前元素组合为新种子的逻辑。第三个参数采用最终的累积值并将其投影到理想的平均值。
public static decimal PriceAtVolume(IEnumerable<A> data, long requestedVolume)
{
return data.Aggregate(
(Volume: 0L, Price: 0M), // Seed
(sum, item) => // Accumulator function
{
if (sum.Volume == requestedVolume)
return sum; // Goal reached, quick return
if (item.Available < requestedVolume - sum.Volume)
return // Consume all of it
(
sum.Volume + item.Available,
sum.Price + item.Price * item.Available
);
return // Consume part of it (and we are done)
(
requestedVolume,
sum.Price + item.Price * (requestedVolume - sum.Volume)
);
},
sum => sum.Volume == 0M ? 0M : sum.Price / sum.Volume // Result selector
);
}
更新:我将返回类型从双精度更改为十进制,因为decimal is the preferred type for currency values。
如果此函数经常用相同的数据调用,并且数据列表很大,则可以通过将累积的摘要存储在List<(long, decimal)>
中并应用BinarySearch
来对其进行优化。快速找到所需的条目。但是,它变得很复杂,我不希望优化的前提条件会经常出现。
答案 1 :(得分:0)
您可以做一些事情以按顺序生成商品价格。例如
public class A
{
public int Price;
public int Available;
public IEnumerable<int> Inv => Enumerable.Repeat(Price, Available);
}
var avg1 = items.SelectMany(i => i.Inv).Take(100).Average(); // 10
var avg2 = items.SelectMany(i => i.Inv).Take(1200).Average(); // 10.8333333333333
答案 2 :(得分:0)
这也很好(尽管不是单线的):
private static decimal CalculateWeighedAverage(List<A> amountsAndPrices, int requestedVolume)
{
int originalRequestedVolume = requestedVolume;
return (decimal)amountsAndPrices.Sum(amountAndPrice =>
{
int partialResult = Math.Min(amountAndPrice.Available, requestedVolume) * amountAndPrice.Price;
requestedVolume = Math.Max(requestedVolume - amountAndPrice.Available, 0);
return partialResult;
}) / originalRequestedVolume;
}
只要请求的数量大于0,就获取价格*的总和,并减去每次“求和迭代”中列表中每个项目的数量。最后除以原始请求的体积。
答案 3 :(得分:0)
我认为,使用LINQ最好的办法是最大程度地减少服务器上运行的总计算量,并在客户端上进行大部分计算,但是最小化从服务器上下载的量。
我假设items
已被投影到两个最小列(Price
,Availability
)。如果没有,可以先添加Select
,然后再将数据从数据库中拉到orderedItems
。
// find price of last item needed; worst case there won't be one
var lastPriceItem = items.Select(i => new { i.Price, RT = items.Where(it => it.Price <= i.Price).Sum(it => it.Available) }).FirstOrDefault(irt => irt.RT > origReqVol);
// bring over items below that price
var orderedItems = items.OrderBy(i => i.Price).Where(i => i.Price <= lastPriceItem.Price).ToList();
// compute running total on client
var rtItems = orderedItems.Select(i => new {
Item = i,
RT = orderedItems.Where(i2 => i2.Price <= i.Price).Sum(i2 => i2.Available)
});
// computer average price
var reqVol = origReqVol;
var ans = rtItems.Select(irt => new { Price = irt.Item.Price, Quantity = Math.Min((reqVol -= irt.Item.Available)+irt.Item.Available, irt.Item.Available) })
.Sum(pq => pq.Price * pq.Quantity) / (double)origReqVol;