0

Let's say I have someList of things I want to split into two separate lists by some condition.

I've thought of 3 options:

// 1 - two Where's
var trueGroup = someList.Where(x => x.SomeCondition);
var falseGroup = someList.Where(x => !x.SomeCondition);

// 2 - groupby the condition, then ToDictionary
var groupedByCondition = someList.GroupBy(x => x.SomeCondition)
    .ToDictionary(x => x.Key, x => x.ToList());
var trueGroup = groupedByCondition[true];
var falseGroup = groupedByCondition[false];

// 3 - groupby the condition, then First
var groupedByCondition = someList.GroupBy(x => x.SomeCondition);
var trueGroup = groupedByCondition.First(x => x.Key).ToList();
var falseGroup = groupedByCondition.First(x => !x.Key).ToList();

All 3 definitely work, but I'm looking for the most efficient way to do this. I'm fairly certain option #1 is least efficient since it needs to iterate the full list twice (please correct me if I'm wrong about that), and options #2 + #3 are probably equally efficient (at best one of them might be a micro-optimization).

However, something keeps nagging me that I'm forgetting a very obvious solution. Is there a more efficient/generally accepted way to do this that I'm simply not thinking of?

5
  • 2
    Lookups are incredibly powerful ways of partitioning collections of data. Commented Aug 22 at 16:15
  • 2
    You can use MoreLINQ's Partition to do exactly what you want. From the doc example: var (evens, odds) = Enumerable.Range(0, 10).Partition(x => x % 2 == 0);. Unlike the question's attempts, you get back actual lazily-evaluated IEnumerable<>s that can be used immediately. There's an overload that can be used to return custom results too, so if you really want lists, .Partition(...,(left,right)=>(left.ToList(),right.ToList()) Commented Aug 22 at 16:27
  • @JeffMercado lookups is actually EXACTLY what I was thinking about! I do remember hearing in a few places that there were "considerable performance issues" with lookups, but with logic this simple, is it really much of an issue? Commented Aug 22 at 16:32
  • @CHowell The only "problem" with it as far as I know is that the partitions don't give you random access to its contents. You're meant to enumerate through them. Otherwise, the original source collection is only iterated through once to create the lookups and can be manipulated independently. If this isn't a problem for your use case, then it might be the answer for you. Commented Aug 22 at 17:05
  • The most efficient solution depends on a few things: 1) You shouldn't be using LINQ if you need to optimize runtime performance. Use foreach, or even better, a for loop. 2) Do you rather want to optimize memory usage, because someList is huge? 3) Is someList already materialized? 4) Is a materialized result acceptable or not? 5) What exactly does the following code do with the result? Commented Aug 26 at 9:43

2 Answers 2

3

If all you need is to split the sequence into two lists by a boolean condition, you can do it like below.

List<yourType> trueList = new List<yourType>();
List<yourType> falseList = new List<yourType>();

foreach (var item in someList)
{
    if (item.SomeCondition)
        trueGroup.Add(item);
    else
        falseGroup.Add(item);
}

This way you only iterate the list once, with no extra dictionary or grouping objects created.

For small collections the difference doesn’t matter, but if efficiency is your concern this is the simplest and most performant approach.

Time complexity will be o(n), as you loop through only once

Sign up to request clarification or add additional context in comments.

3 Comments

That's not LINQ at all
I agree but asked for more efficient way of doing it. So I gave efficient answer.
Yes, just iterating old-style is sometimes the best solution. If you need it often, write your own linqish extension method.
1

You can write your own LINQ-like method that will iterate over the collection and return two collections:

public static class ENuemrableExtensions
{
    public static (List<T>, List<T>) SplitIntoTwo<T>(
        this IEnumerable<T> collection,
        Func<T, bool> predicate)
    {
        var truthyValues = new List<T>();
        var falsyValues = new List<T>();
        foreach (var item in collection)
        {
            if (predicate(item))
                truthyValues.Add(item);
            else
                falsyValues.Add(item);
        }
        return (truthyValues, falsyValues);
    }
}

Example usage:

int[] i = [1, 2, 3, 4];
var (greaterThan2, lessThanOrEqual2) = i.SplitIntoTwo(x => x > 2);

3 Comments

OP doesn't want two IEnumerable<T> enumerables, they want two List<T> lists. If you create lists but return them as enumerables, OP will need end up doing ToList() on them, thereby duplicating the lists. And if you do ToArray() there will be three duplicates of each list. Your List<T>, the array returned as an enumerable, and OP's ToList() list.
@dbc Small detail, however corrected for completeness sake.
It is a small detail but OP seems concerned with micro-optimization. Eliminating creation of unnecessary intermediate collections falls into the the micro-optimization category.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.