2014-09-03 3 views
5
IEnumerable<int> list = new[] { 1, 2, 7, 3, 11, 5 }; 
int item = (from x in list where (x == list.Max()) select x).First(); 
IEnumerable<int> above = from x in list where list.ToList().IndexOf(item) > list.ToList().IndexOf(x) select x; 
IEnumerable<int> below = from x in list where list.ToList().IndexOf(item) < list.ToList().IndexOf(x) select x; 

Ich möchte ein Element in einem IEnumerable und spaltete die IEnumerable in IEnumerable s finden, die nicht mehr das Element enthält, die ich gefunden . Der obige Code veranschaulicht das Ergebnis, das ich erreichen möchte, jedoch musste ich IEnumerable in eine Liste konvertieren.Split IEnumerable in drei Teilen: „oben“, „Artikel“, „unten“ mit Effizienz

Ich denke, es muss eine Möglichkeit geben, das nur mit LinQ und IEnumerable zu tun. Wie kann das gemacht werden?

+2

Als Erstes können Sie einfach 'int item = list.Max()' verwenden. – Vache

+3

Zweitens klingt es so, als ob Sie ['Außer '] (http://msdn.microsoft.com/en-us/library/vstudio/bb300779%28v=vs.100%29.aspx) verwenden möchten. – Default

+0

@Default ich lese auf außer - es scheint wie es ein Element aus einer Liste entfernt - ich kann nicht sehen, wie ich das Ergebnis in zwei IEnumerable am Splitpoint teilen würde. – Johannes

Antwort

14

So haben wir hier einige Teilprobleme. Das erste Problem besteht darin, das Objekt in einer Sammlung mit dem höchsten Wert eine Projektion dieses Elements zurückzugeben. Max vergleicht nur das Objekt selbst oder gibt bei einer Projektion das Ergebnis dieser Projektion zurück.

public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source 
    , Func<TSource, TKey> selector 
    , IComparer<TKey> comparer = null) 
{ 
    if (comparer == null) 
    { 
     comparer = Comparer<TKey>.Default; 
    } 
    using (IEnumerator<TSource> iterator = source.GetEnumerator()) 
    { 
     if (!iterator.MoveNext()) 
     { 
      throw new ArgumentException("Source was empty"); 
     } 

     TSource maxItem = iterator.Current; 
     TKey maxValue = selector(maxItem); 

     while (iterator.MoveNext()) 
     { 
      TKey nextValue = selector(iterator.Current); 
      if (comparer.Compare(nextValue, maxValue) > 0) 
      { 
       maxValue = nextValue; 
       maxItem = iterator.Current; 
      } 
     } 
     return maxItem; 
    } 
} 

So können wir viel effizienter den Index des Elements mit dem größten Wert erhalten:

var splitPoint = list.Select((index, number) => new { index, number }) 
    .MaxBy(pair => pair.number) 
    .index; 

Weiter, um die Sammlung teilen Sie einfach überspringen können/nehmen:

var firstHalf = list.Take(index); 
var secondHalf = list.Skip(index + 1); 

Es gibt eine Reihe von verschiedenen Problemen mit dem Code, den Sie haben, die hier gelöst werden.

Sie berechnen den Wert Maxfür jeden einzelnen Artikel in Ihrer Abfrage item zu bekommen, anstatt sie einmal Berechnung und dass die berechneten Wert verwenden.

