LINQ continues after Take

Say we have an IEnumerable<T> stuff;

Is there a compressed way to take n elements, and then another m elements after the first, without revaluation?

code example:

 stuff.Take(10); stuff.Skip(10).Take(20); // re-evaluates stuff 

What I was thinking maybe this is (not working code)

 var it = stuff.GetEnumerator(); it.Take(10); it.Take(20); 

Edit to add to the complexity and clarify the complexity of what I would like to accomplish: I want to continue the request after Take, i.e.

 it.Take(10); var cont = it.Select(Mutate); cont.Take(20); cont = cont.Where(Filter); cont.Take(5); 
+7
c # linq
source share
2 answers

If you just want to create a wrapper for IEnumerable that will handle any LINQ added and passing one pass through the source, use this class and extension:

 public static class EnumerableOnceExt { public static EnumerableOnce<IEnumerable<T>, T> EnumerableOnce<T>(this IEnumerable<T> src) => new EnumerableOnce<IEnumerable<T>, T>(src); } public class EnumerableOnce<T, V> : IEnumerable<V>, IDisposable where T : IEnumerable<V> { EnumeratorOnce<V> onceEnum; public EnumerableOnce(T src) { onceEnum = new EnumeratorOnce<V>(src.GetEnumerator()); } public IEnumerator<V> GetEnumerator() { return onceEnum; } IEnumerator IEnumerable.GetEnumerator() { return onceEnum; } public void DoSkip(int n) { while (n > 0 && onceEnum.MoveNext()) --n; } public void DoTake(int n) { while (n > 0 && onceEnum.MoveNext()) --n; } #region IDisposable Support private bool disposedValue = false; // To detect redundant calls protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { onceEnum.ActuallyDispose(); } disposedValue = true; } } // This code added to correctly implement the disposable pattern. public void Dispose() { Dispose(true); } #endregion } public class EnumeratorOnce<V> : IEnumerator<V> { IEnumerator<V> origEnum; public EnumeratorOnce(IEnumerator<V> src) { origEnum = src; } public V Current => origEnum.Current; object IEnumerator.Current => origEnum.Current; public bool MoveNext() => origEnum.MoveNext(); public void Reset() { origEnum.Reset(); } public void ActuallyDispose() { origEnum.Dispose(); } #region IDisposable Support protected virtual void Dispose(bool disposing) { // don't allow disposing early } // This code added to correctly implement the disposable pattern. public void Dispose() { Dispose(true); } #endregion } 

Now your sample code will work if you call EnumerableOnce() to wrap the source code while you are doing the enumerations:

 var it1 = it.EnumerableOnce(); it1.Take(10).ToList(); var @continue = it1.Select(Mutate); @continue.Take(20).ToList(); @continue = @continue.Where(Filter); @continue.Take(5).ToList(); 

You can also add new methods to EnumerableOnce :

 public void DoSkip(int n) { while (n > 0 && srcEnum.MoveNext()) --n; } public void DoTake(int n) { while (n > 0 && srcEnum.MoveNext()) --n; } 

And call them:

 var it1 = it.EnumerableOnce(); it1.DoTake(10); var @continue = it1.Select(Mutate); @continue.DoSkip(20); @continue = @continue.Where(Filter); @continue.DoTake(5); 
+2
source share

You can use the Publish extension method in the System.Interactive NuGet package released by Microsoft to accomplish this. This is a fantastic library that provides some of the "missing" LINQ features. From the documentation, the Publish method:

Creates a buffer with a view of the original sequence, forcing each enumerator to access the remainder of the sequence from the current index in the buffer.

those. it allows you to partially list the sequence, and the next time you list the sequence that you pick up where the previous listing ended.

 var publishedSource = stuff.Publish(); var firstTenItems = publishedSource.Take(10).ToArray(); var nextTwentyTransformedItems = publishedSource.Take(20).Select(Mutate).ToArray(); // How you apply 'Where' depends on what you want to achieve. // This returns the next 5 items that match the filter but if there are less // than 5 items that match the filter you could end up enumerating the // entire remainder of the sequence. var nextFiveFilteredItems = publishedSource.Where(Filter).Take(5).ToArray(); // This enumerates _only_ the next 5 items and yields any that match the filter. var nextOfFiveItemsThatPassFilter = publishedSource.Take(5).Where(Filter).ToArray() 
+4
source share

All Articles