开发者

split a list using linq

开发者 https://www.devze.com 2023-02-02 11:51 出处:网络
I\'ve the following code: var e = someList.GetEnumerator(); var a = new List<Foo>(); var b = new List<Foo>();

I've the following code:

  var e = someList.GetEnumerator();
  var a = new List<Foo>();
  var b = new List<Foo>();
  while(e.MoveNext())  {
     if(CheckCondition(e.Current)) {
         b.Add(e.Current);
         break;
     }
     a.Add(e.Current);
 }

while(e.MoveNext())
  b.Add(e.Current)

This looks ugly. Basically, iterate through a list and add elements to one list until some condition kicks in, and add the rest to another list.

Is there a better way e.g. using linq ? CheckCondition() is expensive, and the lists can be huge so I'd pr开发者_开发技巧efer to not do anything that iterates the lists twice.


Here's a solution that's going to enumerate the list twice, but it won't check the condition the second time, so it should be faster:

var a = someList.TakeWhile(x => !CheckCondition(x)).ToList();
var b = someList.Skip(a.Count).ToList();

If someList implements IList<T>, each item will actually be enumerated only once, so there won't be any penalty.
I thought Skip was optimized for the case of IList<T>, but apparently it's not... However you could easily implement your own Skip method that uses this optimization (see Jon Skeet's article about this)

It would actually be more elegant if there was a TakeUntil method... we can easily create it:

public static IEnumerable<TSource> TakeUntil<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    foreach(var item in source)
    {
        if (predicate(item))
            break;
        yield return item;
    }
}

With this method, the code becomes:

var a = someList.TakeUntil(CheckCondition).ToList();
var b = someList.Skip(a.Count).ToList();


I didn't want to change Ani's answer, but here's a slight simplification.

var listToBeAdded = a;
foreach (var item in someList)
{
    if (listToBeAdded == a && CheckCondition(item))
        listToBeAdded = b;

    listToBeAdded.Add(item);
}


Personally, I don't think there's any need for LINQ here.

I would do something like:

bool conditionHit = false;

foreach (var item in someList)
{
    if (!conditionHit)
        conditionHit = CheckCondition(item);

    var listToBeAdded = conditionHit ? b : a;
    listToBeAdded.Add(item);
}


If someList is a concrete List<T> then this will only need a single pass through each element:

var a = someList.TakeWhile(x => !CheckCondition(x)).ToList();
var b = someList.GetRange(a.Count, someList.Count - a.Count);


This will end up going over the items in the first list more than once, but only calls through CheckCondition the first time:

var a = someList.TakeWhile(e => !CheckCondition(e));
var b = someList.Skip(a.Count());


Try this (not re-using Linq's built-in methods(known for rewinding the iterators)), just reusing the OP's logic (which I believe is performant, it doesn't re-evaluate condition on next half of list) and packing it in a neat extension method and Tuple:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;



namespace Craft
{
    class Act
    {
        static void Main(string[] args)
        {

            var a = new List<string>
                { "I", "Love", "You", "More", "Today", "Than", "Yesterday" };

            var tx = a.SplitByCondition(s => s == "More");

            foreach (var s in tx.Item1)
                Console.WriteLine("First Half : {0}", s);

            foreach (var s in tx.Item2)
                Console.WriteLine("Second Half : {0}", s);

            Console.ReadLine();                    
        }

    }//Act

    public static class Helper
    {

        public static Tuple<List<T>, List<T>> SplitByCondition<T>
            (this IEnumerable<T> t, Func<T, bool> terminator)
        {


            var tx = new Tuple<List<T>, List<T>>
                          (new List<T>(), new List<T>()); 

            var iter = t.GetEnumerator();

            while (iter.MoveNext())
            {
                if (terminator(iter.Current))
                {
                    tx.Item2.Add(iter.Current);
                    break;
                }

                tx.Item1.Add(iter.Current);
            }

            while (iter.MoveNext())
                tx.Item2.Add(iter.Current);

            return tx;
        }      

    }//Helper

}//Craft

Output:

First Half : I
First Half : Love
First Half : You
Second Half : More
Second Half : Today
Second Half : Than
Second Half : Yesterday
0

精彩评论

暂无评论...
验证码 换一张
取 消