Consider the following code
IEnumerable<Items> remainingItems = Items
var results = new List<List<Items>>();
var counter = 0;
while (remainingItems.Any())
{
var result = new List<Item>();
result.AddRange(remainingItems.TakeWhile(x => somePredicate(x, counter));
results.Add(result);
remainingItems = remainingItems.Skip(result.Count);
counter++;
}
If it's not clear whats happening, I'm taking an Ienumerable, and iterating through it till a predicate fails, putting all those items into one pile, and then continue iterating through the remaining items till the next predicate fails, and put all of those in a pile. Rinse, Wash, Repeat.
Now the bit I want to focus on here is the Ienumerable.Skip()
Since it uses delayed execution, it means I have to go through all the elements I've already skipped on each loop.
I could use ToList() to force it to evaluate, but then it needs to iterate through all the remaining items to do so, which is just as bad.
So what I really need is an IEnumerable, which does the skipping eagerly, and stores the first last point we were up to, to continue from there. So I need some function like:
IEnumerable.SkipAndCache(n) which allows me to access an IEnumerator starting at the nth item.
Any ideas?
You can use MoreLinq for that. There is an experimental function called Memoize which lazily caches the sequence. So the code will look like this:
while (remainingItems.Any())
{
var result = new List<Item>();
result.AddRange(remainingItems.TakeWhile(x => somePredicate(x, counter));
results.Add(result);
remainingItems = remainingItems.Skip(result.Count).Memoize();
counter++;
}
Here the result will not be materialized because it is still lazy evaluation:
remainingItems = remainingItems.Skip(result.Count).Memoize();
Here the remainingItems sequence will be evaluated and cached (the iterator will not go through all the elements like in ToList):
remainingItems.Any()
And here the cache will be used:
result.AddRange(remainingItems.TakeWhile(x => somePredicate(x, counter));
To use this method you need to add:
using MoreLinq.Experimental;
As we are skipping the result set in series why not use the for loop for the same like
for(int i = 0 ; i < result.Count ; i++){
//do some business logic and now i got X result
i = i + X
}
Yield might be useful, if I'm understanding your question correctly
public static IEnumerable<IEnumerable<T>> Test<T>(IEnumerable<T> source)
{
var items = new List<T>();
foreach (T item in source)
{
items.Add(item);
if (!SomePredicate(item))
{
yield return items;
items = new List<T>();
}
}
// if you want any remaining items to go into their own IEnumerable, even if there's no more fails
if (items.Count > 0)
{
yield return items;
}
}
Just as en example I made my fail condition to be !item % 10 == 0 and passed in values 0 to 1000 to the above method. I get 101 IEnumerables containing 0 in the first, and the rest containing 1 to 10, 11 to 20, etc. etc.
You could write a simple extension method to help with this:
public static IEnumerable<IEnumerable<T>> PartitionBy<T>(this IEnumerable<T> sequence, Func<T, int, bool> predicate)
{
var block = new List<T>();
int index = 0;
foreach (var item in sequence)
{
if (predicate(item, index++))
{
block.Add(item);
}
else if (block.Count > 0)
{
yield return block.ToList(); // Return a copy so the caller can't change our local list.
block.Clear();
}
}
if (block.Count > 0)
yield return block; // No need for a copy since we've finished using our local list.
}
(As an extension method, you need to put that in a static class.)
Then you can use it to partition data like so. For this example, we will partition a list of ints into partitions where the list element's value is equal to its index:
static void Main()
{ // 0 1 2 3 4 5 6 7 8 9
var ints = new List<int> {0, 1, 0, 3, 4, 5, 0, 0, 8, 9};
var result = ints.PartitionBy(((item, index) => item == index)); // Items where value == index.
foreach (var seq in result)
Console.WriteLine(string.Join(", ", seq));
// Output is:
// 0, 1
// 3, 4, 5
// 8, 9
}
Note that this implementation skips over elements that do not match the predicate.
Here's an alternative, more complicated implementation that doesn't make a copy of the data:
class Indexer
{
public int Index;
public bool Finished;
}
public static IEnumerable<IEnumerable<T>> PartitionBy<T>(this IEnumerable<T> sequence, Func<T, int, bool> predicate)
{
var iter = sequence.GetEnumerator();
var indexer = new Indexer();
while (!indexer.Finished)
{
yield return nextBlock(iter, predicate, indexer);
}
}
static IEnumerable<T> nextBlock<T>(IEnumerator<T> iter, Func<T, int, bool> predicate, Indexer indexer)
{
int index = indexer.Index;
bool any = false;
while (true)
{
if (!iter.MoveNext())
{
indexer.Finished = true;
yield break;
}
if (predicate(iter.Current, index++))
{
any = true;
yield return iter.Current;
}
else
{
indexer.Index = index;
if (any)
yield break;
}
}
}
Related
I have those functions
public static IEnumerable<IEnumerable<T>> GetPermutations<T>(IEnumerable<T> items, int count)
{
int i = 0;
foreach (var item in items)
{
if (count == 1)
yield return new T[] { item };
else
{
foreach (var result in GetPermutations(items.Skip(i + 1), count - 1))
yield return new T[] { item }.Concat(result);
}
++i;
}
}
public static List<List<int>> GetAllValidCombinations(List<int> items)
{
var finalList = new List<List<int>>();
switch (items.Count)
{
case 1:
finalList.Add(items);
break;
case 3:
finalList.AddRange(GetPermutations(items, 2));
finalList.AddRange((List<List<int>>)GetPermutations(items, 3));
break;
}
return finalList;
}
and i want to get an List<List> from GetAllValidCombinations.
In the case 3 of GetAllValidCombinationsin the first line im getting:
Error CS1503 Argument 1: cannot convert from 'System.Collections.Generic.IEnumerable<System.Collections.Generic.IEnumerable>' to 'System.Collections.Generic.IEnumerable<System.Collections.Generic.List>'
and if i try the second line im getting error Specified cast is not valid
How i can do this cast in one line?
Sweepers answer is on the money, also you could refactor it a little by only using lists in the inner collection and making it completely generic.
public static IEnumerable<List<T>> GetPermutations<T>(List<T> items, int count)
{
for (var index = 0; index < items.Count; index++)
if (count == 1)
yield return new List<T> { items[index] };
else
foreach (var result in GetPermutations(items.Skip(index + 1).ToList(), count - 1))
yield return new List<T> { items[index] }
.Concat(result)
.ToList();
}
public static IEnumerable<List<T>> GetAllValidCombinations<T>(List<T> items)
{
if (items.Count == 1)
return new List<List<T>> {items};
if (items.Count == 3)
return GetPermutations(items, 2)
.Concat(GetPermutations(items, 3));
return Enumerable.Empty<List<T>>();
}
Usage
var results = GetAllValidCombinations(new List<int>() {1, 2, 3});
foreach (var result in results)
Console.WriteLine(string.Join(",",result));
Output
1,2
1,3
2,3
1,2,3
Full Demo Here
AddRange expects an IEnumerable of Lists, but you have given it an IEnumerable of IEnumerables. Those IEnumerable could be anything, not necessarily lists, right? They could be sets or arrays or some other type that I wrote, that just happens to implement IEnumerable<T>... This is the reason why the compiler gives you the error.
And as you have written GetPermutations, we can see that they are actually arrays of T! So you are trying to add a bunch of arrays to a list of lists! That doesn't make much sense, does it?
Fortunately, ToList converts any IEnumerable to a List. You should apply this method to each IEnumerable nested inside the outer IEnumerable using Select:
var enumerableOfEnumerables = GetPermutations(items, 2);
finalList.AddRange(enumerableOfEnumerables.Select(x => x.ToList()));
I would like to verify if the sum of the elements (which are non-negativ) of my list isinferior to some values. And I don't want to calculate the the whole sum it is not necessary.(if we prove that the sum of the first element don't respect the property, we stop the computation)
So I would like a LINQ command that verify each element of the cummulative sum is inferior to some value as long as it see that the ineqality hold.
var b = a.Aggregate(new List<int> { 0 }, (ls, x) => { ls.Add(x + ls.Last()); return ls; }).All(x => x < 4);
This method doesn't work. All stop when it see that the ith element of the cummulative sum doesn't safisty the property but the whole cummulative sum is compute.
Have you a better way to do that? (I know we can do that efficiently with loop but I want to do that with LINQ)
if I use a loop:
var s = 0;
var b = true;
foreach(var x in list)
{
s=s+x;
if(s>4){ b= false; break;}
}
Thank you
You don't need to use a LINQ method to do what you want. You can write your own using enumerators and loops. After all, LINQ-to-Objects operations themselves are implemented using loops. For example TakeWhile is implemented as an iterator that loops over the source and yields matching elements :
static IEnumerable<TSource> TakeWhileIterator<TSource>(IEnumerable<TSource> source, Func<TSource, int, bool> predicate) {
int index = -1;
foreach (TSource element in source) {
checked { index++; }
if (!predicate(element, index)) break;
yield return element;
}
}
The downside is that this generates a state machine for the iterator and returns all matching elements, whether they are used or not.
You can write your own extension method that calculates the sum in a loop and returns true if the loop completes without reaching the limit :
public static bool SumBelow(this IEnumerable<int> source, int limit)
{
int sum=0;
foreach (var element in source)
{
sum+=element;
if (sum>limit)
{
return false;
}
}
return true;
}
And use it as an extension method :
var isSumBelow = someEnumerable.SumBelow(5);
Why not a generic method ?
There's no way to specify an operator constraint or an IAddable interface, which is why Sum() itself is implemented for each type separately, eg :
public static int Sum(this IEnumerable<int> source) {
if (source == null) throw Error.ArgumentNull("source");
int sum = 0;
checked {
foreach (int v in source) sum += v;
}
return sum;
}
The functional way
Passing the accumulator and condition checker as functions can be used to create one generic, reusable method that can work with any transormation and condition :
public static bool AccWithinLimit<T>(
this IEnumerable<T> source,
Func<T,T,T> accumulator,
Func<T,bool> terminator,
T seed=default)
{
T total=seed;
foreach (var element in source)
{
total = accumulator(element,total);
if (terminator(total))
{
return false;
}
}
return true;
}
This can be used to check for partial sums with integer arrays :
var myArray=new []{1,2,3};
var limit = 5;
var totalBelowLimit = myArray.AccWithinLimit(myArray,
(sum,elm)=>sum+elm,
sum=>sum>limit);
Or partial products with a list of doubles:
var myList = new List<double>{1.0, 2.0, 3.0};
var limit = 10;
var totalBelowLimit = myList.AccWithinLimit(myArray,
(sum,elm)=>sum*elm,
sum=>sum>limit,
1);
You can use TakeWhile to take items from the list until the sum exeeds some value
public void TestTakeWhileCumulativeSum()
{
int[] numbers = new[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
int maxCumulativeSum = 5;
int previous = 0;
var result = numbers.TakeWhile(n => (previous = n + previous) <= maxCumulativeSum);
Assert.AreEqual(result.Count(), 5);
}
I'm trying to use Linq to convert IEnumerable<int> to IEnumerable<List<int>> - the input stream will be separated by special value 0.
IEnumerable<List<int>> Parse(IEnumerable<int> l)
{
l.Select(x => {
.....; //?
return new List<int>();
});
}
var l = new List<int> {0,1,3,5,0,3,4,0,1,4,0};
Parse(l) // returns {{1,3,5}, {3, 4}, {1,4}}
How to implement it using Linq instead of imperative looping?
Or is Linq not good for this requirement because the logic depends on the order of the input stream?
Simple loop would be good option.
Alternatives:
Enumerable.Aggregate and start new list on 0
Write own extension similar to Create batches in linq or Use LINQ to group a sequence of numbers with no gaps
Aggregate sample
var result = list.Aggregate(new List<List<int>>(),
(sum,current) => {
if(current == 0)
sum.Add(new List<int>());
else
sum.Last().Add(current);
return sum;
});
Note: this is only sample of the approach working for given very friendly input like {0,1,2,0,3,4}.
One can even make aggregation into immutable lists but that will look insane with basic .Net types.
Here's an answer that lazily enumerates the source enumerable, but eagerly enumerates the contents of each returned list between zeroes. It properly throws upon null input or upon being given a list that does not start with a zero (though allowing an empty list through--that's really an implementation detail you have to decide on). It does not return an extra and empty list at the end like at least one other answer's possible suggestions does.
public static IEnumerable<List<int>> Parse(this IEnumerable<int> source, int splitValue = 0) {
if (source == null) {
throw new ArgumentNullException(nameof (source));
}
using (var enumerator = source.GetEnumerator()) {
if (!enumerator.MoveNext()) {
return Enumerable.Empty<List<int>>();
}
if (enumerator.Current != splitValue) {
throw new ArgumentException(nameof (source), $"Source enumerable must begin with a {splitValue}.");
}
return ParseImpl(enumerator, splitValue);
}
}
private static IEnumerable<List<int>> ParseImpl(IEnumerator<int> enumerator, int splitValue) {
var list = new List<int>();
while (enumerator.MoveNext()) {
if (enumerator.Current == splitValue) {
yield return list;
list = new List<int>();
}
else {
list.Add(enumerator.Current);
}
}
if (list.Any()) {
yield return list;
}
}
This could easily be adapted to be generic instead of int, just change Parse to Parse<T>, change int to T everywhere, and use a.Equals(b) or !a.Equals(b) instead of a == b or a != b.
You could create an extension method like this:
public static IEnumerable<IEnumerable<T>> SplitBy<T>(this IEnumerable<T> source, T value)
{
using (var e = source.GetEnumerator())
{
if (e.MoveNext())
{
var list = new List<T> { };
//In case the source doesn't start with 0
if (!e.Current.Equals(value))
{
list.Add(e.Current);
}
while (e.MoveNext())
{
if ( !e.Current.Equals(value))
{
list.Add(e.Current);
}
else
{
yield return list;
list = new List<T> { };
}
}
//In case the source doesn't end with 0
if (list.Count>0)
{
yield return list;
}
}
}
}
Then, you can do the following:
var l = new List<int> { 0, 1, 3, 5, 0, 3, 4, 0, 1, 4, 0 };
var result = l.SplitBy(0);
You could use GroupBy with a counter.
var list = new List<int> {0,1,3,5,0,3,4,0,1,4,0};
int counter = 0;
var result = list.GroupBy(x => x==0 ? counter++ : counter)
.Select(g => g.TakeWhile(x => x!=0).ToList())
.Where(l => l.Any());
Edited to fix possibility of zeroes within numbers
Here is a semi-LINQ solution:
var l = new List<int> {0,1,3,5,0,3,4,0,1,4,0};
string
.Join(",", l.Select(x => x == 0 ? "|" : x.ToString()))
.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries));
This is probably not preferable to using a loop due to performance and other reasons, but it should work.
This question already has answers here:
Create batches in LINQ
(21 answers)
Split List into Sublists with LINQ
(34 answers)
Closed 5 years ago.
I want to take an IEnumerable<T> and split it up into fixed-sized chunks.
I have this, but it seems inelegant due to all the list creation/copying:
private static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, int partitionSize)
{
List<T> partition = new List<T>(partitionSize);
foreach (T item in items)
{
partition.Add(item);
if (partition.Count == partitionSize)
{
yield return partition;
partition = new List<T>(partitionSize);
}
}
// Cope with items.Count % partitionSize != 0
if (partition.Count > 0) yield return partition;
}
Is there something more idiomatic?
EDIT: Although this has been marked as a duplicate of Divide array into an array of subsequence array it is not - that question deals with splitting an array, whereas this is about IEnumerable<T>. In addition that question requires that the last subsequence is padded. The two questions are closely related but aren't the same.
You could try to implement Batch method mentioned above on your own like this:
static class MyLinqExtensions
{
public static IEnumerable<IEnumerable<T>> Batch<T>(
this IEnumerable<T> source, int batchSize)
{
using (var enumerator = source.GetEnumerator())
while (enumerator.MoveNext())
yield return YieldBatchElements(enumerator, batchSize - 1);
}
private static IEnumerable<T> YieldBatchElements<T>(
IEnumerator<T> source, int batchSize)
{
yield return source.Current;
for (int i = 0; i < batchSize && source.MoveNext(); i++)
yield return source.Current;
}
}
I've grabbed this code from http://blogs.msdn.com/b/pfxteam/archive/2012/11/16/plinq-and-int32-maxvalue.aspx.
UPDATE: Please note, that this implementation not only lazily evaluates batches but also items inside batches, which means it will only produce correct results when batch is enumerated only after all previous batches were enumerated. For example:
public static void Main(string[] args)
{
var xs = Enumerable.Range(1, 20);
Print(xs.Batch(5).Skip(1)); // should skip first batch with 5 elements
}
public static void Print<T>(IEnumerable<IEnumerable<T>> batches)
{
foreach (var batch in batches)
{
Console.WriteLine($"[{string.Join(", ", batch)}]");
}
}
will output:
[2, 3, 4, 5, 6] //only first element is skipped.
[7, 8, 9, 10, 11]
[12, 13, 14, 15, 16]
[17, 18, 19, 20]
So, if you use case assumes batching when batches are sequentially evaluated, then lazy solution above will work, otherwise if you can't guarantee strictly sequential batch processing (e.g. when you want to process batches in parallel), you will probably need a solution which eagerly enumerates batch content, similar to one mentioned in the question above or in the MoreLINQ
It feels like you want two iterator blocks ("yield return methods"). I wrote this extension method:
static class Extensions
{
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, int partitionSize)
{
return new PartitionHelper<T>(items, partitionSize);
}
private sealed class PartitionHelper<T> : IEnumerable<IEnumerable<T>>
{
readonly IEnumerable<T> items;
readonly int partitionSize;
bool hasMoreItems;
internal PartitionHelper(IEnumerable<T> i, int ps)
{
items = i;
partitionSize = ps;
}
public IEnumerator<IEnumerable<T>> GetEnumerator()
{
using (var enumerator = items.GetEnumerator())
{
hasMoreItems = enumerator.MoveNext();
while (hasMoreItems)
yield return GetNextBatch(enumerator).ToList();
}
}
IEnumerable<T> GetNextBatch(IEnumerator<T> enumerator)
{
for (int i = 0; i < partitionSize; ++i)
{
yield return enumerator.Current;
hasMoreItems = enumerator.MoveNext();
if (!hasMoreItems)
yield break;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
Maybe?
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, int partitionSize)
{
return items.Select((item, inx) => new { item, inx })
.GroupBy(x => x.inx / partitionSize)
.Select(g => g.Select(x => x.item));
}
There is an already implemented one too: morelinq's Batch.
Craziest solution (with Reactive Extensions):
public static IEnumerable<IList<T>> Partition<T>(this IEnumerable<T> items, int partitionSize)
{
return items
.ToObservable() // Converting sequence to observable sequence
.Buffer(partitionSize) // Splitting it on spececified "partitions"
.ToEnumerable(); // Converting it back to ordinary sequence
}
I know that I changed signature but anyway we all know that we'll have some fixed size collection as a chunk.
BTW if you will use iterator block do not forget to split your implementation into two methods to validate arguments eagerly!
For elegant solution, You can also have a look at MoreLinq.Batch.
It batches the source sequence into sized buckets.
Example:
int[] ints = new int[] {1,2,3,4,5,6};
var batches = ints.Batch(2); // batches -> [0] : 1,2 ; [1]:3,4 ; [2] :5,6
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items,
int partitionSize)
{
int i = 0;
return items.GroupBy(x => i++ / partitionSize).ToArray();
}
How about the partitioner classes in the System.Collections.Concurrent namespace?
You can do this using an overload of Enumerable.GroupBy and taking advantage of integer division.
return items.Select((element, index) => new { Element = element, Index = index })
.GroupBy(obj => obj.Index / partitionSize, (_, partition) => partition);
I have an IEnumerable of a custom type. (That I've gotten from a SelectMany)
I also have an item (myItem) in that IEnumerable that I desire the previous and next item from the IEnumerable.
Currently, I'm doing the desired like this:
var previousItem = myIEnumerable.Reverse().SkipWhile(
i => i.UniqueObjectID != myItem.UniqueObjectID).Skip(1).FirstOrDefault();
I can get the next item by simply ommitting the .Reverse.
or, I could:
int index = myIEnumerable.ToList().FindIndex(
i => i.UniqueObjectID == myItem.UniqueObjectID)
and then use .ElementAt(index +/- 1) to get the previous or next item.
Which is better between the two options?
Is there an even better option available?
"Better" includes a combination of performance (memory and speed) and readability; with readability being my primary concern.
First off
"Better" includes a combination of performance (memory and speed)
In general you can't have both, the rule of thumb is, if you optimise for speed, it'll cost memory, if you optimise for memory, it'll cost you speed.
There is a better option, that performs well on both memory and speed fronts, and can be used in a readable manner (I'm not delighted with the function name, however, FindItemReturningPreviousItemFoundItemAndNextItem is a bit of a mouthful).
So, it looks like it's time for a custom find extension method, something like . . .
public static IEnumerable<T> FindSandwichedItem<T>(this IEnumerable<T> items, Predicate<T> matchFilling)
{
if (items == null)
throw new ArgumentNullException("items");
if (matchFilling == null)
throw new ArgumentNullException("matchFilling");
return FindSandwichedItemImpl(items, matchFilling);
}
private static IEnumerable<T> FindSandwichedItemImpl<T>(IEnumerable<T> items, Predicate<T> matchFilling)
{
using(var iter = items.GetEnumerator())
{
T previous = default(T);
while(iter.MoveNext())
{
if(matchFilling(iter.Current))
{
yield return previous;
yield return iter.Current;
if (iter.MoveNext())
yield return iter.Current;
else
yield return default(T);
yield break;
}
previous = iter.Current;
}
}
// If we get here nothing has been found so return three default values
yield return default(T); // Previous
yield return default(T); // Current
yield return default(T); // Next
}
You can cache the result of this to a list if you need to refer to the items more than once, but it returns the found item, preceded by the previous item, followed by the following item. e.g.
var sandwichedItems = myIEnumerable.FindSandwichedItem(item => item.objectId == "MyObjectId").ToList();
var previousItem = sandwichedItems[0];
var myItem = sandwichedItems[1];
var nextItem = sandwichedItems[2];
The defaults to return if it's the first or last item may need to change depending on your requirements.
Hope this helps.
For readability, I'd load the IEnumerable into a linked list:
var e = Enumerable.Range(0,100);
var itemIKnow = 50;
var linkedList = new LinkedList<int>(e);
var listNode = linkedList.Find(itemIKnow);
var next = listNode.Next.Value; //probably a good idea to check for null
var prev = listNode.Previous.Value; //ditto
By creating an extension method for establishing context to the current element you can use a Linq query like this:
var result = myIEnumerable.WithContext()
.Single(i => i.Current.UniqueObjectID == myItem.UniqueObjectID);
var previous = result.Previous;
var next = result.Next;
The extension would be something like this:
public class ElementWithContext<T>
{
public T Previous { get; private set; }
public T Next { get; private set; }
public T Current { get; private set; }
public ElementWithContext(T current, T previous, T next)
{
Current = current;
Previous = previous;
Next = next;
}
}
public static class LinqExtensions
{
public static IEnumerable<ElementWithContext<T>>
WithContext<T>(this IEnumerable<T> source)
{
T previous = default(T);
T current = source.FirstOrDefault();
foreach (T next in source.Union(new[] { default(T) }).Skip(1))
{
yield return new ElementWithContext<T>(current, previous, next);
previous = current;
current = next;
}
}
}
You could cache the enumerable in a list
var myList = myIEnumerable.ToList()
iterate over it by index
for (int i = 0; i < myList.Count; i++)
then the current element is myList[i], the previous element is myList[i-1], and the next element is myList[i+1]
(Don't forget about the special cases of the first and last elements in the list.)
You are really over complicating things:
Sometimes just a for loop is going to be better to do something, and I think provide a clearer implementation of what you are trying to do/
var myList = myIEnumerable.ToList();
for(i = 0; i < myList.Length; i++)
{
if(myList[i].UniqueObjectID == myItem.UniqueObjectID)
{
previousItem = myList[(i - 1) % (myList.Length - 1)];
nextItem = myList[(i + 1) % (myList.Length - 1)];
}
}
Here is a LINQ extension method that returns the current item, along with the previous and the next. It yields ValueTuple<T, T, T> values to avoid allocations. The source is enumerated once.
/// <summary>
/// Projects each element of a sequence into a tuple that includes the previous
/// and the next element.
/// </summary>
public static IEnumerable<(T Previous, T Current, T Next)> WithPreviousAndNext<T>(
this IEnumerable<T> source, T firstPrevious = default, T lastNext = default)
{
ArgumentNullException.ThrowIfNull(source);
(T Previous, T Current, bool HasPrevious) queue = (default, firstPrevious, false);
foreach (var item in source)
{
if (queue.HasPrevious)
yield return (queue.Previous, queue.Current, item);
queue = (queue.Current, item, true);
}
if (queue.HasPrevious)
yield return (queue.Previous, queue.Current, lastNext);
}
Usage example:
var source = Enumerable.Range(1, 5);
Console.WriteLine($"Source: {String.Join(", ", source)}");
var result = source.WithPreviousAndNext(firstPrevious: -1, lastNext: -1);
Console.WriteLine($"Result: {String.Join(", ", result)}");
Output:
Source: 1, 2, 3, 4, 5
Result: (-1, 1, 2), (1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, -1)
To get the previous and the next of a specific item, you could use tuple deconstruction:
var (previous, current, next) = myIEnumerable
.WithPreviousAndNext()
.First(e => e.Current.UniqueObjectID == myItem.UniqueObjectID);
CPU
Depends entirely on where the object is in the sequence. If it is located at the end I would expect the second to be faster with more than a factor 2 (but only a constant factor). If it is located in the beginning the first will be faster because you don't traverse the whole list.
Memory
The first is iterating the sequence without saving the sequence so the memory hit will be very small. The second solution will take as much memory as the length of the list * references + objects + overhead.
I thought I would try to answer this using Zip from Linq.
string[] items = {"nought","one","two","three","four"};
var item = items[2];
var sandwiched =
items
.Zip( items.Skip(1), (previous,current) => new { previous, current } )
.Zip( items.Skip(2), (pair,next) => new { pair.previous, pair.current, next } )
.FirstOrDefault( triplet => triplet.current == item );
This will return a anonymous type {previous,current,next}.
Unfortunately this will only work for indexes 1,2 and 3.
string[] items = {"nought","one","two","three","four"};
var item = items[4];
var pad1 = Enumerable.Repeat( "", 1 );
var pad2 = Enumerable.Repeat( "", 2 );
var padded = pad1.Concat( items );
var next1 = items.Concat( pad1 );
var next2 = items.Skip(1).Concat( pad2 );
var sandwiched =
padded
.Zip( next1, (previous,current) => new { previous, current } )
.Zip( next2, (pair,next) => new { pair.previous, pair.current, next } )
.FirstOrDefault( triplet => triplet.current == item );
This version will work for all indexes.
Both version use lazy evaluation courtesy of Linq.
Here are some extension methods as promised. The names are generic and reusable with any type simple and there are lookup overloads to get at the item needed to get the next or previous items. I would benchmark the solutions and then see where you could squeeze cycles out.
public static class ExtensionMethods
{
public static T Previous<T>(this List<T> list, T item) {
var index = list.IndexOf(item) - 1;
return index > -1 ? list[index] : default(T);
}
public static T Next<T>(this List<T> list, T item) {
var index = list.IndexOf(item) + 1;
return index < list.Count() ? list[index] : default(T);
}
public static T Previous<T>(this List<T> list, Func<T, Boolean> lookup) {
var item = list.SingleOrDefault(lookup);
var index = list.IndexOf(item) - 1;
return index > -1 ? list[index] : default(T);
}
public static T Next<T>(this List<T> list, Func<T,Boolean> lookup) {
var item = list.SingleOrDefault(lookup);
var index = list.IndexOf(item) + 1;
return index < list.Count() ? list[index] : default(T);
}
public static T PreviousOrFirst<T>(this List<T> list, T item) {
if(list.Count() < 1)
throw new Exception("No array items!");
var previous = list.Previous(item);
return previous == null ? list.First() : previous;
}
public static T NextOrLast<T>(this List<T> list, T item) {
if(list.Count() < 1)
throw new Exception("No array items!");
var next = list.Next(item);
return next == null ? list.Last() : next;
}
public static T PreviousOrFirst<T>(this List<T> list, Func<T,Boolean> lookup) {
if(list.Count() < 1)
throw new Exception("No array items!");
var previous = list.Previous(lookup);
return previous == null ? list.First() : previous;
}
public static T NextOrLast<T>(this List<T> list, Func<T,Boolean> lookup) {
if(list.Count() < 1)
throw new Exception("No array items!");
var next = list.Next(lookup);
return next == null ? list.Last() : next;
}
}
And you can use them like this.
var previous = list.Previous(obj);
var next = list.Next(obj);
var previousWithLookup = list.Previous((o) => o.LookupProperty == otherObj.LookupProperty);
var nextWithLookup = list.Next((o) => o.LookupProperty == otherObj.LookupProperty);
var previousOrFirst = list.PreviousOrFirst(obj);
var nextOrLast = list.NextOrLast(ob);
var previousOrFirstWithLookup = list.PreviousOrFirst((o) => o.LookupProperty == otherObj.LookupProperty);
var nextOrLastWithLookup = list.NextOrLast((o) => o.LookupProperty == otherObj.LookupProperty);
I use the following technique:
var items = new[] { "Bob", "Jon", "Zac" };
var sandwiches = items
.Sandwich()
.ToList();
Which produces this result:
Notice that there are nulls for the first Previous value, and the last Next value.
It uses the following extension method:
public static IEnumerable<(T Previous, T Current, T Next)> Sandwich<T>(this IEnumerable<T> source, T beforeFirst = default, T afterLast = default)
{
var sourceList = source.ToList();
T previous = beforeFirst;
T current = sourceList.FirstOrDefault();
foreach (var next in sourceList.Skip(1))
{
yield return (previous, current, next);
previous = current;
current = next;
}
yield return (previous, current, afterLast);
}
If you need it for every element in myIEnumerable I’d just iterate through it keeping references to the 2 previous elements. In the body of the loop I'd do the processing for the second previous element and the current would be its descendant and first previous its ancestor.
If you need it for only one element I'd choose your first approach.