如何以功能的方式获得中间总和列表?使用LINQ?

时间:2009-04-21 20:51:34

标签: c# linq functional-programming

给定一个对象列表,我需要返回一个列表,该列表包含对象以及到目前为止所见列表中所有对象的对象属性的总和。

更一般地给出

var input = new int[] {1,2,3}

我想得到

的输出
// does not compile but did not want to include extra classes.
var output = { (1,1), (2,3), (3,6) }; 

执行此操作的“正确”功能方法是什么?我当然可以采用标准的迭代方法来实现,但我正在寻找如何以功能性,懒惰的方式完成这项工作。

由于

7 个答案:

答案 0 :(得分:6)

我认为这是最短的方法:

int sum = 0;
var result = input.Select(i => new { i, S = sum += i });

答案 1 :(得分:6)

在功能方面,这是以下组合:

拉链

获取两个序列并创建一系列元素元组

地图

取一个函数f和一个序列,并为原始序列中的每个x返回一个新的序列f(x)

拉链是trivial in c# 4.0 从那里采取简单的实现我们

static class Enumerable 
{ 
    public static IEnumerable<TResult> Zip<TFirst, TSecond, TResult>(
        this IEnumerable<TFirst> first, 
        IEnumerable<TSecond> second, 
        Func<TFirst, TSecond, TResult> func) 
    { 
        var ie1 = first.GetEnumerator(); 
        var ie2 = second.GetEnumerator();

        while (ie1.MoveNext() && ie2.MoveNext()) 
            yield return func(ie1.Current, ie2.Current); 
    } 
}

然后我们需要地图。我们已经拥有它,这就是我们在c#中所谓的选择

IEnumerable<int> input = { 1,2,3,4 };
int a = 0;
var accumulate = input.Select(x => 
    {
         a += x; 
         return a;
    });

但是把它加入到它自己的方法中更安全(在c#中没有讨论)并​​允许支持任意类型/累积。

static class Enumerable 
{ 
    public static IEnumerable<T> SelectAccumulate<T>(
        this IEnumerable<T> seq,
        Func<T,T,T> accumulator) 
    { 
        var e = seq.GetEnumerator(); 
        T t = default(T);             
        while (e.MoveNext()) 
        {
            t = accumulator(t, e.Current);
            yield return t;
        } 
    } 
}

然后我们可以把它们放在一起

var input = new int[] {1,2,3};
var mapsum = input.Zip(
    input.SelectAccumulate((x,y) => x+y), 
    (a,b) => new {a,b});

这将迭代序列两次,但更通用。您可以选择在标准选择和简单闭包中自己执行累加器,但它不再像“构建块”那样有用,它是函数式编程背后的驱动力之一。

除了一个方法之外,元组支持是一种痛苦,因为匿名类型不会在没有相当麻烦的情况下遍历方法边界。 c#4.0中应包含一些基本元组。假设一个名为Pair<T,U>的元组类/结构,你可以这样做:

public static IEnumerable<Pair<T,T>> ZipMapAccumulate<T>(
    this IEnumerable<T> input,
    Func<T,T,T> accumulator)
{
    return input.Zip(
        input.SelectAccumulate((x,y) => accumulator (x,y)), 
        (a,b) => new Pair<T,T>(a,b));
}

//get an int specific one
public static Func<IEnumerable<int>, IEnumerable<Pair<int,int>>> 
    ZipMapSum()
{
    return input => Enumerable.ZipMapAccumulate(
        input, 
        (i,j) => i + j);
}

c#linq变得比f#更加繁琐是对运算符,currying和元组的不良支持,除非你将所有内容保存在一个函数中并且每次都为每种类型“重建它”。

答案 2 :(得分:2)

var output = input.Select((i, indexI) => 
    new {
           Element = i,
           RunningSum = input.Where((j, indexJ) => indexJ <= indexI).Sum()
        });

这将产生一个匿名类型的集合,其中包含两个属性Element和RunningSum。

<强> UPDTAE

这是另一种仅使用LINQ Aggregate扩展方法的解决方案。是的,它很难看。

var output = input.Aggregate(
   new List<KeyValuePair<Int32, Int32>>(),
   (result, element) =>
   {
      result.Add(new KeyValuePair<Int32, Int32>(
         element,
         element + result.LastOrDefault().Value));
      return result;
   });

答案 3 :(得分:2)

    int runningTotal=0;
    var p = input.Select((l)=>
        {
            runningTotal+=l;
            return new {l,Total=runningTotal};
        });

修改

Foreach将始终有序。打开反射器并查看List上的ForEach(ForEach在数组中不存在)但是所有ForEach都是对元素进行for循环。

我不确定选择,据我所知,但我从来没有挖过它。

答案 4 :(得分:1)

var input = new int[] {1,2,3}
var output = new List<KeyValuePair<int,int>>();
int runningTotal = 0;

foreach (int current in input)
{
  runningTotal += current;
  output.Add(new KeyValuePair(current, runningTotal);
}

如果你真的想要,将它转换为Linq .Foreach()函数会很容易。问题是如果你不想要一个单独的运行总计。

功能版:

intput.Foreach(current=>
{
    runningTotal += current;
      output.Add(new KeyValuePair(current, runningTotal);

}
);

答案 5 :(得分:0)

功能实现(无副作用)且无扩展名:

internal struct Tuple<T> {
    public T A;
    public T B;
}

internal class Program {
    private static void Main(string[] args) {
        var ints = new[] { 5, 7, 11, 13 };
        IEnumerable<Tuple<int>> result = ints.Aggregate<int, IEnumerable<Tuple<int>>>(new List<Tuple<int>>(),
                (sum, item) => sum.Concat(new[] { new Tuple<int> { A = sum.LastOrDefault().A + item, B = item } }));

        Console.WriteLine(string.Join(" ", result.Select(item => "(" + item.A + "," + item.B + ")").ToArray()));
    }
}

虽然无法弄清楚如何使用匿名类型而不是元组。 如果有:

,也可以简化
IEnumerable<T> Append(this IEnumerable<T> @this, T item);

答案 6 :(得分:0)

只是为了好玩,这里是F#,使用内置的Seq.scan功能:

> let input = [1;2;3];;

val input : int list

> input |> Seq.scan (fun (_, acc) x -> (x, acc + x)) (0,0) |> Seq.skip 1;;

val it : seq<int * int> = seq [(1, 1); (2, 3); (3, 6)]