2015-11-09 19 views
7

Diciamo che ho uno List<IEnumerable<double>> numero variabile di fonti infinite di numeri doppi. Diciamo che sono tutte le funzioni del generatore di onde e ho bisogno di sovrapporle in un generatore di onde singole rappresentato da IEnumerable<double> semplicemente prendendo il numero successivo di ciascuna e le citando.LINQ unisce Elenco <IEnumerable <T>> in un IEnumerable <T> con qualche regola

So che posso fare questo attraverso metodi iteratore, qualcosa di simile:

public IEnumerable<double> Generator(List<IEnumerable<double>> wfuncs) 
    { 
     var funcs = from wfunc in wfuncs 
        select wfunc.GetEnumerator(); 

     while(true) 
     { 
      yield return funcs.Sum(s => s.Current); 
      foreach (var i in funcs) i.MoveNext(); 
     } 
    } 

tuttavia, sembra piuttosto "pedonale". Esiste un modo LINQ per raggiungere questo obiettivo?

+3

'SelectMany ', forse? –

+0

Ma come? Ho provato a coprirmi la testa, senza fortuna. Ciò di cui ho bisogno è una strana variazione di Zip() con un numero variabile di sequenze. – mmix

+2

solo chiedendo. perché lo vuoi in LINQ? la tua implementazione è leggibile e molto carina. –

risposta

4

Immagino che non ci sia modo di aggirare questo senza estendere LINQ. Quindi ecco quello che ho scritto alla fine. Cercherò di contattare gli autori MoreLinq per ottenere questo incluso in qualche modo, può essere utile in alcuni scenari di rotazione:

public static class EvenMoreLinq 
{ 
    /// <summary> 
    /// Combines mulitiple sequences of elements into a single sequence, 
    /// by first pivoting all n-th elements across sequences 
    /// into a new sequence then applying resultSelector to collapse it 
    /// into a single value and then collecting all those 
    /// results into a final sequence. 
    /// NOTE: The length of the resulting sequence is the length of the 
    ///  shortest source sequence. 
    /// Example (with sum result selector): 
    /// S1 S2 S2 | ResultSeq 
    /// 1 2 3 |   6 
    /// 5 6 7 |   18 
    /// 10 20 30 |   60 
    /// 6 - 7 |   - 
    /// -   - |   
    /// </summary> 
    /// <typeparam name="TSource">Source type</typeparam> 
    /// <typeparam name="TResult">Result type</typeparam> 
    /// <param name="source">A sequence of sequences to be multi-ziped</param> 
    /// <param name="resultSelector">function to compress a projected n-th column across sequences into a single result value</param> 
    /// <returns>A sequence of results returned by resultSelector</returns> 
    public static IEnumerable<TResult> MultiZip<TSource, TResult> 
            this IEnumerable<IEnumerable<TSource>> source, 
            Func<IEnumerable<TSource>, TResult> resultSelector) 
    { 
     if (source == null) throw new ArgumentNullException("source"); 
     if (source.Any(s => s == null)) throw new ArgumentNullException("source", "One or more source elements are null"); 
     if (resultSelector == null) throw new ArgumentNullException("resultSelector"); 

     var iterators = source.Select(s => s.GetEnumerator()).ToArray(); 
     try 
     { 
      while (iterators.All(e => e.MoveNext())) 
       yield return resultSelector(iterators.Select(e => e.Current)); 
     } 
     finally 
     { 
      foreach (var i in iterators) i.Dispose(); 
     } 
    } 
} 

utilizzare questo sono riuscito a comprimere il mio generatore combinato:

interface IWaveGenerator 
{ 
    IEnumerable<double> Generator(double timeSlice, double normalizationFactor = 1.0d); 
} 


[Export(typeof(IWaveGenerator))] 
class CombinedWaveGenerator : IWaveGenerator 
{ 
    private List<IWaveGenerator> constituentWaves; 

    public IEnumerable<double> Generator(double timeSlice, double normalizationFactor = 1) 
    { 
     return constituentWaves.Select(wg => wg.Generator(timeSlice)) 
           .MultiZip(t => t.Sum() * normalizationFactor); 
    } 
    // ... 
} 
+1

Assicurarsi di acquisire il risultato di 'iteratori' in un elenco o array in memoria. Altrimenti, la chiamata a 'MoveNext()' funzionerà su un oggetto enumeratore diverso rispetto alla chiamata a '.Current': stai rivalutando' .Select() 'ogni volta che esegui iterazioni su di essi. – StriplingWarrior

+0

Vero, trascurato quello. Grazie. – mmix

+2

'iterators' non può mai essere' null', quindi non è necessario controllarlo con null. – Servy

8

È possibile aggregare il metodo Zip su IEnumerables.

public IEnumerable<double> Generator(List<IEnumerable<double>> wfuncs) 
    { 
     return wfuncs.Aggregate((func, next) => func.Zip(next, (d, dnext) => d + dnext)); 
    } 

Ciò che fa è applicare in modo basico lo stesso metodo Zip più e più volte. Con quattro IEnumerables questo potrebbe espandersi a:

wfuncs[0].Zip(wfuncs[1], (d, dnext) => d + dnext) 
     .Zip(wfuncs[2], (d, dnext) => d + dnext) 
     .Zip(wfuncs[3], (d, dnext) => d + dnext); 

Prova ora: fiddle

+0

sembra abbastanza strano per me. – Hristo

+0

+1 per l'idea costruttiva. Tuttavia, 'Zip' si interrompe quando ** qualsiasi ** degli enumerabili coinvolti termina, mentre quello che OP vuole è mantenere il rendimento fino a ** tutto ** fine. –

+0

@IvanStoev: OP dice che i 'IEnumerable <>' sono "infinito", che io intendo dire che nessuno di essi smetterà mai di produrre valori. – StriplingWarrior

3

questa è una situazione in cui LINQ sarebbe probabilmente più difficile da capire, e non comprare nulla. La soluzione migliore è semplicemente sistemare il metodo di campionamento. Qualcosa del genere dovrebbe funzionare:

public IEnumerable<double> Generator(IReadOnlyCollection<IEnumerable<double>> wfuncs) 
{ 
    var enumerators = wfuncs.Select(wfunc => wfunc.GetEnumerator()) 
     .ToList(); 

    while(enumerators.All(e => e.MoveNext())) 
    { 
     yield return enumerators.Sum(s => s.Current); 
    } 
} 
+0

Penso che sia semplice e facile da leggere. Sine hai cambiato il tipo del parametro di input del metodo, perché non hai semplicemente usato 'IEnumerable >'? Temi che "IEnumerable <>" "esterno" sia infinito? –

+0

@JeppeStigNielsen: Principalmente, mi piace usare 'IReadOnlyCollection <>' s. Trovo che riducano la probabilità che il codice chiamante abbia problemi di enumerazione multipla. Ma 'IEnumerable >' sarebbe perfettamente valido, specialmente se si sta cercando di renderlo più generale, come mmix ha fatto nella sua risposta. – StriplingWarrior

Problemi correlati