I have a library that only accepts a proprietary immutable collection type. I would like to have a function that accepts one of these collections and performs some changes to this collection by returning a new collection that contains the changes made.
I would like to use a LINQ syntax instead of copying this collection to a List and back.
Add operations is easy for me: concat the enumerable with another one.
But what about Replace (at the given index, return the value given instead of that IEnumerable's value), Insert (at given index, return the given value and then continue iterating over the IEnumerable) or Delete (at given index, skip the IEnumerable's value)?
Are there functions like this available in the .NET framework or in another library? If not, how would I go about implementing these functions?
You can make your own extensions for these operations:
Add
public static IEnumerable<T> Add<T>(this IEnumerable<T> enumerable, T value)
{
foreach (var item in enumerable)
yield return item;
yield return value;
}
or:
public static IEnumerable<T> Add<T>(this IEnumerable<T> enumerable, T value)
{
return enumerable.Concat(new T[] { value });
}
Insert
public static IEnumerable<T> Insert<T>(this IEnumerable<T> enumerable, int index, T value)
{
int current = 0;
foreach (var item in enumerable)
{
if (current == index)
yield return value;
yield return item;
current++;
}
}
or
public static IEnumerable<T> Insert<T>(this IEnumerable<T> enumerable, int index, T value)
{
return enumerable.SelectMany((x, i) => index == i ? new T[] { value, x } : new T[] { x });
}
Replace
public static IEnumerable<T> Replace<T>(this IEnumerable<T> enumerable, int index, T value)
{
int current = 0;
foreach (var item in enumerable)
{
yield return current == index ? value : item;
current++;
}
}
or
public static IEnumerable<T> Replace<T>(this IEnumerable<T> enumerable, int index, T value)
{
return enumerable.Select((x, i) => index == i ? value : x);
}
Remove
public static IEnumerable<T> Remove<T>(this IEnumerable<T> enumerable, int index)
{
int current = 0;
foreach (var item in enumerable)
{
if (current != index)
yield return item;
current++;
}
}
or
public static IEnumerable<T> Remove<T>(this IEnumerable<T> enumerable, int index)
{
return enumerable.Where((x, i) => index != i);
}
Then you can make calls like this:
IEnumerable<int> collection = new int[] { 1, 2, 3, 4, 5 };
var added = collection.Add(6); // 1, 2, 3, 4, 5, 6
var inserted = collection.Insert(0, 0); // 0, 1, 2, 3, 4, 5
var replaced = collection.Replace(1, 22); // 1, 22, 3, 4, 5
var removed = collection.Remove(2); // 1, 2, 4, 5
The question is a little broad, so I'll demonstrate a possibility for a Replace method. There are no methods for that in the framework that replace something in an IEnumerable, as IEnumerable should represent an immutable sequence.
So a naive way to return a new IEnumerable with replaced elements:
public static class Extensions
{
public static IEnumerable<T> Replace<T>(this IEnumerable<T> source, T oldValue, T newValue)
{
return source.Select(element => element == oldValue ? newValue : element);
}
}
This will iterate through the source sequence and return the source elements except for those who Equal the oldValue. Note that this uses the == operator and how this works depends on the type argument for T.
Also note that this uses deferred execution. The source sequence is only enumerated when you start to enumerate the resulting IEnumerable. So if you change the source sequence after a call to Replace, the resulting sequence will yield this change, too.
Implementations for Insert and Delete are straight forward, too, though you'll need to count an index in the source sequence.
IEnumerable is by definition a immutable enumerable collection of elements of a given type. Immutable means that you cannot modify it directly and you always have to create a new instance.
You can however use the yield keyword to implement this behavior, preferably using extension methods.
For example replace could look like this:
public static IEnumerable<T> ReplaceAt<T>(this IEnumerable<T> collection, int index, T item)
{
var currentIndex = 0;
foreach (var originalItem in collection)
{
if (currentIndex != index)
{
//keep the original item in place
yield return originalItem;
}
else
{
//we reached the index where we want to replace
yield return item;
}
currentIndex++;
}
}
For a functional, more LINQ-ey feeling solution, inspired by Arturo's answer:
public static IEnumerable<T> Replace<T>(this IEnumerable<T> enumerable, Func<T, bool> selector, T value)
{
foreach (var item in enumerable)
{
yield return selector(item) ? value : item;
}
}
Used like this: var newEnumerable = oldEnumerable.Replace(x => x.Id == 1, newItem)
Related
I've got an enumerable that contains responses from a service call that come in gradually.
I can't do ToList on the enumerable as that would block until all responses are received instead of listing them as they come.
I also can't iterate twice as that would trigger another service call.
How to get the first element in the enumerable and return the continuation of the enumerable? I can't use an iterator method as I get a compilation error:
Iterators cannot have ref, in or out parameters.
I've tried this code:
public IEnumerable<object> GetFirstAndRemainder(IEnumerable<object> enumerable, out object first)
{
first = enumerable.Take(1).FirstOrDefault();
return enumerable.Skip(1); // Second interation - unexceptable
}
// This one has a compilation error: Iterators cannot have ref, in or out parameters
public IEnumerable<object> GetFirstAndRemainder2(IEnumerable<object> enumerable, out object first)
{
var enumerator = enumerable.GetEnumerator();
enumerator.MoveNext();
first = enumerator.Current;
while (enumerator.MoveNext())
{
yield return enumerator.Current;
}
}
Instead of using an out parameter, you can use ValueTuple<T1, T2> (as of C# 7.0, documented here) to return two elements: the first item of the IEnumerable<T>, and the remainder as another IEnumerable<T>.
using System.Linq;
class Program {
static void Main(string[] args) {
(int first, IEnumerable<int> remainder) = GetFirstAndRemainder(Enumerable.Range(1, 5));
// first = 1
// remainder yields (2, 3, 4, 5)
}
// Returns the first item and the remainders as an IEnumerable
static (T, IEnumerable<T>) GetFirstAndRemainder<T>(IEnumerable<T> sequence) {
var enumerator = sequence.GetEnumerator();
enumerator.MoveNext();
return (enumerator.Current, enumerator.AsEnumerable());
}
}
You also need to convert from an IEnumerator to an IEnumerable which I did with an extension method:
static class Extensions {
public static IEnumerable<T> AsEnumerable<T>(this IEnumerator<T> enumerator) {
while (enumerator.MoveNext()) {
yield return enumerator.Current;
}
}
}
Note that due to your requirements, iterating once over the remainder will exhaust it even though it has the type IEnumerable<T>.
There is also the possibility to do cheeky deconstruction like so:
var (x, xs) = new int?[] { 1, 2, 3 };
// x = 1, xs = 2, 3
All you need is to implement Deconstruct() on IEnumerable<T>. Based on the implementations from previous answers:
public static class Ext {
public static void Deconstruct<T>(this IEnumerable<T> source, out T? first, out IEnumerable<T> tail) {
using var e = source.GetEnumerator();
(first, tail) = e.MoveNext()
? (e.Current, e.AsEnumerable())
: (default, Enumerable.Empty<T>());
}
public static IEnumerable<T> AsEnumerable<T>(this IEnumerator<T> enumerator) {
while (enumerator.MoveNext()) yield return enumerator.Current;
}
}
If you feel particularly funny you can extend that approach to do things like this:
var (fst, snd, trd, rest) = new int?[] { 1, 2, 3, 4, 5 };
public static class Ext {
// ...
public static void Deconstruct<T>(this IEnumerable<T> source, out T? first, out T? second, out IEnumerable<T> tail) {
using var e = source.GetEnumerator();
if (e.MoveNext())
(first, (second, tail)) = (e.Current, e.AsEnumerable());
else
(first, second, tail) = (default, default, Enumerable.Empty<T>());
}
public static void Deconstruct<T>(this IEnumerable<T> source, out T? first, out T? second, out T? third, out IEnumerable<T> tail) {
using var e = source.GetEnumerator();
if (e.MoveNext())
(first, (second, third, tail)) = (e.Current, e.AsEnumerable());
else
(first, second, third, tail) = (default, default, default, Enumerable.Empty<T>());
}
}
As the answer of #corentin-pane has too many pending edits, I will share the extension methods I ended up implementing as a full new answer
public static class ExtraLinqExtensions
{
public static (T, IEnumerable<T>) GetFirstAndRemainder<T>(this IEnumerable<T> sequence)
{
using var enumerator = sequence.GetEnumerator();
return enumerator.MoveNext()
? (enumerator.Current, enumerator.AsEnumerable())
: (default, Enumerable.Empty<T>());
}
public static IEnumerable<T> AsEnumerable<T>(this IEnumerator<T> enumerator)
{
while (enumerator.MoveNext())
{
yield return enumerator.Current;
}
}
}
Where the most notable change is the check for whether the enumerator succeeded in doing a MoveNext().
I'm trying to using a fluent interface to build a collection, similar to this (simplified) example:
var a = StartWith(1).Add(2).Add(3).Add(4).ToArray();
/* a = int[] {1,2,3,4}; */
The best solution I can come up with add Add() as:
IEnumerable<T> Add<T>(this IEnumerable<T> coll, T item)
{
foreach(var t in coll) yield return t;
yield return item;
}
Which seems to add a lot of overhead that going to be repeated in each call.
IS there a better way?
UPDATE:
in my rush, I over-simplified the example, and left out an important requirement. The last item in the existing coll influences the next item. So, a slightly less simplified example:
var a = StartWith(1).Times10Plus(2).Times10Plus(3).Times10Plus(4).ToArray();
/* a = int[] {1,12,123,1234}; */
public static IEnumerable<T> StartWith<T>(T x)
{
yield return x;
}
static public IEnumerable<int> Times10Plus(this IEnumerable<int> coll, int item)
{
int last = 0;
foreach (var t in coll)
{
last = t;
yield return t;
}
yield return last * 10 + item;
}
A bit late to this party, but here are a couple ideas.
First, consider solving the more general problem:
public static IEnumerable<A> AggregateSequence<S, A>(
this IEnumerable<S> items,
A initial,
Func<A, R, A> f)
{
A accumulator = initial;
yield return accumulator;
foreach(S item in items)
{
accumulator = f(accumulator, item);
yield return accumulator;
}
}
And now your program is just new[]{2, 3, 4}.AggregateSequence(1,
(a, s) => a * 10 + s).ToArray()
However that lacks the "fluency" you want and it assumes that the same operation is applied to every element in the sequence.
You are right to note that deeply nested iterator blocks are problematic; they have quadratic performance in time and linear consumption of stack, both of which are bad.
Here's an entertaining way to implement your solution efficiently.
The problem is that you need both cheap access to the "right" end of the sequence, in order to do an operation on the most recently added element, but you also need cheap access to the left end of the sequence to enumerate it. Normal queues and stacks only have cheap access to one end.
Therefore: start by implementing an efficient immutable double-ended queue. This is a fascinating datatype; I have an implementation here using finger trees:
https://blogs.msdn.microsoft.com/ericlippert/2008/01/22/immutability-in-c-part-10-a-double-ended-queue/
https://blogs.msdn.microsoft.com/ericlippert/2008/02/12/immutability-in-c-part-eleven-a-working-double-ended-queue/
Once you have that, your operations are one-liners:
static IDeque<T> StartWith<T>(T t) => Deque<T>.Empty.EnqueueRight(t);
static IDeque<T> Op<T>(this IDeque<T> d, Func<T, T> f) => d.EnqueueRight(f(d.PeekRight()));
static IDeque<int> Times10Plus(this IDeque<int> d, int j) => d.Op(i => i * 10 + j);
Modify IDeque<T> and Deque<T> to implement IEnumerable<T> in the obvious way and you then get ToArray for free. Or do it as an extension method:
static IEnumerable<T> EnumerateFromLeft(this IDeque<T> d)
{
var c = d;
while (!c.IsEmpty)
{
yield return c.PeekLeft();
c = c.DequeueLeft();
}
}
You could do the following:
public static class MySequenceExtensions
{
public static IReadOnlyList<int> Times10Plus(
this IReadOnlyList<int> sequence,
int value) => Add(sequence,
value,
v => sequence[sequence.Count - 1] * 10 + v);
public static IReadOnlyList<T> Starts<T>(this T first)
=> new MySequence<T>(first);
public static IReadOnlyList<T> Add<T>(
this IReadOnlyList<T> sequence,
T item,
Func<T, T> func)
{
var mySequence = sequence as MySequence<T> ??
new MySequence<T>(sequence);
return mySequence.AddItem(item, func);
}
private class MySequence<T>: IReadOnlyList<T>
{
private readonly List<T> innerList;
public MySequence(T item)
{
innerList = new List<T>();
innerList.Add(item);
}
public MySequence(IEnumerable<T> items)
{
innerList = new List<T>(items);
}
public T this[int index] => innerList[index];
public int Count => innerList.Count;
public MySequence<T> AddItem(T item, Func<T, T> func)
{
Debug.Assert(innerList.Count > 0);
innerList.Add(func(item));
return this;
}
public IEnumerator<T> GetEnumerator() => innerList.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
Note that I'm using IReadOnlyList to make it possible to index into the list in a performant way and be able to get the last element if needed. If you need a lazy enumeration then I think you are stuck with your original idea.
And sure enough, the following:
var a = 1.Starts().Times10Plus(2).Times10Plus(3).Times10Plus(4).ToArray();
Produces the expected result ({1, 12, 123, 1234}) with, what I think is, reasonable performance.
You can do like this:
public interface ISequence
{
ISequenceOp StartWith(int i);
}
public interface ISequenceOp
{
ISequenceOp Times10Plus(int i);
int[] ToArray();
}
public class Sequence : ISequence
{
public ISequenceOp StartWith(int i)
{
return new SequenceOp(i);
}
}
public class SequenceOp : ISequenceOp
{
public List<int> Sequence { get; set; }
public SequenceOp(int startValue)
{
Sequence = new List<int> { startValue };
}
public ISequenceOp Times10Plus(int i)
{
Sequence.Add(Sequence.Last() * 10 + i);
return this;
}
public int[] ToArray()
{
return Sequence.ToArray();
}
}
An then just:
var x = new Sequence();
var a = x.StartWith(1).Times10Plus(2).Times10Plus(3).Times10Plus(4).ToArray();
I want to make a copy of an IEnumerable<T> in which a single item at a given index has been replaced by a given value.
I defined the following method which does what I want:
public static IEnumerable<T> ReplaceAt<T>(this IEnumerable<T> source, T item, int index)
{
foreach (T before in source.Take(index))
{
yield return before;
}
yield return item;
foreach (T after in source.Skip(index + 1))
{
yield return after;
}
}
However, while perhaps easy to understand, it seems 'inefficient' to create two iterators, one of which skips the items already taken by the first iterator.
Is there a better way of defining this?
How about:
public static IEnumerable<T> ReplaceAt<T>(this IEnumerable<T> source, T item, int index)
{
return source.Select((value, i) => index == i ? item : value);
}
Not sure about efficient, but have you tried this?
public static IEnumerable<T> ReplaceAt<T>(this IEnumerable<T> source, T item, int index)
{
return source.Select((x, i) => i == index ? item : x);
}
If you want to go crazy, you can unroll the foreach manually:
public static IEnumerable<T> ReplaceAt<T>(this IEnumerable<T> source, T item, int index)
{
int itemIndex = 0;
using(var iter = source.GetEnumerator())
{
while(iter.MoveNext())
{
yield return itemIndex++ == index ? item : iter.Current;
}
}
}
What is the best way to get exactly x values from an Enumerable in C#.
If i use Enumerable .Take() like this:
var myList = Enumerable.Range(0,10);
var result = myList.Take(20);
The result will only have 10 elements.
I want to fill the missing entries with a default value.
Something like this:
var myList = Enumerable.Range(0,10);
var result = myList.TakeOrDefault(20, default(int)); //Is there anything like this?
Is there such a function in C# and if not, what would be the best way to achieve this?
You could do something like:
var result = myList.Concat(Enumerable.Repeat(default(int), 20)).Take(20);
And it would be easy to turn this into an extension method:
public static IEnumerable<T> TakeOrDefault<T>(this IEnumerable<T> list, int count, T defaultValue)
{
return list.Concat(Enumerable.Repeat(defaultValue, count)).Take(count);
}
But there is a subtle gotcha here. This would work perfectly fine for value types, for a reference type, if your defaultValue isn't null, you are adding the same object multiple times. Which probably isn't want you want. For example, if you had this:
var result = myList.TakeOrDefault(20, new Foo());
You are going to add the same instance of Foo to pad your collection. To solve that problem, you'd need something like this:
public static IEnumerable<T> TakeOrDefault<T>(this IEnumerable<T> list, int count, Func<T> defaultFactory)
{
return list.Concat(Enumerable.Range(0, count).Select(i => defaultFactory())).Take(count);
}
Which you'd call like this:
var result = myList.TakeOrDefault(20, () => new Foo())
Of course, both methods can co-exist, so you could easily have:
// pad a list of ints with zeroes
var intResult = myIntList.TakeOrDefault(20, default(int));
// pad a list of objects with null
var objNullResult = myObjList.TakeOrDefault(20, (object)null);
// pad a list of Foo with new (separate) instances of Foo
var objPadNewResult = myFooList.TakeOrDefault(20, () => new Foo());
Its not there by default, but it's easy enough to write as an extension method
public static IEnumerable<T> TakeOrDefault<T>(this IEnumerable<T> items, int count, T defaultValue)
{
var i = 0;
foreach(var item in items)
{
i++;
yield return item;
if(i == count)
yield break;
}
while(i++<count)
{
yield return defaultValue;
}
}
Live example: http://rextester.com/XANF91263
What you're looking for is a general-purpose PadTo method, which extends the collection's length if needed using a given value.
public static IEnumerable<T> PadTo<T>(this IEnumerable<T> source, int len)
{
return source.PadTo(len, default(T));
}
public static IEnumerable<T> PadTo<T>(this IEnumerable<T> source, int len, T elem)
{
return source.PadTo(len, () => elem);
}
public static IEnumerable<T> PadTo<T>(this IEnumerable<T> source, int len, Func<T> elem)
{
int i = 0;
foreach(var t in source)
{
i++;
yield return t;
}
while(i++ < len)
yield return elem();
}
You can now express:
myList.Take(20).PadTo(20);
This is analogous to Scala's List[A].padTo
You could use Concat for this purpose. You can use a simple helper method to join this all together:
public IEnumerable<T> TakeSpawn(this IEnumerable<T> #this, int take, T defaultElement)
{
return #this.Concat(Enumerable.Repeat(defaultElement, take)).Take(take);
}
The idea is that you always append another enumerable on the end of the original enumerable, so if the input doesn't have enough elements, it will start enumerating from the Repeat.
There isn't anything in the .NET Framework, not that I'm aware of. This can be achieved easily using an extension method though and it works for all types if you supply a default value yourself:
public static class ListExtensions
{
public static IEnumerable<T> TakeOrDefault<T>(this List<T> list, int count, T defaultValue)
{
int missingItems = count - list.Count;
List<T> extra = new List<T>(missingItems);
for (int i = 0; i < missingItems; i++)
extra.Add(defaultValue);
return list.Take(count).Concat(extra);
}
}
I wrote a quick extension for this which depends on T being a value type.
public static class Extensions
{
public static IEnumerable<T> TakeOrDefault<T>(this IEnumerable<T> list, int totalElements)
{
List<T> finalList = list.ToList();
if (list.Count() < totalElements)
{
for (int i = list.Count(); i < totalElements; i++)
{
finalList.Add(Activator.CreateInstance<T>());
}
}
return finalList;
}
}
Why not just write an extension method that checks the count and returns the default value for remaining entries:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
List<int> values = new List<int>{1, 2, 3, 4};
IEnumerable<int> moreValues = values.TakeOrDefault(3, 100);
Console.WriteLine(moreValues.Count());
moreValues = values.TakeOrDefault(4, 100);
Console.WriteLine(moreValues.Count());
moreValues = values.TakeOrDefault(10, 100);
Console.WriteLine(moreValues.Count());
}
}
public static class ExtensionMethods
{
public static IEnumerable<T> TakeOrDefault<T>(this IEnumerable<T> enumerable, int count, T defaultValue)
{
int returnedCount = 0;
foreach (T variable in enumerable)
{
returnedCount++;
yield return variable;
if (returnedCount == count)
{
yield break;
}
}
if (returnedCount < count)
{
for (int i = returnedCount; i < count; i++)
{
yield return defaultValue;
}
}
}
}
}
I needed a method to give me all but the last item in a sequence. This is my current implementation:
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source)
{
using (IEnumerator<T> iterator = source.GetEnumerator())
{
if(iterator.MoveNext())
while(true)
{
var current = iterator.Current;
if(!iterator.MoveNext())
yield break;
yield return current;
}
}
}
What I need it for is to do something with all the items except the last one. In my case I have a sequence of objects with various properties. I then order them by date, and then I need to do an adjustment to all of them except the most recent item (which would be the last one after ordering).
Thing is, I am not too into these enumerators and stuff yet and don't really have anyone here to ask either :p What I am wondering is if this is a good implementation, or if I have done a small or big blunder somewhere. Or if maybe this take on the problem is a weird one, etc.
I guess a more general implementation could have been an AllExceptMaxBy method. Since that is kind of what it is. The MoreLinq has a MaxBy and MinBy method and my method kind of need to do the same, but return every item except the maximum or minimum one.
This is tricky, as "last element" isn't a Markov stopping point: you can't tell that you've got to the last element until you try to get the next one. It's doable, but only if you don't mind permanently being "one element behind". That's basically what your current implementation does, and it looks okay, although I'd probably write it slightly differently.
An alternative approach would be to use foreach, always yielding the previously returned value unless you were at the first iteration:
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source)
{
T previous = default(T);
bool first = true;
foreach (T element in source)
{
if (!first)
{
yield return previous;
}
previous = element;
first = false;
}
}
Another option, closer to your code:
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source)
{
using (IEnumerator<T> iterator = source.GetEnumerator())
{
if(!iterator.MoveNext())
{
yield break;
}
T previous = iterator.Current;
while (iterator.MoveNext())
{
yield return previous;
previous = iterator.Current;
}
}
}
That avoids nesting quite as deeply (by doing an early exit if the sequence is empty) and it uses a "real" while condition instead of while(true)
If you're using .NET 3.5, I guess you could use:
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source)
{
return source.TakeWhile((item, index) => index < source.Count() - 1))
}
Your implementation looks perfectly fine to me - it's probably the way I would do it.
The only simplification I might suggest in relation to your situation is to order the list the other way round (i.e. ascending rather than descending). Although this may not be suitable in your code, it would allow you to simply use collection.Skip(1) to take all items except the most recent one.
If this isn't possible for reasons you haven't shown in your post, then your current implementation is no problem at all.
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source)
{
if (!source.Any())
{
yield break;
}
Queue<T> items = new Queue<T>();
items.Enqueue(source.First());
foreach(T item in source.Skip(1))
{
yield return items.Dequeue();
items.Enqueue(item);
}
}
(Old answer scrapped; this code has been tested and works.) It prints
first
second
FIRST
SECOND
THIRD
public static class ExtNum{
public static IEnumerable skipLast(this IEnumerable source){
if ( ! source.Any())
yield break;
for (int i = 0 ; i <=source.Count()-2 ; i++ )
yield return source.ElementAt(i);
yield break;
}
}
class Program
{
static void Main( string[] args )
{
Queue qq = new Queue();
qq.Enqueue("first");qq.Enqueue("second");qq.Enqueue("third");
List lq = new List();
lq.Add("FIRST"); lq.Add("SECOND"); lq.Add("THIRD"); lq.Add("FOURTH");
foreach(string s1 in qq.skipLast())
Console.WriteLine(s1);
foreach ( string s2 in lq.skipLast())
Console.WriteLine(s2);
}
}
Combining all answers, and using <LangVersion>latest<LangVersion>:
.NET Fiddle
public static class EnumerableExtensions
{
// Source is T[]
public static IEnumerable<T> SkipLast<T>(this T[] source, int count) =>
source.TakeWhile((item, index) => index < source.Length - count);
public static IEnumerable<T> SkipLast<T>(this T[] source) => source.SkipLast(1);
// Source is ICollection<T>
public static IEnumerable<T> SkipLast<T>(this ICollection<T> source, int count) =>
source.TakeWhile((item, index) => index < source.Count - count);
public static IEnumerable<T> SkipLast<T>(this ICollection<T> source) => source.SkipLast(1);
// Source is unknown or IEnumerable<T>
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int count)
{
switch (source)
{
case T[] array:
return SkipLast(array, count);
case ICollection<T> collection:
return SkipLast(collection, count);
default:
return skipLast();
}
IEnumerable<T> skipLast()
{
using IEnumerator<T> iterator = source.GetEnumerator();
if (!iterator.MoveNext())
yield break;
Queue<T> items = new Queue<T>(count);
items.Enqueue(iterator.Current);
for (int i = 1; i < count && iterator.MoveNext(); i++)
items.Enqueue(iterator.Current);
while (iterator.MoveNext())
{
yield return items.Dequeue();
items.Enqueue(iterator.Current);
}
}
}
public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source)
{
switch (source)
{
case T[] array:
return SkipLast(array);
case ICollection<T> collection:
return SkipLast(collection);
default:
return skipLast();
}
IEnumerable<T> skipLast()
{
using IEnumerator<T> iterator = source.GetEnumerator();
if (!iterator.MoveNext())
yield break;
T previous = iterator.Current;
while (iterator.MoveNext())
{
yield return previous;
previous = iterator.Current;
}
}
}
}