Filtrado de colecciones en C #

142

Estoy buscando una forma muy rápida de filtrar una colección en C #. Actualmente estoy usando colecciones genéricas List <object>, pero estoy abierto a usar otras estructuras si funcionan mejor.

Actualmente, estoy creando una nueva Lista <objeto> y recorriendo la lista original. Si el criterio de filtrado coincide, pongo una copia en la nueva lista.

¿Hay una mejor manera de hacer esto? ¿Hay alguna forma de filtrar en el lugar para que no se requiera una lista temporal?

Jason Z
fuente
Eso va a ser increíblemente rápido. ¿Está causando que su sistema se desacelere? ¿Es una lista enorme ? De lo contrario, no me preocuparía.
Iain Holder

Respuestas:

237

Si usa C # 3.0, puede usar linq, mucho mejor y mucho más elegante:

List<int> myList = GetListOfIntsFromSomewhere();

// This will filter out the list of ints that are > than 7, Where returns an
// IEnumerable<T> so a call to ToList is required to convert back to a List<T>.
List<int> filteredList = myList.Where( x => x > 7).ToList();

Si no puede encontrar el .Where, eso significa que necesita importar using System.Linq;en la parte superior de su archivo.

Jorge Córdoba
fuente
19
El método de extensión Where devuelve IEnumerable <T>, no List <T>. Debería ser: myList.Where (x => x> 7) .ToList ()
Rafa Castaneda
1
¿Cómo funciona esto para filtrar por cadenas? Como encontrar todos los elementos de una lista de cadenas que comienzan con "ch"
joncodo
2
@JonathanO Puedes usar métodos dentro del Func. listOfStrings.Where (s => s.StartsWith ("ch")). ToList ();
Mike G
1
¿Hay alguna manera de objetivar consultas linq? Por ejemplo, ¿usar en .Where(predefinedQuery)lugar de usar .Where(x => x > 7)?
XenoRo
2
@AlmightyR: solo defínalo como un método que toma un argumento. Ej: public bool predefinedQuery(int x) { return x > 7; }. Entonces tu .Where(predefinedQuery)funcionaría bien.
Don
21

Aquí hay un bloque de código / ejemplo de algunos filtros de listas usando tres métodos diferentes que reuní para mostrar el filtrado de listas basado en Lambdas y LINQ.

#region List Filtering

static void Main(string[] args)
{
    ListFiltering();
    Console.ReadLine();
}

private static void ListFiltering()
{
    var PersonList = new List<Person>();

    PersonList.Add(new Person() { Age = 23, Name = "Jon", Gender = "M" }); //Non-Constructor Object Property Initialization
    PersonList.Add(new Person() { Age = 24, Name = "Jack", Gender = "M" });
    PersonList.Add(new Person() { Age = 29, Name = "Billy", Gender = "M" });

    PersonList.Add(new Person() { Age = 33, Name = "Bob", Gender = "M" });
    PersonList.Add(new Person() { Age = 45, Name = "Frank", Gender = "M" });

    PersonList.Add(new Person() { Age = 24, Name = "Anna", Gender = "F" });
    PersonList.Add(new Person() { Age = 29, Name = "Sue", Gender = "F" });
    PersonList.Add(new Person() { Age = 35, Name = "Sally", Gender = "F" });
    PersonList.Add(new Person() { Age = 36, Name = "Jane", Gender = "F" });
    PersonList.Add(new Person() { Age = 42, Name = "Jill", Gender = "F" });

    //Logic: Show me all males that are less than 30 years old.

    Console.WriteLine("");
    //Iterative Method
    Console.WriteLine("List Filter Normal Way:");
    foreach (var p in PersonList)
        if (p.Gender == "M" && p.Age < 30)
            Console.WriteLine(p.Name + " is " + p.Age);

    Console.WriteLine("");
    //Lambda Filter Method
    Console.WriteLine("List Filter Lambda Way");
    foreach (var p in PersonList.Where(p => (p.Gender == "M" && p.Age < 30))) //.Where is an extension method
        Console.WriteLine(p.Name + " is " + p.Age);

    Console.WriteLine("");
    //LINQ Query Method
    Console.WriteLine("List Filter LINQ Way:");
    foreach (var v in from p in PersonList
                      where p.Gender == "M" && p.Age < 30
                      select new { p.Name, p.Age })
        Console.WriteLine(v.Name + " is " + v.Age);
}

