70 votes

Dictionnaire C# - une clé, plusieurs valeurs

Je veux créer un magasin de données pour me permettre de stocker certaines données.

La première idée était de créer un dictionnaire avec une clé et plusieurs valeurs, un peu comme une relation de type "un à plusieurs".

Je pense que le dictionnaire n'a qu'une seule valeur clé.

Comment pourrais-je stocker ces informations autrement ?

73voto

mbx Points 1823

À partir de la version 3.5+ de .NET, au lieu d'utiliser une balise Dictionary<IKey, List<IValue>> vous pouvez utiliser un Lookup de l'espace de noms LINQ :

// Lookup Order by payment status (1:m)
// would need something like Dictionary<Boolean, IEnumerable<Order>> orderIdByIsPayed
ILookup<Boolean, Order> byPayment = orderList.ToLookup(o => o.IsPayed);
IEnumerable<Order> payedOrders = byPayment[false];

De MSDN :

Une Lookup<TKey, TElement> ressemble à un Dictionary<TKey, TValue>. La différence avec différence est qu'un Dictionary<TKey, TValue> fait correspondre les clés à des valeurs uniques, alors qu'une Lookup<TKey, TElement> fait correspondre les clés à des collections de valeurs.

Vous pouvez créer une instance d'une Lookup<TKey, TElement> en appelant ToLookup sur un objet qui implémente IEnumerable.

Vous pouvez également lire cette réponse à un Question connexe . Pour plus d'informations, consultez MSDN .

Exemple complet :

using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqLookupSpike
{
    class Program
    {
        static void Main(String[] args)
        {
            // Init
            var orderList = new List<Order>();
            orderList.Add(new Order(1, 1, 2010, true)); // (orderId, customerId, year, isPayed)
            orderList.Add(new Order(2, 2, 2010, true));
            orderList.Add(new Order(3, 1, 2010, true));
            orderList.Add(new Order(4, 2, 2011, true));
            orderList.Add(new Order(5, 2, 2011, false));
            orderList.Add(new Order(6, 1, 2011, true));
            orderList.Add(new Order(7, 3, 2012, false));

            // Lookup Order by its id (1:1, so usual dictionary is ok)
            Dictionary<Int32, Order> orders = orderList.ToDictionary(o => o.OrderId, o => o);

            // Lookup Order by customer (1:n)
            // would need something like Dictionary<Int32, IEnumerable<Order>> orderIdByCustomer
            ILookup<Int32, Order> byCustomerId = orderList.ToLookup(o => o.CustomerId);
            foreach (var customerOrders in byCustomerId)
            {
                Console.WriteLine("Customer {0} ordered:", customerOrders.Key);
                foreach (var order in customerOrders)
                {
                    Console.WriteLine("    Order {0} is payed: {1}", order.OrderId, order.IsPayed);
                }
            }

            // The same using old fashioned Dictionary
            Dictionary<Int32, List<Order>> orderIdByCustomer;
            orderIdByCustomer = byCustomerId.ToDictionary(g => g.Key, g => g.ToList());
            foreach (var customerOrders in orderIdByCustomer)
            {
                Console.WriteLine("Customer {0} ordered:", customerOrders.Key);
                foreach (var order in customerOrders.Value)
                {
                    Console.WriteLine("    Order {0} is payed: {1}", order.OrderId, order.IsPayed);
                }
            }

            // Lookup Order by payment status (1:m)
            // would need something like Dictionary<Boolean, IEnumerable<Order>> orderIdByIsPayed
            ILookup<Boolean, Order> byPayment = orderList.ToLookup(o => o.IsPayed);
            IEnumerable<Order> payedOrders = byPayment[false];
            foreach (var payedOrder in payedOrders)
            {
                Console.WriteLine("Order {0} from Customer {1} is not payed.", payedOrder.OrderId, payedOrder.CustomerId);
            }
        }

        class Order
        {
            // Key properties
            public Int32 OrderId { get; private set; }
            public Int32 CustomerId { get; private set; }
            public Int32 Year { get; private set; }
            public Boolean IsPayed { get; private set; }

            // Additional properties
            // private List<OrderItem> _items;

            public Order(Int32 orderId, Int32 customerId, Int32 year, Boolean isPayed)
            {
                OrderId = orderId;
                CustomerId = customerId;
                Year = year;
                IsPayed = isPayed;
            }
        }
    }
}

