Say I have an IEnumerable. For example, {2,1,42,0,9,6,5,3,8}.
I need to get "runs" of items that match a predicate. For example, if my predicate was
bool isSmallerThanSix(int number){...}
I would want to get the following output: {{2,1},{0},{5,3}}
Is there a built-in function that accomplishes this?
So far I have this:
public static IEnumerable<IEnumerable<T>> GetSequences<T>(this IEnumerable<T> source,
Func<T, bool> selector) {
if (source == null || selector == null) {
yield break;
}
IEnumerable<T> rest = source.SkipWhile(obj => !selector(obj));
while (rest.Count() > 0) {
yield return rest.TakeWhile(obj => selector(obj));
rest = rest
.SkipWhile(obj => selector(obj))
.SkipWhile(obj => !selector(obj));
}
}
which seems to work, but was written by me in the middle of the night and thus inefficient fifteen ways from Tuesday. Is there a better, preferably built-in (and therefore well-tested) way?
Thank y'all so much for your time,
Ria.
There is not a build in method as far as I'm aware. However, calling the Count
extension method on an IEnumerable
isn't very efficient as it has to enumerate the list to get the count. Therefore, I've come up with this that has the same effect.
public static IEnumerable<IEnumerable<T>>
GetSequences<T>(this IEnumerable<T> source, Func<T, bool> selector)
{
// omitted null checks for brevity
var list = new List<T>();
foreach(var item in source)
{
if (selector.Invoke(item))
{
list.Add(item);
}
else if (list.Count > 0)
{
yield return list;
list = new List<T>();
}
}
if (list.Count > 0)
yield return list;
}
As Jon Skeet mentioned, the use of SkipWhile
and TakeWhile
also seem pretty inefficient in this case as they will create iterator upon iterator upon iterator. You can check this out as when debugging your example it goes a bit crazy as you step through it trying to find the next sequence and so on even though the example is simple.
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