2009-07-26 15 views
5

Sto cercando di trovare una query LINQ per convertire un IEnumerable<int> ad un'altra IEnumerable<int>, dove ogni int nel risultato è la somma di tutti gli interi fino a quella posizione dalla lista iniziale:Come calcolare una somma parziale di una serie di valori in una query Linq?

Dato int[] a
ho bisogno int[] b
Dove b[0] = a[0], b[1] = a[0] + a[1], b[2] = a[0] + a[1] + a[2] e così via

in alternativa, le somme di cui sopra possono essere scritte come b[1] = b[0] + a[1], b[2] = b[1] + a[2] e così via, ma non vedo come sarebbe d'aiuto.

posso, ovviamente, farlo con un ciclo for, ma ottenere la sequenza a [] da una query e ho pensato che sarebbe aspetto molto più gradevole se continuo che query invece di improvvisamente l'aggiunta di un for lì :)

risposta

13

Beh, si può fare con gli effetti collaterali abbastanza facilmente, anche se è piuttosto icky ...

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

sarebbe bello se il quadro fornito una sorta di "aggregato in esecuzione" per incapsulare questo, ma è non per quanto ne so.

+0

sei assolutamente fantastico :) Sì, è un po 'icky, ma è abbastanza buono, e io sono in una missione per eliminare il per le dichiarazioni ultimamente. –

8

Ho scritto una funzione per farlo un po 'di tempo fa. È simile alla funzione scanl di 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

Si è verificato un problema con 'yield return first', * first * se di tipo T, non TResult –

5

Un'alternativa alla soluzione del signor Skeet: Se lasciamo cadere il requisito di una query LINQ e più letteralmente rivolgiamo "convertire un IEnumerable<int> ad un altro IEnumerable<int>" possiamo usare questo:

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

che noi può essere applicato a una serie infinita:

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

Questo è anche utile se non si desidera creare l'intero array in una sola volta.

+0

Sì, ho usato la sintassi dell'array perché era più semplice spiegare i requisiti, ma l'argomento è effettivamente IEnumerable . Tuttavia, il signor Skeet funziona ancora in quel caso, solo senza la chiamata .ToArray(), ed è più compatto, quindi sto ancora votando per quello :) –

+0

Intendevo "la soluzione del signor Skeet", naturalmente: P –

0

La risposta sopra non funziona ... e non condivide la stessa firma di Scanl di Haskell .... fondamentalmente è un'estensione all'idea di "aggregato" di linq ... Penso che questo corrisponda al implementazione Haskell meglio

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

utilizzo

[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); 
    } 
Problemi correlati