split a list using linq
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
精彩评论