Как получить список промежуточных сумм функциональным способом? Используя LINQ? - PullRequest
6 голосов
/ 22 апреля 2009

Учитывая список объектов, мне нужно вернуть список, состоящий из объектов и суммы свойств объектов для всех объектов в списке, которые мы видели до сих пор.

В более общем смысле

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 ]

6 голосов
/ 22 апреля 2009

в функциональном отношении это комбинация:

молния

взять две последовательности и создать последовательность кортежей из элементов

и

карта

Возьмите функцию f и последовательность и верните новую последовательность, которая является f (x) для каждого x в исходной последовательности

Почтовый индекс тривиален в 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); 
    } 
}

Тогда нам нужна карта. У нас это уже есть, это то, что мы называем Select в 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 #, это плохая поддержка операторов, карри и кортежей, если вы не храните все внутри одной функции и не «воссоздаете ее» каждый раз для каждого типа.

6 голосов
/ 22 апреля 2009

Я думаю, что это самый короткий подход:

int sum = 0;
var result = input.Select(i => new { i, S = sum += i });
2 голосов
/ 22 апреля 2009
    int runningTotal=0;
    var p = input.Select((l)=>
        {
            runningTotal+=l;
            return new {l,Total=runningTotal};
        });

Редактировать

Foreach всегда будет в порядке. Откройте отражатель и посмотрите на ForEach в списке (ForEach не существует в массиве), но все, что делает ForEach - это цикл for для элементов.

Я не уверен насчет выбора, насколько я знаю, но я никогда не копался в этом.

2 голосов
/ 22 апреля 2009
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;
   });
1 голос
/ 22 апреля 2009
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);

}
);
0 голосов
/ 22 апреля 2009

Просто для удовольствия, здесь это в 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)]
0 голосов
/ 22 апреля 2009

Функциональная реализация (без побочных эффектов) и без расширений:

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()));
    }
}

Хотя не могу понять, как сделать это с анонимным типом вместо Tuple. Также может быть упрощено, если будет:

IEnumerable<T> Append(this IEnumerable<T> @this, T item);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...