разделить список с помощью linq
У меня есть следующий код:
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)
Это выглядит некрасиво. В принципе, повторите список и добавьте элементы в один список, пока не сработает какое-то условие, а остальное добавьте в другой список.
есть ли лучший способ, например, с помощью LINQ ? CheckCondition () стоит дорого, и списки могут быть огромными, поэтому я бы предпочел не делать ничего, что повторяет списки дважды.
6 ответов
вот решение, которое будет перечислять список дважды, но оно не будет проверять условие во второй раз, поэтому оно должно быть быстрее:
var a = someList.TakeWhile(x => !CheckCondition(x)).ToList();
var b = someList.Skip(a.Count).ToList();
если someList
осуществляет IList<T>
, каждый элемент фактически будет перечислен только один раз, поэтому не будет никакого штрафа.
Я думал Skip
был оптимизирован для случая IList<T>
, но, по-видимому, это не так... Однако, вы можете легко реализовать свой собственный 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;
}
}
С помощью этого метода, код становится:
var a = someList.TakeUntil(CheckCondition).ToList();
var b = someList.Skip(a.Count).ToList();
Я не хотел меняться Ани, но вот небольшое упрощение.
var listToBeAdded = a;
foreach (var item in someList)
{
if (listToBeAdded == a && CheckCondition(item))
listToBeAdded = b;
listToBeAdded.Add(item);
}
лично я не думаю, что здесь есть необходимость в LINQ.
Я бы сделал что-то вроде:
bool conditionHit = false;
foreach (var item in someList)
{
if (!conditionHit)
conditionHit = CheckCondition(item);
var listToBeAdded = conditionHit ? b : a;
listToBeAdded.Add(item);
}
Если someList
конкретный List<T>
тогда для этого потребуется только один проход через каждый элемент:
var a = someList.TakeWhile(x => !CheckCondition(x)).ToList();
var b = someList.GetRange(a.Count, someList.Count - a.Count);
это в конечном итоге будет идти по пунктам в первом списке более одного раза, но только звонки через CheckCondition
в первый раз:
var a = someList.TakeWhile(e => !CheckCondition(e));
var b = someList.Skip(a.Count());
попробуйте это (не повторно используя встроенные методы 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
выход:
First Half : I
First Half : Love
First Half : You
Second Half : More
Second Half : Today
Second Half : Than
Second Half : Yesterday