首页 > 解决方案 > 如何使用 C#/LINQ 计算加权平均值

问题描述

这是处理库存数据;数据格式如下:

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 },
};

我的查询返回特定数量的平均价格,例如:

我已经在 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 以使用数据库迭代器,所以我想避免扫描整个数据并在计算答案时停止迭代。数据已经在数据库中按价格排序。

标签: c#linq

解决方案


这可能是你能得到的最多的 Linqy。它使用接受三个参数的Aggregate方法,特别是三个重载版本中最复杂的一个。Aggregate第一个参数是种子,它被初始化为 zeroed 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
    );
}

更新:我将返回类型从双精度更改为小数,因为小数是货币值的首选类型

顺便说一句,如果经常使用相同的数据调用此函数,并且数据列表很大,则可以通过将累积的摘要存储在 a 中来优化它List<(long, decimal)>,并应用BinarySearch以快速找到所需的条目。但是它变得复杂了,我不希望优化的先决条件会经常出现。


推荐阅读