Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a better way to get sub-sequences where each item matches a predicate?

Tags:

c#

linq

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.

like image 504
Ria Avatar asked Feb 28 '23 23:02

Ria


1 Answers

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.

like image 159
Garry Shutler Avatar answered Mar 09 '23 00:03

Garry Shutler