2016-11-25 2 views
2

ich eine IEnumerable<int> wie diese haben, nur länger:IEnumerable: alle vor dem letzten erhalten, die ein Prädikat übereinstimmt

5, 0, 0, 0, 0, 4, 0, 0, 0, 2, 0, 6, 0, 0, 0, 0, 0 

und jetzt will ich vor dem letzten Wert alle Elemente zurück Nicht-Null:

5, 0, 0, 0, 0, 4, 0, 0, 0, 2, 0 

Es scheint sequence.Last() hilft hier nicht, weil es das letzte Vorkommen, nicht den Index des letzten Vorkommens zurückgibt.

Ich hatte gedacht,

var lastIndex = sequence.LastIndex(x=>x!=0); 
subsequence = sequence.Take(lastIndex); 

zu verwenden, die im allgemeinen Fall funktionieren würde, aber LastIndex nicht existiert, oder

var last = sequence.Last(y=>y!=0); 
subsequence = sequence.TakeWhile(x=>x!=last) 

die am Beispiel funktionieren würde, aber nicht in der allgemeiner Fall, in dem doppelte Nicht-Null-Werte vorkommen können.

Irgendwelche Ideen?

Antwort

5

können Sie versuchen, diese

var allDataBeforeLastNonZero= sequence.GetRange(0,sequence.FindLastIndex(x=>x!=0)); 
+2

Dies ist bei weitem die hässlichste Lösung, nimmt aber 'sequence' an sei eine 'Liste '. Anyway +1. – HimBromBeere

+0

Es ist ein 'IEnumerable ' also müsste es zuerst in eine Liste umgewandelt werden. –

+4

Da wirst du _have_ laufen müssen, um ganz IEnumerable zu gehen - kein Schaden, es in Liste umzuwandeln Erstens: – Evk

4

Vielleicht gibt es effizientere Ansätze, aber diese ist lesbar, oder?

var allBeforeLastNonZero = sequence 
    .Reverse()    // look from the end 
    .SkipWhile(i => i == 0) // skip the zeros 
    .Skip(1)     // skip last non-zero 
    .Reverse();    // get original order 
+1

Wenn die gegebenen 'IEnumerable ' ist keine 'ICollection ' und die Sequenz ist ziemlich lang, es wird viel Zeit und Speicher benötigen, um den Aufruf ".Reverse()" auszuführen. – Oliver

+0

Reverse() kehrt die Auflistung nicht um. Es erstellt einen umgekehrten Iterator, der vom Ende der Sammlung ausgeht. – Dmitry

+0

@Dmitry: stimmt, aber Oliver hat recht, wenn die Eingabefolge keine Sammlung ist, wird der ['Puffer 'verwendet] (https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs, ed118118b642d9d4, references) erstellt ein neues, um es [rückwärts zu durchlaufen] (https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,e247ee4147678571,references). Wenn also die Eingabesequenz eine Sammlung ist, muss das erste 'Reverse' kein Temp-Array erstellen, aber das zweite' Reverse' benötigt es. –

1

Sie könnten Ihre Liste in einen String konvertieren und String.Trim verwenden:

var str = String.Join(",", myInputArray); 
var result = str.TrimEnd(',', '0').Split(',').Select(x => Convert.ToInt32(x)).ToList(); 
result.RemoveAt(result.Count - 1); 

haben scheint zugeben, ein bisschen hässlich, aber es sollte funktionieren.

+0

Jetzt ist es noch hässlicher, aber Sie haben mein upvote für dieses kreative Ich dachte nicht an – fubo

+0

@fubo Was war Ihr Vorschlag zum Entfernen des letzten Elements? Nur aus Neugier. Ich habe keine Lösung gefunden, die auch nicht hässlich ist. – HimBromBeere

+0

