2009-07-26 15 views
5

estoy tratando de llegar a una consulta LINQ para convertir un IEnumerable<int> a otro IEnumerable<int>, donde cada int en el resultado es la suma de todos los enteros hasta que la posición de la lista inicial:¿Cómo calcular una suma continua de una serie de entradas en una consulta de Linq?

Dada int[] a
necesito int[] b
Dónde b[0] = a[0], b[1] = a[0] + a[1], b[2] = a[0] + a[1] + a[2] y así sucesivamente

Alternativamente, las sumas anteriormente se pueden escribir como b[1] = b[0] + a[1], b[2] = b[1] + a[2] y así sucesivamente, pero no veo cómo eso ayuda.

que puede, por supuesto, hacer esto con un bucle for, pero obtener la secuencia a [] de una consulta y pensé que sería mejor si sigo esa consulta en lugar de repente la adición de un for allí :)

Respuesta

13

Bueno, puede hacerlo con efectos secundarios con bastante facilidad, aunque es bastante repulsivo ...

int sum = 0; 
int[] b = a.Select(x => (sum += x)).ToArray(); 

sería bueno si el marco proporciona una especie de "agregación acumulativa" para encapsular esto, pero por lo que yo sé, no lo sabe

+0

Usted es absolutamente fantástico :) Sí, es un poco desordenada, pero es lo suficientemente bueno, y yo estoy en una búsqueda para quitar la declaraciones de los últimos tiempos. –

8

Escribí una función para hacer esto hace un tiempo. Es similar a la función scanl de Haskell.

public static IEnumerable<TResult> Scan<T, TResult>(
    this IEnumerable<T> source, 
    Func<T, T, TResult> combine) 
{ 
    using (IEnumerator<T> data = source.GetEnumerator()) 
     if (data.MoveNext()) 
     { 
      T first = data.Current; 

      yield return first; 

      while (data.MoveNext()) 
      { 
       first = combine(first, data.Current); 
       yield return first; 
      } 
     } 
} 

int[] b = a 
    .Scan((running, current) => running + current) 
    .ToArray(); 
+1

Tiene un problema en 'yield return first', * first * if of type T, not TResult –

5

Una alternativa a la solución del Sr. Skeet: Si dejamos caer el requisito de una consulta LINQ y más literalmente dirigimos a "convertir un IEnumerable<int> a otro IEnumerable<int>" podemos utilizar esto:

static IEnumerable<int> Sum(IEnumerable<int> a) 
    { 
     int sum = 0; 
     foreach (int i in a) 
     { 
      sum += i; 
      yield return sum; 
     } 
    } 

que puede aplicarse a una serie infinita:

foreach (int i in Sum(MyMath.NaturalNumbers)) 
     Console.WriteLine(i); 

Esto también es útil si no desea crear toda la matriz a la vez.

+0

Sí, utilicé la sintaxis de matriz porque era más fácil explicar los requisitos, pero el argumento es realmente un IEnumerable . Sin embargo, el Sr. Skeet todavía trabaja en ese caso, simplemente sin la llamada .ToArray(), y es más compacto, así que todavía estoy votando por eso :) –

+0

Quise decir "solución del Sr. Skeet", por supuesto: P –

0

La respuesta anterior no funciona ... y no comparte la misma firma que el scanl de Haskell .... básicamente es una extensión de la idea del "agregado" de linq ... Creo que esto coincide con el implementación Haskell mejor

public static IEnumerable<TResult> Scanl<T, TResult>(
     this IEnumerable<T> source, 
     TResult first, 
     Func<TResult, T, TResult> combine) 
    { 
     using (IEnumerator<T> data = source.GetEnumerator()) 
     { 
      yield return first; 

      while (data.MoveNext()) 
      { 
       first = combine(first, data.Current); 
       yield return first; 
      } 
     } 
    } 

uso

[TestMethod] 
    public void Scanl_Test() 
    { 
     var xs = new int[] { 1, 2, 3, 4, 5, 6, 7 }; 

     var lazyYs = xs.Scanl(0, (y, x) => y + x); 

     var ys = lazyYs.ToArray(); 

     Assert.AreEqual(ys[0], 0); 
     Assert.AreEqual(ys[1], 1); 
     Assert.AreEqual(ys[2], 3); 
     Assert.AreEqual(ys[3], 6); 
     Assert.AreEqual(ys[4], 10); 
     Assert.AreEqual(ys[5], 15); 
     Assert.AreEqual(ys[6], 21); 
     Assert.AreEqual(ys[7], 28); 
    } 
Cuestiones relacionadas