How can I extract an IEnumerable part based on known collection items?

I have a collection, in particular IList<T> . I know two elements in the collection, startElement and endElement .

Is there a LINQ query that will return an enumerated value from startElement to endElement , inclusive?

I was thinking about using sequence.SkipWhile(p=>p!=startElement).TakeWhile(q=>q!=endElement) , but this skips the last element ...

+5
source share
5 answers

The best I can think of:

 var subSection = TestData.SkipWhile(p => p != startElement).ToList(); var result = subSection.Take(subSection.IndexOf(endElement) + 1); 
+1
source

George wrote a more flexible extension, you can find it here: fooobar.com/questions/1228631 / ...

Old version:

 public static class MyExtensions { public static IEnumerable <TData> InBetween <TData> (this IEnumerable <TData> Target, TData StartItem, TData EndItem) { var Comparer = EqualityComparer <TData>.Default; var FetchData = false; var StopIt = false; foreach (var Item in Target) { if (StopIt) break; if (Comparer.Equals (Item, StartItem)) FetchData = true; if (Comparer.Equals (Item, EndItem)) StopIt = true; if (FetchData) yield return Item; } yield break; } } 

So now you can use it as follows:

 sequence.InBetween (startElement, endElement); 

And this will not lead to a repetition of the whole sequence. Keep in mind that there are many extensions read here http://linqlib.codeplex.com/

+1
source

This does not use LINQ, but it is probably the easiest and most understandable approach.

  int startIndex = sequence.IndexOf(startElement), endIndex = sequence.IndexOf(endElement); var range = sequence.GetRange( startIndex, // +1 to account for zero-based indexing 1 + endIndex - startIndex ); 

Note that this is technically less efficient than alternatives, but if you already have an IList in mind, the differences are likely to be less than a millisecond, which is a small sacrifice for reading code.

I would recommend wrapping the code block with a stopwatch, however, to test your specific situation.

+1
source

This will be most effective since it does not create unnecessary enumerator objects and only intercepts the list once.

 var result = new List<T>(); var inSequence = false; for (var i = 0; i < list.Length; i++) { var current = list[i]; if (current == startElement) inSequence = true; if (!inSequence) continue; result.add(current); if (current == endElement) break; } 

This will not handle the case where endElement missing, but you can do it quite easily by setting result = null as the last line of the for loop, where i = list.Length - 1

+1
source

I assume that you do not want to use additional memory and do not want to exceed the algorithmic complexity of the basic iteration method, therefore ToList, GroupBy, IndexOf are not described in my proposed implementations.

In addition, in order not to place restrictions on the type of element, I use predicates.

  public static class EnumerableExtensions { /// <summary> /// This one works using existing linq methods. /// </summary> public static IEnumerable<T> GetRange<T>(this IEnumerable<T> source, Func<T, bool> isStart, Func<T, bool> isStop) { var provideExtraItem = new[] { true, false }; return source .SkipWhile(i => !isStart(i)) .SelectMany(i => provideExtraItem, (item, useThisOne) => new {item, useThisOne }) .TakeWhile(i => i.useThisOne || !isStop(i.item)) .Where(i => i.useThisOne) .Select(i => i.item); } /// <summary> /// This one is probably a bit faster. /// </summary> public static IEnumerable<T> GetRangeUsingIterator<T>(this IEnumerable<T> source, Func<T, bool> isStart, Func<T, bool> isStop) { using (var iterator = source.GetEnumerator()) { while (iterator.MoveNext()) { if (isStart(iterator.Current)) { yield return iterator.Current; break; } } while (iterator.MoveNext()) { yield return iterator.Current; if (isStop(iterator.Current)) break; } } } } 

These methods can be used as extension methods:

 new[]{"apple", "orange", "banana", "pineapple"}.GetRange(i => i == "orange", i => i == "banana") 
+1
source

All Articles