Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Take all items except the last ones that satisfy condition?

My specific requirement is that I have an IEnumerable<IEnumerable<string>>, and I want to "take" all items in the outer enumeration except any "empty" trailing items, where "empty" means all strings are null/empty or the inner enumeration is empty. Note that I want to keep any empty items that appear before the last non-empty one. For example:

Item 1: a, b, c
Item 2: (nothing)
Item 3: a, f, g, h
Item 4: (nothing)
Item 5: (nothing)

I would like to keep items 1–3 but trim items 4 and 5.

In a more general sense, I have an enumeration of items, where I want to trim any trailing items that satisfy a condition, that appear behind the last item that does not satisfy the condition.

For the sake of choosing an appropriate solution, I might add that the outer enumeration will usually contain a few hundred up to a few hundred thousand items, while the inner enumerations contain only a few items each. There will probably be only a couple of empty items that I need to trim.

My current solution puts all outer items in a list (after transforming them with .Select(...)), and then in a loop keep removing the last item if it's empty, until a non-empty item is found.

like image 295
Kjell Rilbe Avatar asked Mar 09 '23 11:03

Kjell Rilbe


1 Answers

There is no standard efficient LINQ solution. I would go with a custom extension "LINQ like" method like this:

public static class EnumerableExtensions
{
    public static IEnumerable<T> SkipLastWhile<T>(this IEnumerable<T> source, Func<T, bool> predicate)
    {
        var skipBuffer = new List<T>();
        foreach (var item in source)
        {
            if (predicate(item))
                skipBuffer.Add(item);
            else
            {
                if (skipBuffer.Count > 0)
                {
                    foreach (var skipped in skipBuffer)
                        yield return skipped;
                    skipBuffer.Clear();
                }
                yield return item;
            }
        }
    }
}

It requires additional space for buffering the longest item sequence satisfying the skip predicate while the LINQ Reverse method has to buffer the whole input sequence.

The usage will be:

var result = input.SkipLastWhile(e => !e.Any());
like image 197
Ivan Stoev Avatar answered Mar 23 '23 14:03

Ivan Stoev