2016-06-30 14 views
0

Ich möchte collection auf Artikel partitionieren, die bestimmte Bedingung erfüllt. Ich kann das mit TakeWhile und SkipWhile, die recht einfach zu verstehen:zu partitionieren Sammlung

public static bool IsNotSeparator(int value) => value != 3; 

var collection = new [] { 1, 2, 3, 4, 5 }; 
var part1 = collection.TakeWhile(IsNotSeparator); 
var part2 = collection.SkipWhile(IsNotSeparator); 

Aber dies von Anfang collection zweimal durchlaufen würde und wenn IsNotSeparator dauert lange könnte es Performance-Problem sein.

schnellen Weg wäre, etwas zu verwenden, wie:

var part1 = new List<int>(); 
var index = 0; 
for (var max = collection.Length; index < max; ++index) { 
    if (IsNotSeparator(collection[i])) 
     part1.Add(collection[i]); 
    else 
     break; 
} 
var part2 = collection.Skip(index); 

Aber das ist wirklich weniger besser lesbar als erstes Beispiel.

Also meine Frage ist: Was wäre die beste Lösung für die Partition collection auf bestimmtes Element?

Was ich aber diese beiden oben zu kombinieren ist:

var collection = new [] { 1, 2, 3, 4, 5 }; 
var part1 = collection.TakeWhile(IsNotSeparator).ToList(); 
var part2 = collection.Skip(part1.Count); 
+1

Dies ist eine ziemlich eigensinnige Frage. Was ist eine "beste Lösung"? Lesbarkeit? Größe des Codes? Geschwindigkeit? Speichernutzung? – Groo

+0

@Groo Ich würde für die Lesbarkeit gehen. Aber wenn 'IsNotSeparator' lange dauert, kann es ein Leistungsproblem sein. –

+0

Ist die Liste sortiert? Das wäre der größte Leistungsunterschied. Wenn Sie es verallgemeinern, funktioniert es wie 'String.Aufteilen ', dann könnten Sie eine Erweiterungsmethode machen, die etwas wie 'IEnumerable > Split (diese IEnumerable Liste, Prädikat Übereinstimmung)' würde, die so viele Brocken zurückgeben würde, die mit dem angegebenen Element getrennt sind. – Groo

Antwort

0

Dies ist ein kurzes Beispiel, wie Sie die allgemeinere Methode (mehrere Splits, wie sie in den Kommentaren erwähnt) tun würde, ohne LINQ (es ist möglich, es zu LINQ konvertieren, aber ich bin nicht sicher, ob es nicht mehr lesbar sein, und ich bin in einer leichten Eile jetzt):

public static IEnumerable<IEnumerable<T>> Split<T>(this IList<T> list, Predicate<T> match) 
{ 
    if (list.Count == 0) 
     yield break; 

    var chunkStart = 0; 
    for (int i = 1; i < list.Count; i++) 
    { 
     if (match(list[i])) 
     { 
      yield return new ListSegment<T>(list, chunkStart, i - 1); 
      chunkStart = i; 
     } 
    } 

    yield return new ListSegment<T>(list, chunkStart, list.Count - 1); 
} 

Der Code eine Klasse ListSegment<T> : IEnumerable<T> Namen setzt die einfach iteriert aus from bis to über die ursprüngliche Liste (kein Kopieren, ähnlich wie ArraySegment<T> funktioniert (ist aber leider auf Arrays beschränkt).

So wird der Code so viele Stücke zurückgeben, da es Übereinstimmungen, dh dieser Code:

var collection = new[] { "A", "B", "-", "C", "D", "-", "E" }; 
foreach (var chunk in collection.Split(i => i == "-")) 
    Console.WriteLine(string.Join(", ", chunk)); 

würde drucken:

A, B 
-, C, D 
-, E 
0

Wie über die Array Copy Methoden:

var separator = 3; 
var collection = new [] { 1, 2, 3, 4, 5 }; 

var i = Array.IndexOf(collection,separator); 

int[] part1 = new int[i]; 
int[] part2 = new int[collection.Length - i]; 
Array.Copy(collection, 0, part1, 0, i); 
Array.Copy(collection, i, part2, 0, collection.Length - i); 

Alternativ effizientere Nutzung Array zu sein:

var i = Array.IndexOf(collection,separator); 
var part1 = new ArraySegment<int>(collection, 0, i); 
var part2 = new ArraySegment<int>(collection, i, collection.Length - i); 

ArraySegment ist ein Wrapper um ein Array, der einen Bereich von Elementen in diesem Array begrenzt. Mehrere ArraySegment-Instanzen können sich auf dasselbe ursprüngliche Array beziehen und können sich überlappen.

Bearbeiten - Fügen Sie eine Kombination der ursprünglichen Frage mit ArraySegment hinzu, um die Sammlung nicht zweimal zu iterieren.

public static bool IsNotSeparator(int value) => value != 3; 
var collection = new [] { 1, 2, 3, 4, 5 }; 

var index = collection.TakeWhile(IsNotSeparator).Count(); 

var part1 = new ArraySegment<int>(collection, 0, index); 
var part2 = new ArraySegment<int>(collection, index, collection.Length - index); 
+0

Es wäre einfach, wenn ich den Separatorindex im Voraus kennen würde. Ich weiß nicht, welchen Index ich im Voraus aufteilen muss, und es wird von der 'IsNotSeparator' Methode bestimmt. –

+0

@DovydasSopa sicherlich können Sie Ihren IsNotSeparator als GetSeparator-Methode neu schreiben, die das Trennzeichen zurückgibt und dann können Sie die Array.IndexOf-Methode gemäß meiner Antwort verwenden –

Verwandte Themen