разделить список с помощью 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