3 votes

Comment puis-je diviser une liste générique (de T) en fonction d'une propriété d'un élément de la liste ?

J'ai une liste générique (de Foo) qui contient n objets de type Foo. Une des propriétés de Foo est PropertyA. PropertyA peut être l'une des valeurs de ValueA, ValueB ou ValueC. Existe-t-il un moyen facile de diviser cela en trois listes séparées, une pour ValueA, une pour ValueB et une pour ValueC?

Je peux écrire du code qui boucle sur la liste d'origine et ajoute chaque élément à une nouvelle liste en fonction de la valeur de la propriété, mais cela ne semble pas être très maintenable (que se passe-t-il si j'obtiens soudainement une valeur de ValueD, par exemple?)

**EDIT. J'aurais dû mentionner que j'utilise la version 2.0 du framework.

5voto

David B Points 53123

En C#, j'écrirais :

  List> result = fooList
    .GroupBy(foo => foo.PropertyA)
    .Select(g => g.ToList())
    .ToList();

5voto

Heinzi Points 66519

Si vous voulez exactement 3 listes pour valueA, valueB et valueC (même si l'une d'entre elles est vide) :

Dim listA = (From x in myList Where x.PropertyA = ValueA).ToList()
Dim listB = (From x in myList Where x.PropertyA = ValueB).ToList()
...

Sinon, utilisez l'opérateur GroupBy comme suggéré par d'autres.


EDIT: Comme vous utilisez Framework 2.0, je suppose que vous devrez recourir à votre idée de boucle. Cependant, l'implémentation d'un algorithme générique utilisant GroupBy ne devrait pas être trop difficile. Quelque chose du genre :

Dim dic as New Dictionary(Of TypeOfYourValues, List(Of Foo))
For Each e As Foo In myList
    If Not dic.ContainsKey(e.PropertyA) Then
        dic(e.PropertyA) = New List(Of Foo)
    End if
    dic(e.PropertyA).Add(e)
Next

Ensuite, parcourez les valeurs du dictionnaire.

5voto

David B Points 53123

En C# avec .Net 2.0, j'ai écrit (trop de fois) :

//if PropertyA is not int, change int to whatever that type is
Dictionary> myCollections =
  new Dictionary>();
//
foreach(Foo myFoo in fooList)
{
  //if I haven't seen this key before, make a new entry
  if (!myCollections.ContainsKey(myFoo.PropertyA))
  {
    myCollections.Add(myFoo.PropertyA, new List());
  }
  //now add the value to the entry.
  myCollections[myFoo.PropertyA].Add(myFoo);
}
//
// now recollect these lists into the result.
List> result = new List>();
foreach(List someFoos in myCollections.Values)
{
  result.Add(someFoos);
}

Maintenant, je n'écris que :

List> result = fooList
  .GroupBy(foo => foo.PropertyA)
  .Select(g => g.ToList())
  .ToList();

Ou

ILookup> result = fooList.ToLookup(foo => foo.PropertyA);

3voto

Richard Szalay Points 42486

Vous pouvez utiliser Enumerable.GroupBy :

var groupings = list.GroupBy(x => x.PropertyA);

foreach(var grouping in groupings)
{
    // grouping.Key est la valeur regroupée

    foreach(var entry in grouping)
    {
        // processus
    }
}

3voto

Doug Rohrer Points 856

Voici ci-dessous le C# pour la version VB.Net - notez qu'il y a une classe supplémentaire (FooFinder) car il n'y a pas de méthodes anonymes en VB.NET, donc j'avais besoin de quelque chose pour pouvoir stocker l'état de la correspondance.

Voici une manière plus "fonctionnelle" d'accomplir la même chose, mais en utilisant toujours la syntaxe C# 2.0. Notez la différence importante par rapport aux autres solutions (boucles/dictionnaires) est l'utilisation de la méthode FindAll sur List, qui va itérer sur votre collection et renvoyer tous les éléments pour lesquels le délégué retourne true. C#:

using System;
using System.Collections.Generic;