Sie gehen dann, für jedes Element in der Liste, und kopieren Sie alle Elemente in eine neue Liste, zweimal, durchsuchen Sie diese Liste, um zu versuchen, die Position des maximalen Elements zu finden, und dann versuchen, zu finden die Position des aktuellen Artikels. Sie tun dann die ganze Sache zweimal. Das bedeutet, dass Sie das gesamte Array viermal für jedes Element in eine Liste kopieren, viermal pro Element in der Auflistung nach der Position des maximalen Elements suchen und die Liste linear durchsuchen, um den Index des aktuellen Elements zu finden (etwas Sie könnten in kürzester Zeit rechnen, indem Sie für jedes Element zweimal zählen. Dies wird ... schlecht skalieren, da die Anzahl der Gegenstände zunimmt.

Der Code hier findet den Index des Max-Objekts in einem einzigen Durchgang der Sammlung und erstellt dann Sequenzen, die jede Hälfte darstellen, die über die einfache Iteration hinausgehen.

+2

+1 für 'Take()' und 'Skip()'. Obwohl das Erstellen einer Erweiterung übertrieben erscheinen mag. Ich würde stattdessen Filipes Antwort verwenden und 'Take()' und 'Skip()' verwenden. – rcdmk

+0

@rcdmk Dies erhöht die Komplexität im Austausch für eine nützliche allgemeine Methode mit breiten Anwendungen, und beseitigt auch die Notwendigkeit, die gesamte Sammlung in eine Liste zu materialisieren und dann diese Liste zu iterieren, nur um das Element zu finden, das Sie bereits früher wieder gefunden hatten . Wenn die Sammlung klein genug ist, dass das kein Problem ist, dann ist das in Ordnung, aber der ganze Sinn dieser Frage besteht darin, die Abfrage zu optimieren, um unnötige Operationen wie diese zu vermeiden. – Servy

+0

@Servy Guter Punkt. Du hast es gewonnen. Denken Sie an einen großen Datensatz und optimierte Art und Weise. – rcdmk

4

Verwenden Sie die Erweiterungsmethoden, in denen Sie den Index verwenden können. Schau dir das Beispiel unten mit den Kommentaren an.

// define the list 
IEnumerable<int> list = new[] { 1, 2, 7, 3, 11, 5 }; 

// define some value (max in your sample) 
int value = list.Max(); 

// get the index of the value you want 
int indexValue = list.ToList().IndexOf(value); 

// find collections 
IEnumerable<int> above = list.Where((value, index) => index < indexValue); 
IEnumerable<int> below = list.Where((value, index) => index > indexValue); 
+0

Ist das nicht das, was das OP schon macht? – Mrchief

+0

@Mrchief: Nein. Die OP suchte den Index für * jeden einzelnen Artikel * auf. Dies liefert den Index als Argument und ist effizienter. –

+0

Das wird sowieso vom Compiler inline, so dass Sie nicht jedes Mal die Look-Up-Kosten übernehmen. – Mrchief

3

Zuerst müssen Sie den (ersten) Index des maximalen Elements finden. Als eine Variation von Servys Antwort kann dies unter Verwendung von Select und Aggregate durchgeführt werden. Dann, bevor die Elemente aufzählen über und nach diesem Index:

 var indexOfMax = list 
      .Select((value, index) => new KeyValuePair<int, int>(index, value)) 
      .Aggregate(new KeyValuePair<int, int>(-1, -1), (min, cur) => 
       { 
        if (min.Key == -1 || cur.Value > min.Value) 
         return cur; 
        return min; 
       }).Key; 

     var beginning = list.Take(indexOfMax); 
     var end = list.Skip(indexOfMax + 1); 
+2

Anstatt "int" zu verwenden und "-1" als Pseudo-Null-Wert zu verwenden, können Sie einfach "int?" Verwenden und tatsächlich "null" verwenden, um einen Wert für eine ganze Zahl darzustellen. 'KeyValuePair' ist auch speziell für Objekte, die eine Zuordnung von einem Wert zu einem anderen darstellen. Für ein Paar im Allgemeinen, das nicht die Schlüssel/Wert-Beziehung hat, sollten Sie stattdessen "Tuple" verwenden. – Servy

+1

Beachten Sie auch, dass, während dies weniger Code als das Erstellen einer allgemeinen MaxBy-Methode verwendet, wie in meiner Antwort getan, ich finde, dass die Methode nützlich genug ist und Anwendungen davon sind genug genug, dass es sich lohnt, diese Logik in umzuformen eine eigene Methode, anstatt die Implementierung der Logik jedes Mal zu wiederholen, wenn Sie das Problem haben. Ihre Lösung sorgt für weniger Code in einer Antwort, kann aber im Laufe einer größeren Codebasis leicht mehr Code und weniger wartbaren Code ergeben. – Servy

+0

@Servy - Ich habe KeyValuePair aus zwei Gründen verwendet. Erstens, für extrem kleine Tupel bevorzuge ich die Verwendung von Strukturen zu Klassen aus Leistungsgründen. Zweitens repräsentiert KeyValuePair in diesem Fall wirklich ein Mapping, da der Key der Listenindex ist. -1 als Signifikant für einen ungültigen oder nicht initialisierten Index ist ziemlich konventionell. Wie für das Erstellen Ihrer eigenen 'MaxBy()' Methode, tun Sie es definitiv, wenn Sie den Code wiederverwenden werden; Code-Wiederverwendung ist immer gut. – dbc

0

Zuerst müssen Sie garantieren, dass Ihre IEnumerable<T> Objektpositionen in irgendeiner bestimmten Reihenfolge enthalten. Dies kann entweder mit IOrderedEnumerable<T> anstelle von unformatierten IEnumerable<T>, oder mit IList<T> gehen, die Elemente per Index referenzieren können.

Wenn Sie das herausgefunden haben, wird das Teilen ziemlich trivial: Iterieren Sie die Elemente nach Index oder nach Reihenfolge und fügen Sie sie zu IList<T> above hinzu, bis Sie Ihr Element finden. Überspringe dieses Element und fahre fort, die Elemente zu durchlaufen, während du sie zu IList<T> below hinzufügst.

Verwandte Themen