Filtrando coleções em C #

142

Estou procurando uma maneira muito rápida de filtrar uma coleção em c #. Atualmente, estou usando coleções genéricas de List <object>, mas estou aberto a usar outras estruturas se elas tiverem melhor desempenho.

Atualmente, estou apenas criando uma nova lista <object> e fazendo um loop pela lista original. Se os critérios de filtragem corresponderem, coloco uma cópia na nova lista.

Existe uma maneira melhor de fazer isso? Existe uma maneira de filtrar no local para que não seja necessária uma lista temporária?

Jason Z
fonte
Isso vai ser incrivelmente rápido. Está causando uma lentidão no sistema? É uma lista enorme ? Caso contrário, eu não me preocuparia.
Iain Holder

Respostas:

237

Se você estiver usando o C # 3.0, poderá usar o linq, muito melhor e muito mais 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();

Se você não encontrar o .Where, significa que precisa importar using System.Linq;na parte superior do seu arquivo.

Jorge Córdoba
fonte
19
O método de extensão Where retorna IEnumerable <T>, não List <T>. Ele deve ser: myList.Where (x => x> 7) .ToList ()
Rafa Castaneda
1
Como isso funciona para filtrar por seqüências de caracteres. Como encontrar todos os itens em uma lista de strings que começam com "ch"
joncodo
2
@ JonathanO Você pode usar métodos dentro do Func. listOfStrings.Where (s => s.StartsWith ("ch")). ToList ();
Mike G
1
Existe uma maneira de objetivar consultas linq? Por exemplo, para usar em .Where(predefinedQuery)vez de usar .Where(x => x > 7)?
XenoRo
2
@ AlmightyR: Basta defini-lo como um método que leva um argumento. Ex: public bool predefinedQuery(int x) { return x > 7; }. Então o seu .Where(predefinedQuery)funcionaria bem.
Don
21

Aqui está um bloco de código / exemplo de alguma filtragem de lista usando três métodos diferentes que reuni para mostrar a filtragem de lista baseada em Lambdas e 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
fonte
14

List<T> tem um FindAll método que fará a filtragem para você e retornará um subconjunto da lista.

O MSDN tem um ótimo exemplo de código aqui: http://msdn.microsoft.com/en-us/library/aa701359(VS.80).aspx

Edição: Eu escrevi isso antes que eu tivesse uma boa compreensão do LINQ e do Where()método. Se eu escrevesse isso hoje, provavelmente usaria o método que Jorge menciona acima. O FindAllmétodo ainda funciona se você estiver preso em um ambiente .NET 2.0.

Mykroft
fonte
4
Como o Linq é bom, mas pelo menos uma magnitude mais lenta, os métodos de extensão FindAll e de filtragem (a matriz tem vários deles, por exemplo) que não dependem de IEnumerable ainda fazem sentido para cenários onde o desempenho é importante. (Fwiw, I obteve resultados do factor de 7 a 50 mais o tempo necessário para Linq e / ou IEnumerable, geralmente)
Philm
Existe uma razão para essa não ser a resposta aceita? Parece ser mais rápido e a sintaxe é mais clara (sem toList ()) chamada no final.
Ran Lottem 10/02/19
6

Você pode usar IEnumerable para eliminar a necessidade de uma lista temporária.

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

onde Matches é o nome do seu método de filtro. E você pode usar isso como:

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

Isso chamará a função GetFilteredItems quando necessário e, em alguns casos, se você não usar todos os itens da coleção filtrada, poderá fornecer um bom ganho de desempenho.

Serhat Ozgel
fonte
4

Para fazer isso no lugar, você pode usar o método RemoveAll da classe "List <>" junto com uma classe personalizada "Predicate" ... mas tudo o que faz é limpar o código ... sob o capô, ele está fazendo o mesmo coisa que você é ... mas sim, ele faz no lugar, então você faz o mesmo na lista temporária.

Adam Haile
fonte
4

Você pode usar o método FindAll da lista, fornecendo um representante para filtrar. No entanto, concordo com o @ IainMH que não vale a pena se preocupar muito, a menos que seja uma lista enorme.

bdukes
fonte
3

Se você estiver usando o C # 3.0, poderá usar o linq

Ou, se preferir, use a sintaxe de consulta especial fornecida pelo compilador C # 3:

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

O uso do LINQ é relativamente mais lento do que o uso de um predicado fornecido ao FindAllmétodo Lists . Também tenha cuidado com o LINQ, pois a enumeração do listnão é realmente executada até você acessar o resultado. Isso pode significar que, quando você acha que criou uma lista filtrada, o conteúdo pode diferir do que você esperava quando realmente a leu.

gouldos
fonte
1

Se sua lista for muito grande e você estiver filtrando repetidamente - você pode classificar a lista original no atributo filter, pesquisa binária para encontrar os pontos inicial e final.

Hora inicial O (n * log (n)) e O (log (n)).

A filtragem padrão leva O (n) de cada vez.

Daniel Roberts
fonte