namespace SplitList
{
    class Program
    {
        class Foo
        {
            public Foo(string propertyA, int number)
            {
                _propertyA = propertyA;
                _number = number;
            }

            private int _number;

            private string _propertyA;

            public string PropertyA
            {
                get { return _propertyA; }
            }

            public int Number
            {
                get { return _number; }
            }
        }
        static void Main(string[] args)
        {
            List foos = new List();
            foos.Add(new Foo("ValueA", 1));
            foos.Add(new Foo("ValueA", 2));
            foos.Add(new Foo("ValueA", 3));
            foos.Add(new Foo("ValueA", 4));
            foos.Add(new Foo("ValueB", 5));
            foos.Add(new Foo("ValueB", 6));
            foos.Add(new Foo("ValueC", 7));
            foos.Add(new Foo("ValueC", 8));
            foos.Add(new Foo("ValueC", 9));

            List aFoos = foos.FindAll(delegate(Foo f) { return f.PropertyA == "ValueA"; });
            List bFoos = foos.FindAll(delegate(Foo f) { return f.PropertyA == "ValueB"; });
            List cFoos = foos.FindAll(delegate(Foo f) { return f.PropertyA == "ValueC"; });
            WriteFoos("ValueA", aFoos);
            WriteFoos("ValueB", bFoos);
            WriteFoos("ValueC", cFoos);
            Console.ReadLine();
        }

        private static void WriteFoos(string propertyAValue, List list)
        {
            Console.WriteLine("Groupe {0}:", propertyAValue);
            list.ForEach(delegate(Foo f)
                             {
                             Console.WriteLine("Numéro:{0}, PropertyA:{1}", f.Number, f.PropertyA);
                             });

        }
    }
}

VB.NET:

Module Module1

    Class FooFinder
        Public Sub New(ByVal propertyAValue As String)
            Me.PropertyAValue = propertyAValue
        End Sub
        Public ReadOnly PropertyAValue As String
        Function Matches(ByVal f As Foo) As Boolean
            Return (f.PropertyAValue = Me.PropertyAValue)
        End Function
    End Class
    Class Foo

        Public Sub New(ByVal propertyAValue As String, ByVal number As Integer)
            _propertyAValue = propertyAValue
            _number = number
        End Sub

        Private _propertyAValue As String
        Private _number As Integer

        Public Property PropertyAValue() As String
            Get
                Return _propertyAValue
            End Get
            Set(ByVal value As String)
                _propertyAValue = value
            End Set
        End Property

        Public Property Number() As Integer
            Get
                Return _number
            End Get
            Set(ByVal value As Integer)
                _number = value
            End Set
        End Property
    End Class
    Sub Main()

        Dim foos As New List(Of Foo)
        foos.Add(New Foo("ValueA", 1))
        foos.Add(New Foo("ValueA", 2))
        foos.Add(New Foo("ValueA", 3))
        foos.Add(New Foo("ValueB", 4))
        foos.Add(New Foo("ValueB", 5))
        foos.Add(New Foo("ValueC", 6))
        foos.Add(New Foo("ValueC", 7))
        foos.Add(New Foo("ValueC", 8))
        foos.Add(New Foo("ValueC", 9))

        Dim aFoos As List(Of Foo) = foos.FindAll(AddressOf New FooFinder("ValueA").Matches)
        Dim bFoos As List(Of Foo) = foos.FindAll(AddressOf New FooFinder("ValueB").Matches)
        Dim cFoos As List(Of Foo) = foos.FindAll(AddressOf New FooFinder("ValueC").Matches)

        WriteFoos("ValueA", aFoos)
        WriteFoos("ValueB", bFoos)
        WriteFoos("ValueC", cFoos)
        Console.ReadLine()

    End Sub

    Private Sub WriteFoos(ByVal propertyAValue As String, ByVal list As List(Of Foo))
        Console.WriteLine("PropertyAValue:{0}", propertyAValue)
        For Each f As Foo In list
            Console.WriteLine("Number:{0}, PropertyAValue:{1}", f.Number, f.PropertyAValue)
        Next
    End Sub
End Module

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X