2010-04-29 14 views
32

Ich versuche eine LINQ-Anweisung zu schreiben, die mir alle möglichen Zahlenkombinationen zurückgibt (ich brauche das für einen Test und ich wurde von dieser article of Eric Lippert inspiriert). Die Prototyp-Methode nenne ich wie folgt aussieht:Generiere Zahlenfolgen mit LINQ

IEnumerable<Collection<int>> AllSequences(int start, int end, int size); 

Die Regeln sind:

  • alle zurück Sammlungen eine Länge von size
  • Zahlenwerte innerhalb einer Sammlung
  • haben zu erhöhen haben jede Zahl zwischen start und end sollten verwendet werden

ruft also die AllSequences(1, 5, 3) in 10 Sammlungen führen sollte, die jeweils eine Größe 3:

1 2 3 
1 2 4 
1 2 5 
1 3 4 
1 3 5 
1 4 5 
2 3 4 
2 3 5 
2 4 5 
3 4 5 

Nun, irgendwie habe ich wirklich eine reine LINQ Lösung sehen möchten. Ich bin in der Lage, eine nicht-LINQ-Lösung selbst zu schreiben, also bitte keine Anstrengung in eine Lösung ohne LINQ.
Meine versucht so an einem Punkt weit beendet, wo ich eine Zahl mit dem Ergebnis eines rekursiven Aufruf meiner Methode verbinden - so etwas wie:

return from i in Enumerable.Range(start, end - size + 1) 
     select BuildCollection(i, AllSequences(i, end, size -1)); 

Aber ich es nicht schaffen BuildCollection() auf einem implementieren LINQ-Basis - oder überspringen Sie diesen Methodenaufruf. Kannst du mir hier helfen?

+0

@Noldorin, @Fede: Danke für die tollen Antworten - ich auf jeden Fall einen genaueren Blick auf den Methoden des 'Enumerable' (wie' Repeat() 'oder' Concat() ') nehmen muß – tanascius

Antwort

32

Ich denke, ich habe es.

IEnumerable<List<int>> AllSequences(int start, int end, int size) 
{ 
    if (size == 0) 
     return Enumerable.Repeat<List<int>>(new List<int>(), 1); 

    return from i in Enumerable.Range(start, end - size - start + 2) 
      from seq in AllSequences(i + 1, end, size - 1) 
      select new List<int>{i}.Concat(seq).ToList(); 
} 
+0

'if (size == 0) return new int [] {} .ToList()'? – Spook

13

Etwas wie das folgende sollte die Arbeit tun, denke ich.

public static IEnumerable<IEnumerable<int>> AllSequences(int start, int end, 
    int size) 
{ 
    return size <= 0 ? new[] { new int[0] } : 
      from i in Enumerable.Range(start, end - size - start + 2) 
      from seq in AllSequences(i + 1, end, size - 1) 
      select Enumerable.Concat(new[] { i }, seq); 
} 

Der Schlüssel zur Lösung ist die compound from clause, die für den Umgang mit verschachtelten enumerables ganz praktisch ist.

Beachten Sie, dass ich die Methodensignatur leicht in IEnumerable<IEnumerable<int>> geändert habe, da dies bei Verwendung von (reinem) LINQ praktischer ist. Sie können es aber jederzeit problemlos in ein IEnumerable<ICollection<int>> konvertieren, wenn Sie möchten.

Lassen Sie mich wissen, wenn der Code eine Erklärung benötigt, aber ich hoffe, die LINQ-Syntax macht es einigermaßen klar.

Bearbeiten 1: Fehler behoben und verbesserte Prägnanz.

Edit 2: Weil mir langweilig und haben nichts Besseres zu tun (nein, nicht wirklich), ich dachte, dass ich eine Erweiterungsmethode schreiben würde, dass die Kombinationen aus einer vorgegebenen Liste von Elementen zu berechnen, verwenden Sie machen der AllSequences Methode.

public static IEnumerable<IEnumerable<T>> Combinations<T>(this IList<T> source, 
    int num) 
{ 
    return AllSequences(0, source.Count - 1, num).Select(
     seq => seq.Select(i => source[i])); 
} 

Vielleicht nicht die effizienteste Art der Berechnung von Kombinationen, aber sicherlich ziemlich kompakter Code!

+3

Du hast meinen Donner gestohlen! Ich habe gerade das ausgearbeitet = D +1 – Tejs

+1

Ich habe es gerade ausgeführt und es verursacht einen stackoverflow :-(, vielleicht mit einer where size> 0, kurz vor der zweiten von? –

+1

Es muss sein "from seq in AllSequences (i +1, end, size-1) ' Anders als das, schön! Ich habe auch nur einen geschrieben, aber dein ist viel prägnanter. +1 – tzaman

41
Enumerable.Range(1, 12) 
      .Select(x => (x - 1) + 1); 
+2

hat die Frage nicht beantwortet, aber war nützlich für mich, also +1 – Kell

+1

Was macht das? Ist das '.Select' nicht redundant? –

+0

@MateenUlhaq Ja, es scheint unnötig zu sein. –