Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does LINQ natively support splitting a collection in two? [duplicate]

Tags:

c#

linq

Given a collection of items, how do I split the collection into 2 sub-collections based on a predicate?

You could do 2 Where searches, but then the run time is 2*N (which, while still O(n), takes twice as long and is obviously not preferred)

IEnumerable<int> even = nums.Where(i => IsEven(i));
IEnumerable<int> odd = nums.Where(i => !IsEven(i));

You could do a single linear pass yourself (refactored into an extension method here), but this means you have to drag this code all over, and more custom code makes things less maintainable.

public static void SplitOnPred<T>(
        this IEnumerable<T> collection,
        Func<T, bool> pred,
        out IEnumerable<T> trueSet,
        out IEnumerable<T> falseSet
    ) {
        List<T> trueSetList = new List<T>();
        List<T> falseSetList = new List<T>();
        foreach( T item in collection ) {
            if( pred( item ) ) {
                trueSetList.Add( item );
            } else {
                falseSetList.Add( item );
            }
        }
        trueSet = trueSetList;
        falseSet = falseSetList;
}

Question: Does LINQ have any native support for splitting a collection in 1 linear pass?

like image 664
James Avatar asked Aug 13 '12 17:08

James


4 Answers

Does LINQ have any native support for splitting a collection in 1 linear pass?

There are no built-in methods that split a collection into two versions based on a predicate. You would need to use your own method, similar to the one you posted.

The closest built-in method would be GroupBy (or ToLookup). You could group by odd or even:

var groups = nums.GroupBy(i => IsEven(i)); 

This will split into two "groups" based on whether the numbers are odd or even.

like image 113
Reed Copsey Avatar answered Sep 22 '22 22:09

Reed Copsey


Reed Copsey's answer mentions ToLookup, and that seems attractive.

var lookup = nums.ToLookup(IsEven); 

where IsEven is a static method with the expected signature and return type. Then

IEnumerable<int> even = lookup[true]; IEnumerable<int> odd = lookup[false]; 
like image 37
Jeppe Stig Nielsen Avatar answered Sep 22 '22 22:09

Jeppe Stig Nielsen


Well if the logic is esclusive, in your case, you can do like

var list = new List<int> {1,2,3,4,5,6,7,8,9,10};    
var result = list.GroupBy(x=> x%2==0);

and in result

foreach(var r in result)
{
    if(r.Key)
     //EVEN
    else 
     //ODD
}
like image 25
Tigran Avatar answered Sep 22 '22 22:09

Tigran


If you want to support deferred execution, use a function or extension like this:

IEnumerable<T> Split<T>(this IEnumerable<T> source, out IEnumerable<T> odd)
{
   IList<T> oddCollector = new List<T>();
   Bool odd = true;
   foreach(T item in source)
   {
      if(odd)
      {
          oddCollector.Add(item);
      }
      else
      {
          yield return item;
      }
      odd = !odd;
   }
 }

My apologies for any small compiler error, i did this from the top of my head. Instead of even/odd, you can add a predicate.

like image 35
Rob van der Veer Avatar answered Sep 20 '22 22:09

Rob van der Veer