Remarque sur l'immuabilité

Par défaut, consultations sont en quelque sorte immuables et l'accès aux internal impliquerait une réflexion. Si vous avez besoin de la mutabilité et que vous ne voulez pas écrire votre propre wrapper, vous pouvez utiliser MultiValueDictionary (anciennement connu sous le nom de MultiDictionary ) de corefxlab (qui faisait auparavant partie de Microsoft.Experimental.Collections qui n'est plus mis à jour).

0 votes

Supposons que la clé et les valeurs sont toutes deux de type chaîne de caractères, alors pourquoi ne pas Dictionary<string, HashSet<string>> au lieu de Dictionary<string, List<string>> ? Une liste ne peut pas garantir l'unicité au sein de la collection de valeurs pour une clé donnée, mais un ensemble le peut.

0 votes

@user3613932 Ce n'est pas le but de ma réponse. Si vous voulez imposer l'unicité des valeurs, il suffit de .Distinct() avant de créer la consultation. Si vous avez besoin de multisets ou de garder les éléments ordonnés ou indexés, une liste semble raisonnable.

0 votes

@mbx Je suis d'accord avec la justesse et la faisabilité de votre approche. Avec l'approche par liste, il y a une hypothèse implicite dans votre réponse, c'est-à-dire que la collection de valeurs par clé est petite, et avec cette hypothèse à l'esprit, votre réponse semble appropriée. Cependant, du point de vue de la lisibilité, si j'utilise quelque chose d'ordinaire pour maintenir l'unicité de la collection (ensemble), je dirais que c'est plus lisible car les interfaces/API sont claires, et même la classe HashSet donne un signal très clair au lecteur de mon code (il n'a pas besoin d'entrer dans mon implémentation pour voir ce que je fais).

54voto

Oded Points 271275

Vous pouvez utiliser une liste pour le deuxième type générique. Par exemple, un dictionnaire de chaînes de caractères dont la clé est une chaîne de caractères :

Dictionary<string, List<string>> myDict;

19voto

Ian Hays Points 91

Microsoft vient d'ajouter une version officielle de ce que vous recherchez (appelée MultiDictionary) disponible via NuGet ici : https://www.nuget.org/packages/Microsoft.Experimental.Collections/

Des informations sur l'utilisation et plus de détails sont disponibles sur le blog officiel de MSDN ici : http://blogs.msdn.com/b/dotnet/archive/2014/06/20/would-you-like-a-multidictionary.aspx

Je suis le développeur de ce paquet, alors faites-moi savoir ici ou sur MSDN si vous avez des questions sur les performances ou autre.

J'espère que cela vous aidera.

Mise à jour

Le site MultiValueDictionary est maintenant sur le repo corefxlab et vous pouvez obtenir le paquet NuGet à partir de este Alimentation MyGet.

0 votes

On dirait que ça s'appelle maintenant MultiValueDictionary. Je veux l'utiliser, mais je ne suis pas sûr de son avenir. Le blog n'a pas été mis à jour depuis 3 ans. Avez-vous des idées pour savoir si vous pouvez l'utiliser en toute sécurité ?

0 votes

Je n'ai pas expérimenté avec le MultiValueDictionary mais il met en œuvre IReadOnlyDictionary qui est immuable. Quoi qu'il en soit, j'ai mis à jour votre réponse, il semble que cet outil ait été déplacé vers le dépôt de corefxlab.

7voto

Tim Ridgely Points 1720

Le type de valeur de votre dictionnaire pourrait être une liste, ou une autre classe qui contient plusieurs objets. Quelque chose comme

Dictionary<int, List<string>>

pour un dictionnaire dont les clés sont des ints et qui contient une liste de chaînes de caractères.

Le choix du type de valeur dépend principalement de l'usage que vous ferez du dictionnaire. Si vous devez effectuer des recherches ou d'autres opérations sur les valeurs, pensez à utiliser une structure de données qui vous aide à faire ce que vous voulez, comme un dictionnaire de type HashSet .

5voto

Blorgbeard Points 38991

Vous pourriez utiliser un Dictionary<TKey, List<TValue>> .

Cela permettrait à chaque clé de faire référence à un liste de valeurs.

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