I have a list (simplified)
[Kind] [Name]
null E
null W
4 T
5 G
6 Q
null L
null V
7 K
2 Z
0 F
I need {E,L} -> Items where their Kind==null and the next Kind==null too
Assume that there is an ID that is increasing and in order.
Is this forward looking possible in Linq?
Like this?
void Main()
{
List<SomeClass> list = new List<SomeClass>() {
new SomeClass() { Kind = null, Name = "E" },
new SomeClass() { Kind = null, Name = "W" },
new SomeClass() { Kind = 4, Name = "T" },
new SomeClass() { Kind = 5, Name = "G" },
...
};
var query = list.Where ((s, i) =>
!s.Kind.HasValue &&
list.ElementAtOrDefault(i + 1) != null &&
!list.ElementAt(i + 1).Kind.HasValue);
}
public class SomeClass
{
public int? Kind { get; set; }
public string Name { get; set; }
}
Edit: Stealing @Jeff Marcado's solution to implement an extension method similar to the above use but a bit cleaner and not making you deal with the index:
public static IEnumerable<TSource> WhereWithLookahead<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, bool> predicate) where TSource : class
{
using(var enumerator = source.GetEnumerator())
{
if (!enumerator.MoveNext())
{
//empty
yield break;
}
var current = enumerator.Current;
while (enumerator.MoveNext())
{
var next = enumerator.Current;
if(predicate(current, next))
{
yield return current;
}
current = next;
}
if (predicate(current, null))
{
yield return current;
}
}
}
// Use:
var query2 = list.WhereWithLookahead((current, next) =>
!current.Kind.HasValue &&
(next != null) &&
next.Kind.HasValue);
For a functional approach, you can implement a lookahead enumerator like so:
IEnumerable<Item> collection = ...;
var lookahead = collection.Zip(collection.Skip(1), Tuple.Create);
The enumerator will iterate through tuples of each item and it's following item. This excludes the last item in the collection. Then it's just a matter of performing the query.
var query = collection.Zip(collection.Skip(1), Tuple.Create)
.Where(tuple => tuple.Item1.Kind == null && tuple.Item2.Kind == null)
.Select(tuple => tuple.Item1);
Unfortunately this will be very inefficient. You're enumerating the length of the collection twice and can be very expensive.
It would be better to write your own enumerator for this so you only go through the collection in one pass:
public static IEnumerable<TResult> LookAhead<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, TSource, TResult> selector)
{
if (source == null) throw new ArugmentNullException("source");
if (selector == null) throw new ArugmentNullException("selector");
using (var enumerator = source.GetEnumerator())
{
if (!enumerator.MoveNext())
{
//empty
yield break;
}
var current = enumerator.Current;
while (enumerator.MoveNext())
{
var next = enumerator.Current;
yield return selector(current, next);
current = next;
}
}
}
Then the query becomes:
var query = collection.LookAhead(Tuple.Create)
.Where(tuple => tuple.Item1.Kind == null && tuple.Item2.Kind == null)
.Select(tuple => tuple.Item1);
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