private class Person
{
    public Person() { }
    public int Age { get; set; }
    public string Name { get; set; }
    public string Gender { get; set; }
}

#endregion
Jon Erickson
fuente
14

List<T>tiene un FindAllmétodo que hará el filtrado por usted y devolverá un subconjunto de la lista.

MSDN tiene un gran ejemplo de código aquí: http://msdn.microsoft.com/en-us/library/aa701359(VS.80).aspx

EDITAR: Escribí esto antes de tener una buena comprensión de LINQ y el Where()método. Si escribiera esto hoy, probablemente usaría el método que Jorge menciona arriba. Sin FindAllembargo, el método aún funciona si está atascado en un entorno .NET 2.0.

Mykroft
fuente
44
Linq está bien, pero al menos una magnitud más lenta, por lo que FindAll y los métodos de extensión de filtrado (la matriz tiene muchos de ellos, por ejemplo) que no dependen de IEnumerable todavía tienen sentido para escenarios donde el rendimiento es importante. (FWIW, obtuve resultados del factor 7 a 50 más tiempo requerido por Linq y / o IEnumerable, en general)
Philm
¿Hay alguna razón por la cual esta no es la respuesta aceptada? Parece ser más rápido y la sintaxis es más clara (no toList ()) llamada al final.
Ran Lottem
6

Puede usar IEnumerable para eliminar la necesidad de una lista temporal.

public IEnumerable<T> GetFilteredItems(IEnumerable<T> collection)
{
    foreach (T item in collection)
    if (Matches<T>(item))
    {
        yield return item;
    }
}

donde Matches es el nombre de su método de filtro. Y puedes usar esto como:

IEnumerable<MyType> filteredItems = GetFilteredItems(myList);
foreach (MyType item in filteredItems)
{
    // do sth with your filtered items
}

Esto llamará a la función GetFilteredItems cuando sea necesario y, en algunos casos, si no usa todos los elementos de la colección filtrada, puede proporcionar una buena ganancia de rendimiento.

Serhat Ozgel
fuente
4

Para hacerlo en su lugar, puede usar el método RemoveAll de la clase "List <>" junto con una clase personalizada "Predicate" ... pero todo lo que hace es limpiar el código ... bajo el capó está haciendo lo mismo Lo que eres ... pero sí, lo hace en su lugar, así que haces lo mismo con la lista temporal.

Adam Haile
fuente
4

Puede usar el método FindAll de la Lista, que proporciona un delegado para filtrar. Sin embargo, estoy de acuerdo con @ IainMH en que no vale la pena preocuparse demasiado a menos que sea una lista enorme.

bdukes
fuente
3

Si usa C # 3.0, puede usar linq

O, si lo prefiere, use la sintaxis de consulta especial proporcionada por el compilador C # 3:

var filteredList = from x in myList
                   where x > 7
                   select x;
Tom Lokhorst
fuente
3

Usar LINQ es relativamente mucho más lento que usar un predicado proporcionado al FindAllmétodo Listas . También tenga cuidado con LINQ ya que la enumeración de la listno se ejecuta realmente hasta que acceda al resultado. Esto puede significar que, cuando cree que ha creado una lista filtrada, el contenido puede diferir de lo que esperaba cuando realmente lo leyó.

gouldos
fuente
1

Si su lista es muy grande y está filtrando repetidamente, puede ordenar la lista original en el atributo de filtro, búsqueda binaria para encontrar los puntos de inicio y finalización.

Tiempo inicial O (n * log (n)) luego O (log (n)).

El filtrado estándar tomará O (n) cada vez.

Daniel Roberts
fuente