如何使用Aggregate函数汇总求和函数?

时间:2019-02-07 11:04:30

标签: c# linq aggregate

我有一个函数,该函数需要IEnumerable并执行GroupJoin来构建字符串。用GroupJoin中的一个简单的总和就可以很好地工作。 我需要更改此值,以便不仅将这些值相加,而且还要累加。

我认为我需要在代码行中添加一个Aggregate函数:item.Sum(x => x.Amount)

IEnumerable<Items> items = {.....};
var list = Enumerable.Range(7, 6).Concat(Enumerable.Range(1, 6))
    .GroupJoin(
        items,
        range => range,
        item => item.TransactionDate.Value.Month,
        (range, item) => string.Concat(
            //Example of string output: { t: "03 Jul 2018", y: 44 }, 
            "{ t: \"",
            new DateTime(
                range > 6 ? 2018 : 2019,
                range,
                1).ToString("dd MMM yyyy"),
            "\", y: ",
            item.Sum(x => x.Amount).ToString(),
            " }"
          ))
     .ToArray();

当前结果:返回一个字符串,例如:{t:“ 2018年7月1日”,y:3},{t:“ 2018年8月1日”,y:4},{t:“ 2018年9月1日”,y :1}

必需结果:返回一个字符串,其中y处的值是累加的。例如:{t:“ 2018年7月1日”,y:3},{t:“ 2018年8月1日”,y:7},{t:“ 2018年9月1日”,y:8}

2 个答案:

答案 0 :(得分:1)

如果可能的话,可以使用汇总。恕我直言,它看起来很可怕,不易理解,难以测试和维护:

TAccumulate聚合(IEnumerable)

  • TSource:金额顺序IEnumerable<int>
  • TAccumulate:总数为IEnumerable<int>的序列
  • 所以从空的List<int>
  • 开始
  • 每个迭代步骤:将总计添加到总计序列中

// first extract the amounts:
IEnumerable<int> amountTotals = itemsInGroup.Select(item => item.Amount)
// Then aggregate.
.Aggregate(new List<int>()            // Seed with empty list

    // every iteration: add a new Total to the TAccumulate and return this TAccumulate:
    (tAccumulate, newItem) => 
    {
         tAccumulate.Add(newItem);
         return tAccumulate;
    })

打底机解决方案

如果要创建扩展功能,它将看起来更加整洁:

  • 将范围值转换为DateTime文本表示形式的一个
  • 将加入该范围的项目转换为总计序列的一个

让我们编写一些扩展功能,因此您可以将它们用作类似LINQ的语句。 参见extension methods demystified

将范围值转换为其DateTime字符串表示形式

static string ToDateTimeText(this int rangeValue)
{
    return $"t: \"{new DateTime(range > 6 ? 2018 : 2019, range, 1):dd MMM yyyy}\"";
}

将序列转换为整数,然后转换为总计。
例如:序列{3,4,5,10} => {3,7,12,22}

static IEnumerable<int> ToTotals(IEnumerable<int> source)
{
     // TODO: exception if source null

     // try to get the first element of the input sequence
     var enumerator = source.GetEnumerator;
     if (enumerator.MoveNext())
     {   // first item fetched; yield return first item
         int total = enumerator.Current;
         yield return total;
         // calculate the rest:
         while (enumerator.MoveNext())
         {   // there is a next item: Calculate total and yield return
             total += enumerator.Current;
             yield return total;
         }
     }
     // else: empty input sequence empty, output is empty
}

现在有了这些功能,您的groupBy ResultSelector将会很简单:

(rangeValue, itemsWithThisRangeValue) => new
{
    Date = rangeValue.ToDateTimeText(),
    AmountTotals = itemsWithThisRangeValue
       .Select(item => item.Amount)
       .ToTotals(),
}

答案 1 :(得分:1)

组联接是解决您的问题的一种好方法,但是我们应该保持可读性(并避免对项目集合进行多次迭代)。

因此,我不会在联接内使用聚合。

var groups = Enumerable.Range(7, 6).Concat(Enumerable.Range(1, 6)).GroupJoin(
            items,
            m => m,
            i => i.TransactionDate.Value.Month,
            (m, itemCol) => new { month = m, items = itemCol }); //Create anonymous type with month and items property.


    List<string> outputs = new List<string>(); //Contains your output
    int sum = 0;

    foreach (var group in groups) //Iterate the groups
    {
        sum += group.items.Sum(i => i.Amount);
        outputs.Add( //I used string.Format for readability. Does not change functionality
            string.Format("{{ t: \"{0}\", y: {1} }}",
                new DateTime(group.month > 6 ? 2018 : 2019, group.month, 1).ToString("dd MMM yyyy"),
                sum));
    }