【问题标题】:Calculating Weighted Average with LINQ使用 LINQ 计算加权平均值
【发布时间】:2011-02-12 11:11:07
【问题描述】:

我的目标是根据另一个表的主键从一个表中获得加权平均值。

示例数据:

表1

Key     WEIGHTED_AVERAGE

0200    0

表2

ForeignKey    Length    Value
0200          105       52
0200          105       60
0200          105       54
0200          105       -1
0200          47        55

我需要根据段的长度获得加权平均值,并且我需要忽略 -1 的值。我知道如何在 SQL 中执行此操作,但我的目标是在 LINQ 中执行此操作。它在 SQL 中看起来像这样:

SELECT Sum(t2.Value*t2.Length)/Sum(t2.Length) AS WEIGHTED_AVERAGE
FROM Table1 t1, Table2 t2
WHERE t2.Value <> -1
AND t2.ForeignKey = t1.Key;

我对 LINQ 还是很陌生,很难弄清楚如何翻译它。结果加权平均值应约为 55.3。谢谢。

【问题讨论】:

    标签: c# linq weighted-average


    【解决方案1】:

    如果您确定对于Table2中的每个外键在Table1中都有对应的记录,那么您可以避免仅进行分组的join。

    在这种情况下,LINQ 查询是这样的:

    IEnumerable<int> wheighted_averages =
        from record in Table2
        where record.PCR != -1
        group record by record.ForeignKey into bucket
        select bucket.Sum(record => record.PCR * record.Length) / 
            bucket.Sum(record => record.Length);
    

    更新

    这是获取特定foreign_key 的wheighted_average 的方法。

    IEnumerable<Record> records =
        (from record in Table2
        where record.ForeignKey == foreign_key
        where record.PCR != -1
        select record).ToList();
    int wheighted_average = records.Sum(record => record.PCR * record.Length) /
        records.Sum(record => record.Length);
    

    获取记录时调用的 ToList 方法是为了避免在两个单独的 Sum 操作中聚合记录时执行两次查询。

    【讨论】:

    • 这将为每个不同的 ForeignKey 返回一个值。如果您只想要特定一个且只有 ForeignKey 的加权平均值,那么您可以避免使用 GroupBy,只需过滤具有所需外键的记录,然后执行聚合操作。我将编辑我的答案,向您展示如何操作。
    【解决方案2】:

    (回答 jsmith 对上述答案的评论)

    如果您不想循环浏览某些收藏,可以尝试以下方法:

    var filteredList = Table2.Where(x => x.PCR != -1)
     .Join(Table1, x => x.ForeignKey, y => y.Key, (x, y) => new { x.PCR, x.Length });
    
    int weightedAvg = filteredList.Sum(x => x.PCR * x.Length) 
        / filteredList.Sum(x => x.Length);
    

    【讨论】:

    • 您知道,我的解决方案假设您想要计算一组行的加权平均值,这些行的外键与第一个表中任何行的键值匹配。 Fede 的解决方案将为您提供特定外键的行。因此,请随意选择更合适的解决方案。
    【解决方案3】:

    这是 LINQ 的扩展方法。

    public static double WeightedAverage<T>(this IEnumerable<T> records, Func<T, double> value, Func<T, double> weight)
    {
        if(records == null)
            throw new ArgumentNullException(nameof(records), $"{nameof(records)} is null.");
    
        int count = 0;
        double valueSum = 0;
        double weightSum = 0;
    
        foreach (var record in records)
        {
            count++;
            double recordWeight = weight(record);
    
            valueSum += value(record) * recordWeight;
            weightSum += recordWeight;
        }
    
        if (count == 0)
            throw new ArgumentException($"{nameof(records)} is empty.");
    
        if (count == 1)
            return value(records.Single());
    
        if (weightSum != 0)
            return valueSum / weightSum;
        else
            throw new DivideByZeroException($"Division of {valueSum} by zero.");
    }
    

    这变得非常方便,因为我可以根据同一记录中的另一个字段获得任何一组数据的加权平均值。

    更新

    我现在检查除以零并抛出更详细的异常而不是返回 0。允许用户捕获异常并根据需要进行处理。

    【讨论】:

    • 谢谢,很有帮助。我最终把它变成了一个班轮...... public static float WeightedAverage(this IEnumerable items, Func value, Func weight) { return items.Sum(item = > value(item) * weight(item)) / items.Sum(weight); }
    • 我必须在计算后添加“If weightedSum.AlmostZero() return 0”,以防止在所有权重和/或所有值都为零时除以零。 AlmostZero 是一个扩展函数,用于检查双精度数是否为零。
    • 请注意,这会枚举记录两次。在许多应用程序中这没问题,但如果它们是从数据库中流式传输的,或者重新枚举不可能或代价高昂,它就会中断。我个人将“记录”键入为 IReadOnlyCollection 而不是 IEnumerable 以向调用者表明他们有责任处理此问题(例如,如有必要,首先调用 .ToList() )。
    猜你喜欢
    • 1970-01-01
    • 2021-11-24
    • 2010-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多