C# 使用linq拆分列表
我有以下代码:C# 使用linq拆分列表,c#,C#,我有以下代码: 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); } w
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)
var e=someList.GetEnumerator();
var a=新列表();
var b=新列表();
while(如MoveNext()){
如果(检查条件(e.电流)){
b、 加上(e.Current);
打破
}
a、 加上(e.Current);
}
while(如MoveNext())
b、 添加(如当前)
这看起来很难看。基本上,遍历一个列表并将元素添加到一个列表中,直到出现某种条件,然后将其余元素添加到另一个列表中
是否有更好的方法,例如使用linq?CheckCondition()非常昂贵,而且列表可能非常庞大,因此我不想做任何重复列表两次的事情。我个人认为这里不需要LINQ 我会这样做:
bool conditionHit = false;
foreach (var item in someList)
{
if (!conditionHit)
conditionHit = CheckCondition(item);
var listToBeAdded = conditionHit ? b : a;
listToBeAdded.Add(item);
}
下面是一个将枚举列表两次的解决方案,但它不会第二次检查条件,因此应该更快:
var a = someList.TakeWhile(x => !CheckCondition(x)).ToList();
var b = someList.Skip(a.Count).ToList();
如果someList
实现了IList
,每个项目实际上只会被枚举一次,因此不会有任何惩罚。我以为
Skip
针对IList
的情况进行了优化,但显然不是。。。但是,您可以轻松实现自己的Skip
方法,该方法使用此优化(请参阅关于此)
如果有一个TakeUntil
方法,它实际上会更优雅。。。我们可以轻松创建它:
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;
}
}
这将不止一次地检查第一个列表中的项目,但仅在第一次通过
CheckCondition
调用:
var a = someList.TakeWhile(e => !CheckCondition(e));
var b = someList.Skip(a.Count());
如果
someList
是一个具体的列表,则只需通过每个元素一次:
var a = someList.TakeWhile(x => !CheckCondition(x)).ToList();
var b = someList.GetRange(a.Count, someList.Count - a.Count);
我不想改变,但这里有一个小小的简化
var listToBeAdded = a;
foreach (var item in someList)
{
if (listToBeAdded == a && CheckCondition(item))
listToBeAdded = b;
listToBeAdded.Add(item);
}
尝试一下(不要重复使用Linq的内置方法(以倒带迭代器而闻名)),只需重复使用OP的逻辑(我相信这是有效的,它不会在列表的下半部分重新计算条件),并将其打包到一个整洁的扩展方法和元组中:
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
+1:很好,但是我认为
Skip
没有你所说的从.NET 4开始的IList
的优化。不确定,阿尼是对的Skip
没有针对IList的特定优化,因此列表的第一部分将始终被遍历两次。@Ani,我刚刚检查过,它似乎没有针对IList进行优化。。。我将更新我的答案。a.Count()将枚举第一个查询,需要再次枚举以获得结果。。。你需要在最后打电话给ToList(你会得到我的解决方案)我理解你的代码,但是如果(e.Current)没有意义。我认为您正在寻找存储上次MoveNext
调用(从第一个循环)的值,并检查它是否成功。更新以使其更有意义(也就是说,一旦CheckCondition为true,将e.Current添加到b
,而不是在循环之外处理该项,我比OP的代码更喜欢这一点,因为如果(e.Current)中没有可疑的。理想情况下,您可以将列表设置为headed
两次(每个列表一次)而不是每次迭代都计算。@Gabe:谢谢。我真正想避免的是重复源代码两次,并重复计算CheckCondition
;这两个要求都在问题中说明。我不认为OP在基于标志的冗余分支方面有任何问题。:)
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
First Half : I
First Half : Love
First Half : You
Second Half : More
Second Half : Today
Second Half : Than
Second Half : Yesterday