【发布时间】:2011-01-07 05:57:40
【问题描述】:
我想创建一个数据存储来允许我存储一些数据。
第一个想法是创建一个字典,其中有一个键和多个值,有点像一对多的关系。
我认为字典只有一个键值。
我还能如何存储这些信息?
【问题讨论】:
标签: c# .net dictionary datasource
我想创建一个数据存储来允许我存储一些数据。
第一个想法是创建一个字典,其中有一个键和多个值,有点像一对多的关系。
我认为字典只有一个键值。
我还能如何存储这些信息?
【问题讨论】:
标签: c# .net dictionary datasource
从 .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<string, HashSet<string>> 而不是Dictionary<string, List<string>>?列表不能保证给定键在值集合中的唯一性,但集合可以。
.Distinct() 它们。如果您需要多重集或保持项目有序或索引,则列表似乎是合理的。
HashSet 类也给出了非常清晰的向我的代码的读者发出信号(他们不必进入我的实现就可以看到我在做什么)。
ToLookup 和ILookup 与基于Dictionary 的实现的用法的样板。
您可以为第二种泛型类型使用列表。例如以字符串为键的字符串字典:
Dictionary<string, List<string>> myDict;
【讨论】:
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,但它实现了不可变的IReadOnlyDictionary。无论如何,我已经更新了您的答案,看起来该工具已移至 corefxlab 存储库。
使用这个:
Dictionary<TKey, Tuple<TValue1, TValue2, TValue3, ...>>
【讨论】:
something.Add(TKey, new Tuple<int, string>(TValue1, TValue2))
您的字典的值类型可以是 List,或其他包含多个对象的类。类似的东西
Dictionary<int, List<string>>
对于以整数为键并保存字符串列表的字典。
选择值类型的一个主要考虑因素是您将使用字典的目的。如果您必须对这些值进行搜索或其他操作,那么也许可以考虑使用一种数据结构来帮助您做您想做的事情——比如HashSet。
【讨论】:
您可以使用Dictionary<TKey, List<TValue>>。
这将允许每个键引用值的列表。
【讨论】:
使用列表字典(或其他类型的集合),例如:
var myDictionary = new Dictionary<string, IList<int>>();
myDictionary["My key"] = new List<int> {1, 2, 3, 4, 5};
【讨论】:
您可以创建一个非常简单的多字典,它会自动处理插入值,如下所示:
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<TKey, HashSet<TValue>>。
【讨论】:
您可以拥有一个包含集合(或任何其他类型/类)作为值的字典。这样一来,您就拥有一个键并将值存储在您的集合中。
【讨论】:
你也可以使用;
List<KeyValuePair<string, string>> Mappings;
【讨论】:
看看微软的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
【讨论】:
.NET 字典的键和值只有一对一的关系。但这并不意味着一个值不能是另一个数组/列表/字典。
我想不出在字典中有一对多关系的理由,但显然有一个。
如果您想将不同类型的数据存储到一个键中,那么这听起来是创建自己的类的理想时机。然后你有一个一对一的关系,但是你有一个值类存储更多的数据。
【讨论】:
这是我实现此行为的方法。
有关涉及ILookup<TKey, TElement> 的更全面的解决方案,请查看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);
}
注意:密钥必须是不可变的(或在密钥更改时删除并重新添加)。
【讨论】:
正确的解决方案是使用Dictionary<TKey1, TKey2, TValue>,其中需要 2 个密钥才能访问某个项目。使用Dictionary<TKey, List<TValue>> 的解决方案将创建与 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
【讨论】:
你可以声明一个<T,T[]>类型的字典,
(当 T = 你想要的任何类型时)
初始化字典项的值时,为每个键声明一个数组。
例如:
`Dictionary<int, string[]> dictionaty = new Dictionary<int, string[]>() {
{1, new string[]{"a","b","c"} },
{2, new string[]{"222","str"} }
}; `
【讨论】: