Take all items except the last ones that satisfy the condition?

My specific requirement is that I have IEnumerable<IEnumerable<string>>one and I want to "take" all the elements in the outer enumeration, except for any "empty" end elements, where "empty" means that all lines are null / empty or the inner enumeration is empty. Please note that I want to keep any empty elements that appear until the last non-empty. For instance:

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 elements 1-3, but trim elements 4 and 5.

In a more general sense, I have an enumeration of elements where I want to trim any finite elements that satisfy the condition that appear after the last element that does not satisfy the condition.

To choose a suitable solution, I can add that an external enumeration will usually contain from several hundred to several hundred thousand elements, while internal enumerations will contain only a few elements. There will probably be only a few empty elements that I need to crop.

My current solution puts all external elements in a list (after converting them with .Select(...)), and then continue to delete the last element in the loop if it is empty until a non-empty element is found.

+4
source share
2 answers

There is no standard effective LINQ solution. I would go with a special LINQ like method:

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;
            }
        }
    }
}

, , LINQ Reverse .

:

var result = input.SkipLastWhile(e => !e.Any());
+4

?

var trimmedItems = items.Reverse().SkipWhile(e => !e.Any()).Reverse();

, , , , .

juharr , :

var trimmedItems = items.Take(items.Reverse().TakeWhile(e => !e.Any()).Count());

, . LINQPad, result.Dump(); -, . , IEnumerable<string> IEnumerable<IEnumerable<string>> , :

/* This is a benchmarking template I use in LINQPad when I want to do a
 * quick performance test. Just give it a couple of actions to test and
 * it will give you a pretty good idea of how long they take compared
 * to one another. It not perfect: You can expect a 3% error margin
 * under ideal circumstances. But if you're not going to improve
 * performance by more than 3%, you probably don't care anyway.*/
void Main()
{
    // Enter setup code here
    var items = new[] { "a, b, c",
    "",
    "a, f, g, h",
    "",
    ""}.AsEnumerable();
    var manyitems = Enumerable.Range(1, 10000).SelectMany(i => items);

    var actions = new[]
    {
        new TimedAction("Control", () =>
        {
            // ToList() is the one thing that all of these have to do.
            manyitems.ToList();
        }),
        new TimedAction("Reverse().SkipWhile().Reverse()", () =>
        {
            manyitems.Reverse().SkipWhile(e => !e.Any()).Reverse().ToList();
        }),
        new TimedAction("Take(Reverse().TakeWhile().Count())", () =>
        {
            manyitems.Take(manyitems.Reverse().TakeWhile(e => !e.Any()).Count()).ToList();
        }),
        new TimedAction("SkipLastWhile", () =>
        {
            manyitems.SkipLastWhile(e => !e.Any()).ToList();
        }),
        // Add tests as desired
    };
    const int TimesToRun = 100; // Tweak this as necessary
    TimeActions(TimesToRun, actions);
}

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
            {
                foreach (var skipped in skipBuffer)
                    yield return skipped;
                skipBuffer.Clear();
                yield return item;
            }
        }
    }
}

#region timer helper methods
// Define other methods and classes here
public void TimeActions(int iterations, params TimedAction[] actions)
{
    Stopwatch s = new Stopwatch();
    int length = actions.Length;
    var results = new ActionResult[actions.Length];
    // Perform the actions in their initial order.
    for (int i = 0; i < length; i++)
    {
        var action = actions[i];
        var result = results[i] = new ActionResult { Message = action.Message };
        // Do a dry run to get things ramped up/cached
        result.DryRun1 = s.Time(action.Action, 10);
        result.FullRun1 = s.Time(action.Action, iterations);
    }
    // Perform the actions in reverse order.
    for (int i = length - 1; i >= 0; i--)
    {
        var action = actions[i];
        var result = results[i];
        // Do a dry run to get things ramped up/cached
        result.DryRun2 = s.Time(action.Action, 10);
        result.FullRun2 = s.Time(action.Action, iterations);
    }
    results.Dump();
}

public class ActionResult
{
    public string Message { get; set; }
    public double DryRun1 { get; set; }
    public double DryRun2 { get; set; }
    public double FullRun1 { get; set; }
    public double FullRun2 { get; set; }
}

public class TimedAction
{
    public TimedAction(string message, Action action)
    {
        Message = message;
        Action = action;
    }
    public string Message { get; private set; }
    public Action Action { get; private set; }
}

public static class StopwatchExtensions
{
    public static double Time(this Stopwatch sw, Action action, int iterations)
    {
        sw.Restart();
        for (int i = 0; i < iterations; i++)
        {
            action();
        }
        sw.Stop();

        return sw.Elapsed.TotalMilliseconds;
    }
}
#endregion

:

Test results

, IEnumerable , LINQ Reverse():

var manyitems = Enumerable.Range(1, 10000).SelectMany(i => items).ToList().AsEnumerable();

BUm9g.png

+2

Source: https://habr.com/ru/post/1673630/


All Articles