2011-01-06 27 views
9

He el siguiente código:dividir una lista utilizando 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) 

Esto se ve feo. Básicamente, itere a través de una lista y agregue elementos a una lista hasta que se active una condición, y agregue el resto a otra lista.

¿Hay alguna otra manera, por ejemplo,? usando linq? CheckCondition() es costoso, y las listas pueden ser enormes, por lo que preferiría no hacer nada que itere las listas dos veces.

+1

entiendo su código, pero si '(e.Current)' no tiene sentido. Creo que está buscando almacenar el valor de la última llamada 'MoveNext' (desde el primer ciclo) y verificar si fue exitoso. – Ani

+0

Actualizado para que tenga más sentido (es decir, agregue e.Actual a 'b' una vez que CheckCondition es verdadero, en lugar de manejar ese elemento fuera del ciclo – Anonym

+0

Sí, está claro ahora. – Ani

Respuesta

7

he aquí una solución que va a enumerar la lista dos veces, pero no va a comprobar el estado de la segunda vez, por lo que debería ser más rápido:

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

Si someList implementa IList<T>, cada elemento se enumerará realmente una sola vez, por lo que no habrá ninguna penalización.
pensé Skip se ha optimizado para el caso de IList<T>, pero al parecer no es ... Sin embargo fácilmente se podría implementar su propio método Skip que utiliza esta optimización (ver Jon Skeet's article sobre esto)

En realidad, sería más elegante si había un método TakeUntil ... podemos crear fácilmente:

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; 
    } 
} 

Con este método, el código se convierte en:

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

+1: Bien, pero creo * * Omitir no tiene la optimización de la que hablas para 'IList 'como de .NET 4. No estoy seguro. – Ani

+0

Ani tiene razón. 'Skip' no tiene optimizaciones específicas para' IList 'por lo que la primera parte de la lista siempre se recorre dos veces. – LukeH

+0

@Ani, acabo de verificar, no parece estar optimizado para IList ... Actualizaré mi respuesta. –

2

Personalmente, no creo que haya necesidad de LINQ aquí.

Me gustaría hacer algo como:

bool conditionHit = false; 

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

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

Me gusta más que el código de OP porque no tiene ese cuestionable 'if (e.Current)' en él. Lo ideal sería establecer 'listTobeAdded' dos veces (una para cada lista) en lugar de calcular cada iteración. – Gabe

+0

@Gabe: Gracias. Lo que realmente quería evitar era iterar el origen dos veces y cálculos redundantes de 'CheckCondition'; ambos requisitos se establecen en el problema. No pensé que el OP tuviera problemas con la bifurcación redundante basada en una bandera. :) – Ani

1

Esto va a terminar pasando a través de los elementos de la primera lista más de una vez, pero sólo llamadas a través de CheckCondition primera vez:

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

a.Count() va a enumerar la primera consulta, que deberá enumerarse * otra vez * para obtener los resultados ... Debe llamar a ToList al final (y obtendrá exactamente mi solución) –

2

Si someList es un hormigón List<T> entonces esto sólo se necesita un único pasar a través de cada elemento:

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

no quería cambiar Ani's answer, pero aquí hay una ligera simplificación.

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

    listToBeAdded.Add(item); 
} 
+1

+1 - Gran respuesta. – ChaosPandion

0

Prueba esto (no reutilizar métodos incorporados de Linq (conocidas para rebobinar los iteradores)), acaba de reutilización de la lógica de la OP (que creo que es performant, no volver a evaluar la condición en la próxima la mitad de la lista) y el embalaje en un método de extensión ordenada y tupla:

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 

salida:

First Half : I 
First Half : Love 
First Half : You 
Second Half : More 
Second Half : Today 
Second Half : Than 
Second Half : Yesterday 
Cuestiones relacionadas