【问题标题】:Operate on collection once, to sum weighted average of fees对收款操作一次,对费用进行加权平均
【发布时间】:2013-01-22 12:30:16
【问题描述】:
return new SchoolFees(
        new Percentage(schoolFeesResult.Sum(x => (x.Amount.Value / totalFees) * x.TuitionFee.Value)),
        new Percentage(schoolFeesResult.Sum(x => (x.Amount.Value / totalFees) * x.TravellingFee.Value)),
        new Percentage(schoolFeesResult.Sum(x => (x.Amount.Value / totalFees) * x.ResidentialFee.Value)));

有没有一种方法可以让我对schoolFeesResult 进行一次操作来计算每种不同类型费用的加权平均值(TuitionTravellingResidence)。基本上我不希望(x.Amount.Value / totalFees) 在我的代码中出现 3 次?

【问题讨论】:

    标签: c# linq c#-4.0 c#-3.0


    【解决方案1】:

    你可以这样使用:

    var fees = from fee in schoolFeesResult
               let weight = fee.Amount.Value / totalFees
               select new 
               {
                   TuitionFee = weight * fee.TuitionFee.Value,
                   TravellingFee = weight * fee.TravellingFee.Value,
                   ResidentialFee = weight * fee.ResidentialFee.Value
               };
    
    // if the calculation of the fees is a performance bottleneck,
    // uncomment the next line:
    // fees = fees.ToList();
    
    return new SchoolFees(
        new Percentage(fees.Sum(x => x.TuitionFee),
        new Percentage(fees.Sum(x => x.TravellingFee),
        new Percentage(fees.Sum(x => x.ResidentialFee));
    

    你可以走得更远:

    var fees = (from fee in schoolFeesResult
                let weight = fee.Amount.Value / totalFees
                group fee by 1 into g
                select new 
                {
                    TuitionFee = g.Sum(x => weight * x.TuitionFee.Value),
                    TravellingFee = g.Sum(x => weight * x.TravellingFee.Value),
                    ResidentialFee = g.Sum(x => weight * x.ResidentialFee.Value)
                }).Single();
    
    return new SchoolFees(
        new Percentage(fees.TuitionFee,
        new Percentage(fees.TravellingFee,
        new Percentage(fees.ResidentialFee);
    

    但我怀疑第二个版本是个好主意。它使代码难以理解。我添加它纯粹是出于学术原因,以展示什么是可能的。

    【讨论】:

    • 我认为你需要ToList 否则每笔费用将计算三倍
    • @lazyberezovsky:没错。如果这是一个问题,您可以毫无问题地添加ToList。我没有添加它,因为我认为这里没有真正的区别。而且它会使代码看起来不那么“漂亮” :-) 不过,我添加了一条评论,因为它确实是一个重要的点。
    • 哈哈 :) +1 代码不错。如果代码看起来不错,谁在乎性能!
    【解决方案2】:

    又一个脑洞大开的解决方案

    Func<Func<Fee, decimal>, decimal> totalFee = feeSelector =>
       schoolFeesResult.Sum(x => x.Amount.Value / totalFees * feeSelector(x));
    
    return new SchoolFees(
       new Percentage(totalFee(f => f.TuitionFee.Value)),
       new Percentage(totalFee(f => f.TravellingFee.Value)),
       new Percentage(totalFee(f => f.ResidentialFee.Value))
    );
    

    甚至更短:

    Func<Func<Fee, decimal>, Percentage> percentageOf = feeSelector =>
       new Percentage(schoolFeesResult.Sum(x => 
             x.Amount.Value / totalFees * feeSelector(x)));
    
    return new SchoolFees(
       percentageOf(f => f.TuitionFee.Value),
       percentageOf(f => f.TravellingFee.Value),
       percentageOf(f => f.ResidentialFee.Value)
    );
    

    【讨论】:

    • +1:很好,我喜欢。注意:这也会枚举schoolFeesResult 3 次。
    • @DanielHilgarth 谢谢!是的,它枚举了 3 次,但很好 :) BTW 几乎没有区别 - 它枚举了 3 次(就像原始代码一样),但我不计算每个 weight * fee :)
    • 每个权重(x.Amount.Value / totalFees)会计算3次,和原代码一样。就像原始代码一样,它只会计算每个单独的weight * fee 一次。因此,就计算而言,它也是如此。只是有更多的风格:)
    【解决方案3】:

    我使用WeightedAverage 的这个实现作为IEnumerable&lt;T&gt; 的扩展方法:

    public static double? WeightedAverage<TSource>(this IEnumerable<TSource> source
                                                           , Func<TSource, float> weightField
                                                           , Func<TSource, double> propertyToWeight)
    {
        var total = source.Sum(weightField);
    
        var sum = source.Select(item => weightField(item) * propertyToWeight(item)).Sum();
        return sum / total;
    
    }  
    

    当然有一些重载需要处理singlesingle?double。也许您可以调整它以适应您想要实现的目标。

    【讨论】:

      【解决方案4】:

      我假设您可以将它放在另一个查询中,恕我直言,它也更可红色:

      var percentages = schoolFeesResult
          .Select(x => new { SFR = x, AmoundDivFees = (x.Amount.Value / totalFees)})
          .Select(x => new { 
              TuitionFee = x.AmoundDivFees * x.SFR.TuitionFee.Value,
              TravellingFee = x.AmoundDivFees * x.SFR.TravellingFee.Value,
              ResidentialFee = x.AmoundDivFees * x.SFR.ResidentialFee.Value
          });
      return new SchoolFees(
          new Percentage(percentages.Sum(x => x.TuitionFee)),
          new Percentage(percentages.Sum(x => x.TravellingFee)),
          new Percentage(percentages.Sum(x => x.ResidentialFee)));
      

      当然我无法测试它。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-06-19
        • 2020-03-26
        • 1970-01-01
        • 2012-06-06
        • 2019-08-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多