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.
"Better" includes a combination of performance (memory and speed) and readability; with readability being my primary concern.
All LINQ methods are extension methods to the IEnumerable<T> interface. That means that you can call any LINQ method on any object that implements IEnumerable<T> . You can even create your own classes that implement IEnumerable<T> , and those classes will instantly "inherit" all LINQ functionality!
While the LINQ methods always return a new collection, they don't create a new set of objects: Both the input collection (customers, in my example) and the output collection (validCustomers, in my previous example) are just sets of pointers to the same objects.
LINQ queries return a lazily evaluated IEnumerable<T> . The query is performed upon enumeration. Even if your source IEnumerable<T> had millions of records the above query would be instantaneous. Edit: Think of LINQ queries as creating a pipeline for your result rather than imperatively creating the result.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With