2012-05-14 15 views
8

Tengo un código que necesita saber que una colección no debe estar vacía o contener solo un elemento.¿Hay una extensión LINQ o (un conjunto sensato/eficiente de enlínea LINQ) que determine si una colección tiene al menos 'x' elementos?

En general, quiero una extensión de la forma:

bool collectionHasAtLeast2Items = collection.AtLeast(2);

puedo escribir una extensión fácilmente, enumerando sobre la recogida e incrementando un indexador hasta que golpeé el tamaño solicitado, o se queda sin elementos, pero ¿hay algo que ya esté en el marco de LINQ que haría esto? Mis pensamientos (en orden de lo que llegó a mí) son ::

bool collectionHasAtLeast2Items = collection.Take(2).Count() == 2; o

bool collectionHasAtLeast2Items = collection.Take(2).ToList().Count == 2;

que parecería trabajo, aunque el comportamiento de tomar más elementos de la colección contiene no está definido (en la documentación) Enumerable.Take Method, sin embargo, parece hacer lo que uno esperaría.

No es la solución más eficiente, ya sea enumerando una vez para tomar los elementos, enumerando nuevamente para contarlos, lo cual es innecesario, o enumerando una vez para tomar los elementos, luego construyendo una lista para obtener la propiedad de conteo que no es enumerador-y, ya que en realidad no quiero la lista.

No es lindo ya que siempre tengo que hacer dos afirmaciones, primero tomando 'x', luego comprobando que en realidad recibí 'x', y depende del comportamiento no documentado.

O quizás podría usar:

bool collectionHasAtLeast2Items = collection.ElementAtOrDefault(2) != null;

Sin embargo, eso no es semánticamente clara. Quizás lo mejor es envolverlo con un nombre de método que signifique lo que quiero. Asumo que esto será eficiente, no he reflexionado sobre el código.

Algunos otros pensamientos están usando Last(), pero explícitamente no quiero enumerar toda la colección.

O tal vez Skip(2).Any(), de nuevo no completamente semánticamente obvio, pero mejor que ElementAtOrDefault(2) != null, aunque creo que producen el mismo resultado?

¿Alguna idea?

+0

¿Por qué no crear una colección, cargar algunos datos y probar? – Paparazzi

+2

La página a la que enlaza tiene un comentario 'Tome enumera fuente y cede elementos hasta que los elementos de recuento hayan sido cedidos o la fuente no contenga más elementos' que pueda leerse como" si la lista fuente es más corta que el conteo, entonces solo eso se devuelve una lista breve " –

+0

@HansKesting - probablemente dijo eso hasta que los abogados se enteraron :) –

Respuesta

4
public static bool AtLeast<T>(this IEnumerable<T> source, int count) 
{ 
    // Optimization for ICollection<T> 
    var genericCollection = source as ICollection<T>; 
    if (genericCollection != null) 
     return genericCollection.Count >= count; 

    // Optimization for ICollection 
    var collection = source as ICollection; 
    if (collection != null) 
     return collection.Count >= count; 

    // General case 
    using (var en = source.GetEnumerator()) 
    { 
     int n = 0; 
     while (n < count && en.MoveNext()) n++; 
     return n == count; 
    } 
} 
+0

El código para el caso general se puede incluir en un método LINQ, ¿no es así? – abatishchev

+0

@abatishchev, sí podría, pero hay menos sobrecarga si manipula el enumerador directamente (sí, es una micro-optimización ...) –

+0

Entonces todo el código se puede escribir sin LINQ en absoluto, eso será un poco más rápido, pero el problema de derechos de autor puede aparecer :) – abatishchev

4

Puede usar Count() >= 2, si la secuencia implementa ICollection?


Detrás de la escena, Enumerable.Count() cheques método de extensión hace la secuencia bajo bucle implementa ICollection. Si lo hace, se devuelve la propiedad Count, por lo que el rendimiento objetivo debe ser O (1).

Por lo tanto, ((IEnumerable<T>)((ICollection)sequence)).Count() >= x también debería tener O (1).

+0

Creo que se está refiriendo a un método de rendimiento superior que no sea Count(). – ericosg

+1

@ericosg: Mi exploración muestra el rendimiento O (1). – abatishchev

+0

Creo que este es un buen método, ya que dadas las limitaciones de 'IEnumerable', lo mejor que puedes esperar es que, en el caso más general, solo enumeres dos elementos. Obtener la optimización para 'ICollection' 'gratis' es una buena victoria. –

3

Puede usar Count, pero si el rendimiento es un problema, estará mejor con Take.

bool atLeastX = collection.Take(x).Count() == x; 

Desde Take (creo) utiliza ejecución diferida, sólo pasará a través de la colección de una vez.

abatishchev mencionó que Count es O (1) con ICollection, por lo que podría hacer algo como esto y obtener lo mejor de ambos mundos.

IEnumerable<int> col; 
// set col 
int x; 
// set x 
bool atLeastX; 
if (col is ICollection<int>) 
{ 
    atLeastX = col.Count() >= x; 
} 
else 
{ 
    atLeastX = col.Take(x).Count() == x; 
} 

También es posible usar Skip/Any, de hecho, apuesto a que sería incluso más rápido que Take/Count.

+0

Siempre asumí que O (1) es tan rápido como puede, ¿está mal? (Me refiero a la parte "... sería aún más rápido") – Alex

+0

Actualizado, disculpe la confusión. –

+0

@alex: En realidad será O (1) + C1 <> O (1) + C2, donde C1, C2 son constantes, que pueden ser diferentes. Por lo tanto, el rendimiento resultante no depende de la cantidad de elementos, sino solo del código subyacente. – abatishchev

Cuestiones relacionadas