【问题标题】:C# dictionary - one key, many valuesC# 字典 - 一键多值
【发布时间】:2011-01-07 05:57:40
【问题描述】:

我想创建一个数据存储来允许我存储一些数据。

第一个想法是创建一个字典,其中有一个键和多个值,有点像一对多的关系。

我认为字典只有一个键值。

我还能如何存储这些信息?

【问题讨论】:

    标签: c# .net dictionary datasource


    【解决方案1】:

    从 .NET 3.5+ 开始,您可以使用 LINQ 命名空间中的 Lookup,而不是使用 Dictionary<IKey, List<IValue>>

    // 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];
    

    来自MSDN

    Lookup 类似于 Dictionary。这 区别在于 Dictionary 将键映射到单个 值,而 Lookup 将键映射到 价值观。

    你可以通过调用创建一个 Lookup 的实例 ToLookup 在实现 IEnumerable 的对象上。

    您可能还想将this answer 阅读为related question。欲了解更多信息,请咨询MSDN

    完整示例:

    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;
                }
            }
        }
    }
    

    关于不变性的评论

    默认情况下,查找是不可变的,访问internals 将涉及反射。 如果您需要可变性并且不想编写自己的包装器,您可以使用来自corefxlab(以前是Microsoft.Experimental.Collections 的一部分,不再更新)的MultiValueDictionary(以前称为MultiDictionary)。

    【讨论】:

    • 假设键和值都是字符串类型,那么为什么不用Dictionary&lt;string, HashSet&lt;string&gt;&gt; 而不是Dictionary&lt;string, List&lt;string&gt;&gt;?列表不能保证给定键在值集合中的唯一性,但集合可以。
    • @user3613932 不是我回答的重点。如果您想强制值的唯一性,只需在创建查找之前.Distinct() 它们。如果您需要多重集或保持项目有序或索引,则列表似乎是合理的。
    • @mbx 我同意您的方法的正确性和可行性。使用列表方法,您的答案中有一个隐含的假设,即每个键的值集合很小,并且考虑到这个假设,您的答案似乎是合适的。但是,从可读性的角度来看,如果我使用现成的东西来保持集合(集合)的唯一性,那么我认为它更具可读性,因为接口/API 很清晰,甚至HashSet 类也给出了非常清晰的向我的代码的读者发出信号(他们不必进入我的实现就可以看到我在做什么)。
    • @user3613932 您的意思是完整示例中的第一个列表,对吗?这只是展示ToLookupILookup 与基于Dictionary 的实现的用法的样板。
    【解决方案2】:

    您可以为第二种泛型类型使用列表。例如以字符串为键的字符串字典:

    Dictionary<string, List<string>> myDict;
    

    【讨论】:

      【解决方案3】:

      Microsoft 刚刚通过 NuGet 在此处添加了您正在寻找的正式预发布版本(称为 MultiDictionary):https://www.nuget.org/packages/Microsoft.Experimental.Collections/

      有关使用信息和更多详细信息,请参阅此处的官方 MSDN 博客文章:http://blogs.msdn.com/b/dotnet/archive/2014/06/20/would-you-like-a-multidictionary.aspx

      我是这个包的开发者,所以如果您对性能或任何问题有任何疑问,请在此处或 MSDN 上告诉我。

      希望对您有所帮助。

      更新

      MultiValueDictionary 现在位于corefxlab repo,您可以从this MyGet 提要获取 NuGet 包。

      【讨论】:

      • 看起来它现在被称为 MultiValueDictionary。我想使用它,但不确定它的未来。博客已经 3 年没有更新了。关于这是否可以安全使用的任何想法?
      • 我没有尝试过MultiValueDictionary,但它实现了不可变的IReadOnlyDictionary。无论如何,我已经更新了您的答案,看起来该工具已移至 corefxlab 存储库。
      • 第二个链接坏了:"403 Forbidden"
      • 应该删除过时的内容(这里有完整的修订历史)——而不是“更新”部分。请change your answer没有“编辑:”、“更新:”或类似的 - 答案应该看起来好像是今天写的)。
      【解决方案4】:

      使用这个:

      Dictionary<TKey, Tuple<TValue1, TValue2, TValue3, ...>>
      

      【讨论】:

      • 知道将值添加到字典的语法会很方便:)
      • 哦,我明白了something.Add(TKey, new Tuple&lt;int, string&gt;(TValue1, TValue2))
      【解决方案5】:

      您的字典的值类型可以是 List,或其他包含多个对象的类。类似的东西

      Dictionary<int, List<string>>
      

      对于以整数为键并保存字符串列表的字典。

      选择值类型的一个主要考虑因素是您将使用字典的目的。如果您必须对这些值进行搜索或其他操作,那么也许可以考虑使用一种数据结构来帮助您做您想做的事情——比如HashSet

      【讨论】:

        【解决方案6】:

        您可以使用Dictionary&lt;TKey, List&lt;TValue&gt;&gt;

        这将允许每个键引用值的列表

        【讨论】:

          【解决方案7】:

          使用列表字典(或其他类型的集合),例如:

          var myDictionary = new Dictionary<string, IList<int>>();
          
          myDictionary["My key"] = new List<int> {1, 2, 3, 4, 5};
          

          【讨论】:

            【解决方案8】:

            您可以创建一个非常简单的多字典,它会自动处理插入值,如下所示:

            public class MultiDictionary<TKey, TValue> : Dictionary<TKey, List<TValue>>
            {
                public void Add(TKey key, TValue value)
                {
                    if (TryGetValue(key, out List<TValue> valueList)) {
                        valueList.Add(value);
                    } else {
                        Add(key, new List<TValue> { value });
                    }
                }
            }
            

            这会创建Add 方法的重载版本。如果尚不存在此条目的条目,则原始条目允许您插入一个键的项目列表。此版本允许您在任何情况下插入单个项目。

            如果您不想有重复值,也可以将其基于 Dictionary&lt;TKey, HashSet&lt;TValue&gt;&gt;

            【讨论】:

              【解决方案9】:

              您可以拥有一个包含集合(或任何其他类型/类)作为值的字典。这样一来,您就拥有一个键并将值存储在您的集合中。

              【讨论】:

                【解决方案10】:

                你也可以使用;

                 List<KeyValuePair<string, string>> Mappings;
                

                【讨论】:

                  【解决方案11】:

                  看看微软的MultiValueDictionary

                  示例代码:

                  MultiValueDictionary<string, string> Parameters = new MultiValueDictionary<string, string>();
                  
                  Parameters.Add("Malik", "Ali");
                  Parameters.Add("Malik", "Hamza");
                  Parameters.Add("Malik", "Danish");
                  
                  //Parameters["Malik"] now contains the values Ali, Hamza, and Danish
                  

                  【讨论】:

                  • 那么你如何检索这些值/元素?不使用循环
                  • 这类似于Ian Hays's answer
                  【解决方案12】:

                  .NET 字典的键和值只有一对一的关系。但这并不意味着一个值不能是另一个数组/列表/字典。

                  我想不出在字典中有一对多关系的理由,但显然有一个。

                  如果您想将不同类型的数据存储到一个键中,那么这听起来是创建自己的类的理想时机。然后你有一个一对一的关系,但是你有一个值类存储更多的数据。

                  【讨论】:

                    【解决方案13】:

                    这是我实现此行为的方法。

                    有关涉及ILookup&lt;TKey, TElement&gt; 的更全面的解决方案,请查看my other answer

                    public abstract class Lookup<TKey, TElement> : KeyedCollection<TKey, ICollection<TElement>>
                    {
                      protected override TKey GetKeyForItem(ICollection<TElement> item) =>
                        item
                        .Select(b => GetKeyForItem(b))
                        .Distinct()
                        .SingleOrDefault();
                    
                      protected abstract TKey GetKeyForItem(TElement item);
                    
                      public void Add(TElement item)
                      {
                        var key = GetKeyForItem(item);
                        if (Dictionary != null && Dictionary.TryGetValue(key, out var collection))
                          collection.Add(item);
                        else
                          Add(new List<TElement> { item });
                      }
                    
                      public void Remove(TElement item)
                      {
                        var key = GetKeyForItem(item);
                        if (Dictionary != null && Dictionary.TryGetValue(key, out var collection))
                        {
                          collection.Remove(item);
                          if (collection.Count == 0)
                            Remove(key);
                        }
                      }
                    }
                    

                    用法:

                    public class Item
                    {
                      public string Key { get; }
                      public string Value { get; set; }
                      public Item(string key, string value = null) { Key = key; Value = value; }
                    }
                    
                    public class Lookup : Lookup<string, Item>
                    {
                      protected override string GetKeyForItem(Item item) => item.Key;
                    }
                    
                    static void Main(string[] args)
                    {
                      var toRem = new Item("1", "different");
                      var single = new Item("2", "single");
                      var lookup = new Lookup()
                      {
                        new Item("1", "hello"),
                        new Item("1", "hello2"),
                        new Item(""),
                        new Item("", "helloo"),
                        toRem,
                        single
                      };
                    
                      lookup.Remove(toRem);
                      lookup.Remove(single);
                    }
                    

                    注意:密钥必须是不可变的(或在密钥更改时删除并重新添加)。

                    【讨论】:

                      【解决方案14】:

                      正确的解决方案是使用Dictionary&lt;TKey1, TKey2, TValue&gt;,其中需要 2 个密钥才能访问某个项目。使用Dictionary&lt;TKey, List&lt;TValue&gt;&gt; 的解决方案将创建与 TKey 的唯一值一样多的列表,这会占用大量内存并降低性能。只有 1 个密钥时的另一个问题是很难删除一个特定项目。

                      由于找不到这样的类,我自己写了一个:

                        public class SortedBucketCollectionClass<TKey1, TKey2, TValue>:
                          IEnumerable<TValue>, ICollection<TValue>, 
                          IReadOnlySortedBucketCollection<TKey1, TKey2, TValue>
                          where TKey1 : notnull, IComparable<TKey1>
                          where TKey2 : notnull, IComparable<TKey2>
                          where TValue : class {...}
                      

                      它只支持使用 TKey1 访问,它返回一个枚举器,包含所有具有 TKey1 的项目,以及使用 TKey1、TKEy2 访问,它返回一个特定项目。还有一个枚举器用于所有存储的项目,一个枚举具有一定范围的 TKey 的所有项目。这很方便,当 TKey1 是 DateTime 并且想要某个周、月或年的所有项目时。

                      我用代码示例写了一篇关于 CodeProject 的详细文章: SortedBucketCollection: A memory efficient SortedList accepting multiple items with the same key

                      您可以在 CodeProject 或 Github 上获取源代码: StorageLib/StorageLib/SortedBucketCollection.cs

                      【讨论】:

                        【解决方案15】:

                        你可以声明一个&lt;T,T[]&gt;类型的字典, (当 T = 你想要的任何类型时) 初始化字典项的值时,为每个键声明一个数组。

                        例如:

                         `Dictionary<int, string[]> dictionaty  = new Dictionary<int, string[]>() {
                                        {1, new string[]{"a","b","c"} },
                                        {2, new string[]{"222","str"} }
                                    }; `
                        

                        【讨论】:

                        • 虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高​​答案的长期价值。
                        • 解释一下。例如,想法/要点是什么?请通过editing (changing) your answer 回复,而不是在 cmets 中(without "Edit:"、"Update:" 或类似的 - 答案应该看起来像是今天写的)。
                        猜你喜欢
                        • 1970-01-01
                        • 2012-07-19
                        • 2013-02-05
                        • 1970-01-01
                        • 1970-01-01
                        • 2023-03-24
                        • 1970-01-01
                        • 2015-06-20
                        相关资源
                        最近更新 更多