Ich sehe keinen besseren Ansatz, da es eine spezielle Behandlung für das letzte benötigte Zeichen gibt :(Tims Ansatz erfordert auch das '.Skip (1)' für das – fubo

5

im allgemeinen Fall funktionieren würde, aber last existiert nicht

Nein, aber Sie können es mit finden:

var lastIndex = sequence 
    .Select((x, i) => new {El = x, Idx = i}) 
    .Where(x => x.El != 0) 
    .Select(x => x.Idx).Last(); 

Wenn Sie mit IQueryable<T> arbeiten müssen, ist das ungefähr so ​​gut, wie Sie bekommen können.

Es hat ein paar Probleme. Zum einen scannt er die Sequenz zweimal durch, und wer sagt, dass die Sequenz das überhaupt zulässt. Wir können es besser machen, aber wir werden Puffer müssen, wenn auch nicht unbedingt die ganze Sache Puffer:

public static IEnumerable<T> BeforeLastMatch<T>(this IEnumerable<T> source, Func<T, bool> predicate) 
{ 
    if (source == null) throw new ArgumentNullException(nameof(source)); 
    if (predicate == null) throw new ArgumentNullException(nameof(predicate)); 
    return BeforeLastMatchImpl(source, predicate); 
} 

public static IEnumerable<T> BeforeLastMatchImpl<T>(IEnumerable<T> source, Func<T, bool> predicate) 
{ 
    var buffer = new List<T>(); 
    foreach(T item in source) 
    { 
    if (predicate(item) && buffer.Count != 0) 
    { 
     foreach(T allowed in buffer) 
     { 
      yield return allowed; 
     } 
     buffer.Clear(); 
    } 
    buffer.Add(item); 
    } 
} 

Anruf sequence.BeforeLastMatch(x => x != 0) und Sie erhalten 5, 0, 0, 0, 0, 4, 0, 0, 0, 2, 0

Wenn Sie es wirklich brauchen sowohl mit IEnumerable und IQueryable zu arbeiten, kann auch gehandhabt werden, aber es ist ein bisschen komplizierter. Ärgere dich nicht, wenn du weißt, dass du nur in-memory IEnumerable hast.(Auch einige Anbieter haben unterschiedliche Unterstützung für verschiedene Funktionen, so dass Sie gezwungen sein könnten, die In-Memory-Version oben ohnehin zu tun):

private class ElementAndIndex<T> 
{ 
    public T Element { get; set; } 
    public int Index { get; set; } 
} 

public static IQueryable<T> BeforeLastMatch<T>(this IQueryable<T> source, Expression<Func<T, bool>> predicate) 
{ 
    if (source == null) throw new ArgumentNullException(nameof(source)); 
    if (predicate == null) throw new ArgumentNullException(nameof(predicate)); 
    // If source is actually an in-memory enumerable, the other method will be more efficient, 
    // so use it instead. 
    var asEnum = source as EnumerableQuery<T>; 
    if (asEnum != null && asEnum.Expression.NodeType == ExpressionType.Constant) 
    { 
    // On any other IQueryable calling `AsEnumerable()` will force it 
    // to be loaded into memory, but on an EnumerableQuery it just 
    // unwraps the wrapped enumerable this will chain back to the 
    // contained GetEnumerator. 
    return BeforeLastMatchImpl(source.AsEnumerable(), predicate.Compile()).AsQueryable(); 
    } 

    // We have a lambda from (T x) => bool, and we need one from 
    // (ElementAndIndex<T> x) => bool, so build it here. 

    var param = Expression.Parameter(typeof(ElementAndIndex<T>)); 
    var indexingPredicate = Expression.Lambda<Func<ElementAndIndex<T>, bool>>(
    Expression.Invoke(predicate, Expression.Property(param, "Element")), 
    param 
); 

    return source.Take(// We're going to Take based on the last index this finds. 
    source 
     // Elements and indices together 
     .Select((x, i) => new ElementAndIndex<T>{ Element = x, Index = i}) 
     // The new predicate we created from that passed to us. 
     .Where(indexingPredicate) 
     // The last matching element. 
     .Select(x => x.Index).Last()); 
} 
+0

Ja, das ist es der beste Weg, denke ich. – Evk

+0

Es wird einfacher, stattdessen eine Warteschlange für den Puffer zu verwenden. –

+0

@JeffMercado nicht wirklich. Wenn wir eine Warteschlange verwenden würden, könnten wir eine Menge 'Pop()' s machen, aber es wäre leistungsfähiger, 'foreach' und dann' Clear() 'zu machen, und wenn wir das mit einer Warteschlange machen, ist der einzige verbleibende Unterschied Das Wort 'Add' wird durch das Wort' Push' ersetzt. –

1
IEnumerable<int> source = new List<int> {5,0,0, 4,0,0,3, 0, 0}; 
List<int> result = new List<int>(); 
List<int> buffer = new List<int>(); 
foreach (var i in source) 
{ 
    buffer.Add(i); 
    if (i != 0) 
    { 
     result.AddRange(buffer); 
     buffer.Clear(); 
    } 
} 
Verwandte Themen