Disons que j'ai une séquence.
IEnumerable<int> sequence = GetSequenceFromExpensiveSource();
// sequence now contains: 0,1,2,3,...,999999,1000000
Obtenir la séquence n’est pas bon marché et est généré dynamiquement, et je souhaite parcourir cette séquence une fois seulement.
Je veux obtenir 0 - 999999 (c'est-à-dire tout sauf le dernier élément)
Je reconnais que je pourrais faire quelque chose comme:
sequence.Take(sequence.Count() - 1);
mais cela aboutit à deux énumérations sur la grande séquence.
Existe-t-il une construction LINQ qui me permet de faire:
sequence.TakeAllButTheLastElement();
Je ne connais pas de solution Linq - Mais vous pouvez facilement coder l'algorithme vous-même à l'aide de générateurs (rendement).
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()));
}
Ou, en tant que solution généralisée, en supprimant les n derniers éléments (en utilisant une file d'attente comme suggéré dans les commentaires):
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()));
}
Au lieu de créer votre propre méthode et dans un cas, l'ordre des éléments n'est pas important, la suivante fonctionnera:
var result = sequence.Reverse().Skip(1);
Parce que je ne suis pas fan de l'utilisation explicite d'une Enumerator
, voici une alternative. Notez que les méthodes d'encapsulation sont nécessaires pour permettre aux arguments non valides de commencer plus tôt, plutôt que de différer les vérifications jusqu'à l'énumération de la séquence.
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;
}
}
Selon la suggestion d'Eric Lippert, il se généralise facilement à n éléments:
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();
}
}
Où je tampon maintenant avant cédant au lieu de céder après, de sorte que le cas n == 0
ne nécessite pas de traitement spécial.
Rien dans la BCL (ou MoreLinq, je crois), mais vous pouvez créer votre propre méthode d'extension.
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;
}
}
}
Il serait utile que .NET Framework soit livré avec une méthode d’extension comme celle-ci.
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();
}
}
Pour ceux qui utilisent une version plus récente de .net, la méthode Enumerable.SkipLast(IEnumerable<TSource>, Int32)
a été ajoutée à .NET Core 2.0.
var sequence = GetSequence();
var allExceptLast = sequence.SkipLast(1);
Source: https://docs.Microsoft.com/en-us/dotnet/api/system.linq.enumerable.skiplast
si vous n'avez pas le temps de déployer votre propre extension, voici un moyen plus rapide:
var next = sequence.First();
sequence.Skip(1)
.Select(s =>
{
var selected = next;
next = s;
return selected;
});
Une légère expansion sur la solution élégante de Joren:
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);
}
Où shrink implémente un simple compte à terme pour supprimer le premier left
nombreux éléments et le même tampon supprimé pour supprimer le dernier right
nombreux éléments.
Une légère variation sur la réponse acceptée, ce qui (à mon goût) est un peu plus simple:
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);
}
}
Si vous pouvez obtenir la Count
ou Length
d'un énumérable, ce que vous pouvez dans la plupart des cas, alors Take(n - 1)
Exemple avec des tableaux
int[] arr = new int[] { 1, 2, 3, 4, 5 };
int[] sub = arr.Take(arr.Length - 1).ToArray();
Exemple avec IEnumerable<T>
IEnumerable<int> enu = Enumerable.Range(1, 100);
IEnumerable<int> sub = enu.Take(enu.Count() - 1);
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;
}
}
}
}
Je ne pense pas que cela puisse être plus succinct que cela - en veillant également à éliminer le 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;
}
}
}
}
Edit: techniquement identique à cette réponse .
La solution que j'utilise pour ce problème est légèrement plus élaborée.
Ma classe statique util contient une méthode d'extension MarkEnd
qui convertit les éléments T
- en éléments EndMarkedItem<T>
-. Chaque élément est marqué avec un int
supplémentaire, qui est soit 0 ; ou (si les 3 derniers éléments sont particulièrement intéressants)/3 , -2 ou -1 pour les 3 derniers éléments.
Cela pourrait être utile en soi, p. lorsque vous voulez créer une liste dans une simple boucle foreach
- avec des virgules après chaque élément sauf le dernier 2, avec l'avant-dernier élément suivi d'un mot de conjonction (tel que «et» ou ou ”), et le dernier élément suivi d'un point.
Pour générer la liste complète sans les derniers éléments n, la méthode d'extension ButLast
itère simplement le EndMarkedItem<T>
s avec le EndMark == 0
.
Si vous ne spécifiez pas tailLength
, seul le dernier élément est marqué (dans MarkEnd()
) ou supprimé (dans ButLast()
).
Comme les autres solutions, cela fonctionne en tampon.
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);
}
}
}
}
}
}
Ceci est une solution générale et élégante IMHO qui traitera tous les cas correctement:
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();
}
}
}
}
Pourquoi ne pas simplement .ToList<type>()
sur la séquence, puis compter les appels et prendre comme vous l’avez fait à l’origine ... mais comme il a été placé dans une liste, il ne devrait pas faire une énumération coûteuse deux fois. Droite?
Vous pouvez écrire:
var list = xyz.Select(x=>x.Id).ToList();
list.RemoveAt(list.Count - 1);