Powiedzmy, że mam sekwencję.
IEnumerable<int> sequence = GetSequenceFromExpensiveSource();
// sequence now contains: 0,1,2,3,...,999999,1000000
Uzyskanie sekwencji nie jest tanie i jest generowane dynamicznie, a ja chcę ją powtórzyć tylko raz.
Chcę dostać 0 - 999999 (czyli wszystko oprócz ostatniego elementu)
Rozumiem, że mógłbym zrobić coś takiego:
sequence.Take(sequence.Count() - 1);
ale to skutkuje dwoma wyliczeniami w dużej sekwencji.
Czy istnieje konstrukcja LINQ, która pozwala mi to zrobić:
sequence.TakeAllButTheLastElement();
sequenceList.RemoveAt(sequence.Count - 1);
. W moim przypadku jest to dopuszczalne, ponieważ po wszystkich manipulacjach LINQ muszę przekonwertować go na tablicę lubIReadOnlyCollection
tak czy inaczej. Zastanawiam się, jaki jest twój przypadek użycia, w którym nawet nie rozważasz buforowania? Jak widzę, nawet zatwierdzona odpowiedź powoduje pewne buforowanie, więcList
moim zdaniem prosta konwersja do jest znacznie łatwiejsza i krótsza.Odpowiedzi:
Nie znam rozwiązania Linq - ale możesz łatwo samodzielnie zakodować algorytm za pomocą generatorów (zwrot zysku).
public static IEnumerable<T> TakeAllButLast<T>(this IEnumerable<T> source) { var it = source.GetEnumerator(); bool hasRemainingItems = false; bool isFirst = true; T item = default(T); do { hasRemainingItems = it.MoveNext(); if (hasRemainingItems) { if (!isFirst) yield return item; item = it.Current; isFirst = false; } } while (hasRemainingItems); } static void Main(string[] args) { var Seq = Enumerable.Range(1, 10); Console.WriteLine(string.Join(", ", Seq.Select(x => x.ToString()).ToArray())); Console.WriteLine(string.Join(", ", Seq.TakeAllButLast().Select(x => x.ToString()).ToArray())); }
Lub jako uogólnione rozwiązanie odrzucające ostatnie n pozycji (używając kolejki, jak sugerowano w komentarzach):
public static IEnumerable<T> SkipLastN<T>(this IEnumerable<T> source, int n) { var it = source.GetEnumerator(); bool hasRemainingItems = false; var cache = new Queue<T>(n + 1); do { if (hasRemainingItems = it.MoveNext()) { cache.Enqueue(it.Current); if (cache.Count > n) yield return cache.Dequeue(); } } while (hasRemainingItems); } static void Main(string[] args) { var Seq = Enumerable.Range(1, 4); Console.WriteLine(string.Join(", ", Seq.Select(x => x.ToString()).ToArray())); Console.WriteLine(string.Join(", ", Seq.SkipLastN(3).Select(x => x.ToString()).ToArray())); }
źródło
Jako alternatywa dla tworzenia własnej metody iw przypadku gdy kolejność elementów nie jest ważna, zadziała następna:
var result = sequence.Reverse().Skip(1);
źródło
equence.Reverse().Skip(1).Reverse()
to dobre rozwiązaniePonieważ nie jestem fanem jawnego używania an
Enumerator
, oto alternatywa. Należy zauważyć, że metody opakowujące są potrzebne, aby umożliwić wczesne wyrzucanie nieprawidłowych argumentów, zamiast odkładać sprawdzanie do momentu faktycznego wyliczenia sekwencji.public static IEnumerable<T> DropLast<T>(this IEnumerable<T> source) { if (source == null) throw new ArgumentNullException("source"); return InternalDropLast(source); } private static IEnumerable<T> InternalDropLast<T>(IEnumerable<T> source) { T buffer = default(T); bool buffered = false; foreach (T x in source) { if (buffered) yield return buffer; buffer = x; buffered = true; } }
Zgodnie z sugestią Erica Lipperta łatwo uogólnia się na n pozycji:
public static IEnumerable<T> DropLast<T>(this IEnumerable<T> source, int n) { if (source == null) throw new ArgumentNullException("source"); if (n < 0) throw new ArgumentOutOfRangeException("n", "Argument n should be non-negative."); return InternalDropLast(source, n); } private static IEnumerable<T> InternalDropLast<T>(IEnumerable<T> source, int n) { Queue<T> buffer = new Queue<T>(n + 1); foreach (T x in source) { buffer.Enqueue(x); if (buffer.Count == n + 1) yield return buffer.Dequeue(); } }
Gdzie teraz buforuję przed poddaniem zamiast po ustąpieniu, aby
n == 0
sprawa nie wymagała specjalnego traktowania.źródło
buffered=false
w klauzuli else przed przypisaniembuffer
. Stan i tak jest już sprawdzany, ale pozwoliłoby to uniknąć zbędnego ustawiania zabuffered
każdym razem przez pętlę.DropLast
. W przeciwnym razie walidacja ma miejsce tylko wtedy, gdy faktycznie wyliczysz sekwencję (przy pierwszym wywołaniuMoveNext
wynikuIEnumerator
). Debugowanie nie jest przyjemną rzeczą, gdy między wygenerowaniemIEnumerable
a wyliczeniem może być dowolna ilość kodu . Obecnie pisałbymInternalDropLast
jako wewnętrzna funkcja programuDropLast
, ale ta funkcjonalność nie istniała w C #, kiedy pisałem ten kod 9 lat temu.Enumerable.SkipLast(IEnumerable<TSource>, Int32)
Sposób dodano NET standardu 2.1. Robi dokładnie to, czego chcesz.IEnumerable<int> sequence = GetSequenceFromExpensiveSource(); var allExceptLast = sequence.SkipLast(1);
Z https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.skiplast
źródło
Nic w BCL (lub wierzę, że MoreLinq), ale możesz stworzyć własną metodę rozszerzenia.
public static IEnumerable<T> TakeAllButLast<T>(this IEnumerable<T> source) { using (var enumerator = source.GetEnumerator()) bool first = true; T prev; while(enumerator.MoveNext()) { if (!first) yield return prev; first = false; prev = enumerator.Current; } } }
źródło
if (!first)
i wyciągnąćfirst = false
z if.prev
w pierwszej iteracji i zapętlaj się na zawsze”.using
teraz oświadczenie.Byłoby pomocne, gdyby .NET Framework został dostarczony z taką metodą rozszerzenia.
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int count) { var enumerator = source.GetEnumerator(); var queue = new Queue<T>(count + 1); while (true) { if (!enumerator.MoveNext()) break; queue.Enqueue(enumerator.Current); if (queue.Count > count) yield return queue.Dequeue(); } }
źródło
Nieznaczne rozwinięcie eleganckiego rozwiązania Jorena:
public static IEnumerable<T> Shrink<T>(this IEnumerable<T> source, int left, int right) { int i = 0; var buffer = new Queue<T>(right + 1); foreach (T x in source) { if (i >= left) // Read past left many elements at the start { buffer.Enqueue(x); if (buffer.Count > right) // Build a buffer to drop right many elements at the end yield return buffer.Dequeue(); } else i++; } } public static IEnumerable<T> WithoutLast<T>(this IEnumerable<T> source, int n = 1) { return source.Shrink(0, n); } public static IEnumerable<T> WithoutFirst<T>(this IEnumerable<T> source, int n = 1) { return source.Shrink(n, 0); }
Gdzie shrink implementuje proste liczenie do przodu, aby porzucić pierwsze
left
wiele elementów i ten sam odrzucony bufor, aby usunąć ostatnieright
wiele elementów.źródło
jeśli nie masz czasu na wprowadzenie własnego rozszerzenia, oto szybszy sposób:
var next = sequence.First(); sequence.Skip(1) .Select(s => { var selected = next; next = s; return selected; });
źródło
Jeśli możesz uzyskać
Count
lubLength
z wyliczalnych, co w większości przypadków możesz, to po prostuTake(n - 1)
Przykład z tablicami
int[] arr = new int[] { 1, 2, 3, 4, 5 }; int[] sub = arr.Take(arr.Length - 1).ToArray();
Przykład z
IEnumerable<T>
IEnumerable<int> enu = Enumerable.Range(1, 100); IEnumerable<int> sub = enu.Take(enu.Count() - 1);
źródło
Niewielka wariacja na temat zaakceptowanej odpowiedzi, która (jak na mój gust) jest nieco prostsza:
public static IEnumerable<T> AllButLast<T>(this IEnumerable<T> enumerable, int n = 1) { // for efficiency, handle degenerate n == 0 case separately if (n == 0) { foreach (var item in enumerable) yield return item; yield break; } var queue = new Queue<T>(n); foreach (var item in enumerable) { if (queue.Count == n) yield return queue.Dequeue(); queue.Enqueue(item); } }
źródło
W C # 8.0 możesz użyć do tego zakresów i indeksów .
var allButLast = sequence[..^1];
Domyślnie C # 8.0 wymaga .NET Core 3.0 lub .NET Standard 2.1 (lub nowszego). Sprawdź ten wątek, aby używać go ze starszymi implementacjami.
źródło
Dlaczego nie po prostu
.ToList<type>()
na sekwencji, a potem liczenie połączeń i przyjmowanie, tak jak pierwotnie, ale ponieważ zostało wciągnięte do listy, nie powinno dwukrotnie wykonywać kosztownego wyliczenia. Dobrze?źródło
Rozwiązanie, którego używam do tego problemu, jest nieco bardziej rozbudowane.
Moja klasa statyczna util zawiera metodę rozszerzenia,
MarkEnd
która konwertujeT
-items wEndMarkedItem<T>
-items. Każdy element jest oznaczony dodatkowymint
, którym jest 0 ; lub (w przypadku, gdy ktoś jest szczególnie zainteresowany 3 ostatnimi pozycjami) -3 , -2 lub -1 dla ostatnich 3 pozycji.Może to być przydatne samo w sobie, np. Gdy chcesz utworzyć listę w prostej
foreach
pętli z przecinkami po każdym elemencie z wyjątkiem ostatnich 2, z przedostatnim elementem, po którym następuje łącznik (taki jak „ i ” lub „ lub ”) i ostatni element, po którym następuje punkt.Aby wygenerować całą listę bez ostatnich n elementów, metoda rozszerzenia
ButLast
po prostu wykonuje iterację poEndMarkedItem<T>
s whileEndMark == 0
.Jeśli nie określisz
tailLength
, tylko ostatnia pozycja zostanie oznaczona (wMarkEnd()
) lub upuszczona (wButLast()
).Podobnie jak inne rozwiązania, działa to przez buforowanie.
using System; using System.Collections.Generic; using System.Linq; namespace Adhemar.Util.Linq { public struct EndMarkedItem<T> { public T Item { get; private set; } public int EndMark { get; private set; } public EndMarkedItem(T item, int endMark) : this() { Item = item; EndMark = endMark; } } public static class TailEnumerables { public static IEnumerable<T> ButLast<T>(this IEnumerable<T> ts) { return ts.ButLast(1); } public static IEnumerable<T> ButLast<T>(this IEnumerable<T> ts, int tailLength) { return ts.MarkEnd(tailLength).TakeWhile(te => te.EndMark == 0).Select(te => te.Item); } public static IEnumerable<EndMarkedItem<T>> MarkEnd<T>(this IEnumerable<T> ts) { return ts.MarkEnd(1); } public static IEnumerable<EndMarkedItem<T>> MarkEnd<T>(this IEnumerable<T> ts, int tailLength) { if (tailLength < 0) { throw new ArgumentOutOfRangeException("tailLength"); } else if (tailLength == 0) { foreach (var t in ts) { yield return new EndMarkedItem<T>(t, 0); } } else { var buffer = new T[tailLength]; var index = -buffer.Length; foreach (var t in ts) { if (index < 0) { buffer[buffer.Length + index] = t; index++; } else { yield return new EndMarkedItem<T>(buffer[index], 0); buffer[index] = t; index++; if (index == buffer.Length) { index = 0; } } } if (index >= 0) { for (var i = index; i < buffer.Length; i++) { yield return new EndMarkedItem<T>(buffer[i], i - buffer.Length - index); } for (var j = 0; j < index; j++) { yield return new EndMarkedItem<T>(buffer[j], j - index); } } else { for (var k = 0; k < buffer.Length + index; k++) { yield return new EndMarkedItem<T>(buffer[k], k - buffer.Length - index); } } } } } }
źródło
public static IEnumerable<T> NoLast<T> (this IEnumerable<T> items) { if (items != null) { var e = items.GetEnumerator(); if (e.MoveNext ()) { T head = e.Current; while (e.MoveNext ()) { yield return head; ; head = e.Current; } } } }
źródło
Nie sądzę, że może to być bardziej zwięzłe niż to - zapewniając również Pozbywanie się
IEnumerator<T>
:public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source) { using (var it = source.GetEnumerator()) { if (it.MoveNext()) { var item = it.Current; while (it.MoveNext()) { yield return item; item = it.Current; } } } }
Edycja: technicznie identyczna z tą odpowiedzią .
źródło
Możesz napisać:
var list = xyz.Select(x=>x.Id).ToList(); list.RemoveAt(list.Count - 1);
źródło
Jest to ogólne i eleganckie rozwiązanie IMHO, które poprawnie obsłuży wszystkie przypadki:
using System; using System.Collections.Generic; using System.Linq; public class Program { public static void Main() { IEnumerable<int> r = Enumerable.Range(1, 20); foreach (int i in r.AllButLast(3)) Console.WriteLine(i); Console.ReadKey(); } } public static class LinqExt { public static IEnumerable<T> AllButLast<T>(this IEnumerable<T> enumerable, int n = 1) { using (IEnumerator<T> enumerator = enumerable.GetEnumerator()) { Queue<T> queue = new Queue<T>(n); for (int i = 0; i < n && enumerator.MoveNext(); i++) queue.Enqueue(enumerator.Current); while (enumerator.MoveNext()) { queue.Enqueue(enumerator.Current); yield return queue.Dequeue(); } } } }
źródło
Moje tradycyjne
IEnumerable
podejście:/// <summary> /// Skips first element of an IEnumerable /// </summary> /// <typeparam name="U">Enumerable type</typeparam> /// <param name="models">The enumerable</param> /// <returns>IEnumerable of type skipping first element</returns> private IEnumerable<U> SkipFirstEnumerable<U>(IEnumerable<U> models) { using (var e = models.GetEnumerator()) { if (!e.MoveNext()) return; for (;e.MoveNext();) yield return e.Current; yield return e.Current; } } /// <summary> /// Skips last element of an IEnumerable /// </summary> /// <typeparam name="U">Enumerable type</typeparam> /// <param name="models">The enumerable</param> /// <returns>IEnumerable of type skipping last element</returns> private IEnumerable<U> SkipLastEnumerable<U>(IEnumerable<U> models) { using (var e = models.GetEnumerator()) { if (!e.MoveNext()) return; yield return e.Current; for (;e.MoveNext();) yield return e.Current; } }
źródło
Prostym sposobem byłoby po prostu przekonwertowanie na kolejkę i usunięcie z kolejki, aż pozostanie tylko liczba elementów, które chcesz pominąć.
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int n) { var queue = new Queue<T>(source); while (queue.Count() > n) { yield return queue.Dequeue(); } }
źródło
Możliwe:
var allBuLast = sequence.TakeWhile(e => e != sequence.Last());
Myślę, że powinno to być jak de "Gdzie", ale z zachowaniem kolejności (?).
źródło
Jeśli wymagana jest szybkość, ta stara metoda powinna być najszybsza, nawet jeśli kod nie wygląda tak gładko, jak mógłby to zrobić linq.
int[] newSequence = int[sequence.Length - 1]; for (int x = 0; x < sequence.Length - 1; x++) { newSequence[x] = sequence[x]; }
Wymaga to, aby sekwencja była tablicą, ponieważ ma stałą długość i indeksowane elementy.
źródło
Prawdopodobnie zrobiłbym coś takiego:
Jest to jedna iteracja ze sprawdzeniem, czy nie jest to ostatnia za każdym razem.
źródło