.NET字典中的重复键?

.NET基类库中是否存在允许使用重复键的字典类?我找到的唯一解决方案是创建,例如,一个类:

Dictionary<string, List<object>>

但实际上用起来很烦人。在Java中,我相信MultiMap可以实现这一点,但在. net中找不到类似的东西。

271678 次浏览

我认为像List<KeyValuePair<object, object>>这样的东西可以做这项工作。

重复的键将破坏Dictionary的整个契约。在字典中,每个键都是唯一的,并映射到单个值。如果您想要将一个对象链接到任意数量的其他对象,最好的方法可能是类似于DataSet的东西(通常的说法是表)。把键放在一列,值放在另一列。这比字典要慢得多,但这是为了失去散列键对象的能力而付出的代价。

你是说完全一致而不是完全重复吗?否则哈希表将无法工作。

同余意味着两个单独的键可以哈希到等价的值,但键不相等。

例如:假设你的哈希表的哈希函数是hashval = key mod 3。1和4都映射到1,但是是不同的值。这就是列表的概念发挥作用的地方。

当需要查找1时,该值被哈希为1,遍历列表,直到找到Key = 1。

如果允许插入重复的键,则无法区分哪些键映射到哪些值。

如果你同时使用字符串作为键和值,你可以使用System.Collections.Specialized.NameValueCollection,它将通过GetValues(string key)方法返回一个字符串值数组。

我刚刚遇到了PowerCollections库,其中包括一个名为MultiDictionary的类。它巧妙地包装了这种类型的功能。

NameValueCollection支持一个键(也是字符串)下的多个字符串值,但这是我所知道的唯一一个例子。

当我遇到需要这种功能的情况时,我倾向于创建类似于您示例中的结构。

如果你使用。net 3.5,使用Lookup类。

编辑:通常使用Enumerable.ToLookup创建Lookup。这确实假设你不需要更改它之后-但我通常发现这已经足够好了。

如果对你有用,我不认为框架中有任何东西会有帮助-使用字典是最好的:(

关于使用Lookup的非常重要的注意事项:

可以通过在实现IEnumerable(T)的对象上调用ToLookup来创建Lookup(TKey, TElement)的实例

没有公共构造函数来创建Lookup(TKey, TElement)的新实例。此外,Lookup(TKey, TElement)对象是不可变的,也就是说,在Lookup(TKey, TElement)对象被创建之后,你不能从它中添加或删除元素或键。

(from MSDN)

我认为这对大多数人来说都是一种阻碍。

List类实际上适用于包含重复项的键/值集合,在此集合上进行迭代。例子:

List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>();


// add some values to the collection here


for (int i = 0;  i < list.Count;  i++)
{
Print(list[i].Key, list[i].Value);
}

回答了最初的问题。类似Dictionary<string, List<object>>的东西是在Code Project中名为MultiMap的类中实现的。

你可以在下面的链接找到更多信息: http://www.codeproject.com/KB/cs/MultiKeyDictionary.aspx < / p >

我无意中发现了这篇文章,寻找相同的答案,但没有找到,所以我使用字典列表组装了一个简单的示例解决方案,覆盖[]操作符,当所有其他字典都有一个给定的键(set)时,将一个新字典添加到列表中,并返回一个值列表(get) 它既丑陋又低效,它只通过键获取/设置,并且总是返回一个列表,但它是有效的:

 class DKD {
List<Dictionary<string, string>> dictionaries;
public DKD(){
dictionaries = new List<Dictionary<string, string>>();}
public object this[string key]{
get{
string temp;
List<string> valueList = new List<string>();
for (int i = 0; i < dictionaries.Count; i++){
dictionaries[i].TryGetValue(key, out temp);
if (temp == key){
valueList.Add(temp);}}
return valueList;}
set{
for (int i = 0; i < dictionaries.Count; i++){
if (dictionaries[i].ContainsKey(key)){
continue;}
else{
dictionaries[i].Add(key,(string) value);
return;}}
dictionaries.Add(new Dictionary<string, string>());
dictionaries.Last()[key] =(string)value;
}
}
}
当使用List<KeyValuePair<string, object>>选项时,你可以使用LINQ来进行搜索:

List<KeyValuePair<string, object>> myList = new List<KeyValuePair<string, object>>();
//fill it here
var q = from a in myList Where a.Key.Equals("somevalue") Select a.Value
if(q.Count() > 0){ //you've got your value }

下面是一种使用List<KeyValuePair<字符串,字符串> >

public class ListWithDuplicates : List<KeyValuePair<string, string>>
{
public void Add(string key, string value)
{
var element = new KeyValuePair<string, string>(key, value);
this.Add(element);
}
}


var list = new ListWithDuplicates();
list.Add("k1", "v1");
list.Add("k1", "v2");
list.Add("k1", "v3");


foreach(var item in list)
{
string x = string.format("{0}={1}, ", item.Key, item.Value);
}

输出k1=v1, k1=v2, k1=v3

我用的方法是

Dictionary<string, List<string>>

这样,您就有一个键保存一个字符串列表。

例子:

List<string> value = new List<string>();
if (dictionary.Contains(key)) {
value = dictionary[key];
}
value.Add(newValue);

如果你正在使用>= .NET 4,那么你可以使用Tuple类:

// declaration
var list = new List<Tuple<string, List<object>>>();


// to add an item to the list
var item = Tuple<string, List<object>>("key", new List<object>);
list.Add(item);


// to iterate
foreach(var i in list)
{
Console.WriteLine(i.Item1.ToString());
}

你可以添加相同的键与不同的情况,如:

< p > key1
Key1
KEY1
KeY1
kEy1
keY1
< / p >

我知道这是个愚蠢的答案,但对我来说很管用。

这也是可能的:

Dictionary<string, string[]> previousAnswers = null;

这样,我们就有了唯一的键。希望这对你有用。

你可以定义一个方法来构建复合字符串键 在任何你想要使用字典的地方,你都必须使用这个方法来构建你的键 例如:< / p >
private string keyBuilder(int key1, int key2)
{
return string.Format("{0}/{1}", key1, key2);
}

使用:

myDict.ContainsKey(keyBuilder(key1, key2))

“滚动您自己的”版本的字典,允许“重复键”条目,这很容易。下面是一个粗略的简单实现。你可能想要考虑添加对IDictionary<T>上基本大部分(如果不是全部)的支持。

public class MultiMap<TKey,TValue>
{
private readonly Dictionary<TKey,IList<TValue>> storage;


public MultiMap()
{
storage = new Dictionary<TKey,IList<TValue>>();
}


public void Add(TKey key, TValue value)
{
if (!storage.ContainsKey(key)) storage.Add(key, new List<TValue>());
storage[key].Add(value);
}


public IEnumerable<TKey> Keys
{
get { return storage.Keys; }
}


public bool ContainsKey(TKey key)
{
return storage.ContainsKey(key);
}


public IList<TValue> this[TKey key]
{
get
{
if (!storage.ContainsKey(key))
throw new KeyNotFoundException(
string.Format(
"The given key {0} was not found in the collection.", key));
return storage[key];
}
}
}

一个关于如何使用它的简单例子:

const string key = "supported_encodings";
var map = new MultiMap<string,Encoding>();
map.Add(key, Encoding.ASCII);
map.Add(key, Encoding.UTF8);
map.Add(key, Encoding.Unicode);


foreach (var existingKey in map.Keys)
{
var values = map[existingKey];
Console.WriteLine(string.Join(",", values));
}

这是一个两种方式的并行字典,我认为这将帮助你:

public class HashMapDictionary<T1, T2> : System.Collections.IEnumerable
{
private System.Collections.Concurrent.ConcurrentDictionary<T1, List<T2>> _keyValue = new System.Collections.Concurrent.ConcurrentDictionary<T1, List<T2>>();
private System.Collections.Concurrent.ConcurrentDictionary<T2, List<T1>> _valueKey = new System.Collections.Concurrent.ConcurrentDictionary<T2, List<T1>>();


public ICollection<T1> Keys
{
get
{
return _keyValue.Keys;
}
}


public ICollection<T2> Values
{
get
{
return _valueKey.Keys;
}
}


public int Count
{
get
{
return _keyValue.Count;
}
}


public bool IsReadOnly
{
get
{
return false;
}
}


public List<T2> this[T1 index]
{
get { return _keyValue[index]; }
set { _keyValue[index] = value; }
}


public List<T1> this[T2 index]
{
get { return _valueKey[index]; }
set { _valueKey[index] = value; }
}


public void Add(T1 key, T2 value)
{
lock (this)
{
if (!_keyValue.TryGetValue(key, out List<T2> result))
_keyValue.TryAdd(key, new List<T2>() { value });
else if (!result.Contains(value))
result.Add(value);


if (!_valueKey.TryGetValue(value, out List<T1> result2))
_valueKey.TryAdd(value, new List<T1>() { key });
else if (!result2.Contains(key))
result2.Add(key);
}
}


public bool TryGetValues(T1 key, out List<T2> value)
{
return _keyValue.TryGetValue(key, out value);
}


public bool TryGetKeys(T2 value, out List<T1> key)
{
return _valueKey.TryGetValue(value, out key);
}


public bool ContainsKey(T1 key)
{
return _keyValue.ContainsKey(key);
}


public bool ContainsValue(T2 value)
{
return _valueKey.ContainsKey(value);
}


public void Remove(T1 key)
{
lock (this)
{
if (_keyValue.TryRemove(key, out List<T2> values))
{
foreach (var item in values)
{
var remove2 = _valueKey.TryRemove(item, out List<T1> keys);
}
}
}
}


public void Remove(T2 value)
{
lock (this)
{
if (_valueKey.TryRemove(value, out List<T1> keys))
{
foreach (var item in keys)
{
var remove2 = _keyValue.TryRemove(item, out List<T2> values);
}
}
}
}


public void Clear()
{
_keyValue.Clear();
_valueKey.Clear();
}


IEnumerator IEnumerable.GetEnumerator()
{
return _keyValue.GetEnumerator();
}
}

例子:

public class TestA
{
public int MyProperty { get; set; }
}


public class TestB
{
public int MyProperty { get; set; }
}


HashMapDictionary<TestA, TestB> hashMapDictionary = new HashMapDictionary<TestA, TestB>();


var a = new TestA() { MyProperty = 9999 };
var b = new TestB() { MyProperty = 60 };
var b2 = new TestB() { MyProperty = 5 };
hashMapDictionary.Add(a, b);
hashMapDictionary.Add(a, b2);
hashMapDictionary.TryGetValues(a, out List<TestB> result);
foreach (var item in result)
{
//do something
}

我将@Hector Correa的答案更改为具有泛型类型的扩展,并添加了一个自定义TryGetValue。

  public static class ListWithDuplicateExtensions
{
public static void Add<TKey, TValue>(this List<KeyValuePair<TKey, TValue>> collection, TKey key, TValue value)
{
var element = new KeyValuePair<TKey, TValue>(key, value);
collection.Add(element);
}


public static int TryGetValue<TKey, TValue>(this List<KeyValuePair<TKey, TValue>> collection, TKey key, out IEnumerable<TValue> values)
{
values = collection.Where(pair => pair.Key.Equals(key)).Select(pair => pair.Value);
return values.Count();
}
}

我使用这个简单的类:

public class ListMap<T,V> : List<KeyValuePair<T, V>>
{
public void Add(T key, V value) {
Add(new KeyValuePair<T, V>(key, value));
}


public List<V> Get(T key) {
return FindAll(p => p.Key.Equals(key)).ConvertAll(p=> p.Value);
}
}

用法:

var fruits = new ListMap<int, string>();
fruits.Add(1, "apple");
fruits.Add(1, "orange");
var c = fruits.Get(1).Count; //c = 2;

你可以创建自己的字典包装器,就像这样,作为奖励,它支持空值作为键:

/// <summary>
/// Dictionary which supports duplicates and null entries
/// </summary>
/// <typeparam name="TKey">Type of key</typeparam>
/// <typeparam name="TValue">Type of items</typeparam>
public class OpenDictionary<TKey, TValue>
{
private readonly Lazy<List<TValue>> _nullStorage = new Lazy<List<TValue>>(
() => new List<TValue>());


private readonly Dictionary<TKey, List<TValue>> _innerDictionary =
new Dictionary<TKey, List<TValue>>();


/// <summary>
/// Get all entries
/// </summary>
public IEnumerable<TValue> Values =>
_innerDictionary.Values
.SelectMany(x => x)
.Concat(_nullStorage.Value);


/// <summary>
/// Add an item
/// </summary>
public OpenDictionary<TKey, TValue> Add(TKey key, TValue item)
{
if (ReferenceEquals(key, null))
_nullStorage.Value.Add(item);
else
{
if (!_innerDictionary.ContainsKey(key))
_innerDictionary.Add(key, new List<TValue>());


_innerDictionary[key].Add(item);
}


return this;
}


/// <summary>
/// Remove an entry by key
/// </summary>
public OpenDictionary<TKey, TValue> RemoveEntryByKey(TKey key, TValue entry)
{
if (ReferenceEquals(key, null))
{
int targetIdx = _nullStorage.Value.FindIndex(x => x.Equals(entry));
if (targetIdx < 0)
return this;


_nullStorage.Value.RemoveAt(targetIdx);
}
else
{
if (!_innerDictionary.ContainsKey(key))
return this;


List<TValue> targetChain = _innerDictionary[key];
if (targetChain.Count == 0)
return this;


int targetIdx = targetChain.FindIndex(x => x.Equals(entry));
if (targetIdx < 0)
return this;


targetChain.RemoveAt(targetIdx);
}


return this;
}


/// <summary>
/// Remove all entries by key
/// </summary>
public OpenDictionary<TKey, TValue> RemoveAllEntriesByKey(TKey key)
{
if (ReferenceEquals(key, null))
{
if (_nullStorage.IsValueCreated)
_nullStorage.Value.Clear();
}
else
{
if (_innerDictionary.ContainsKey(key))
_innerDictionary[key].Clear();
}


return this;
}


/// <summary>
/// Try get entries by key
/// </summary>
public bool TryGetEntries(TKey key, out IReadOnlyList<TValue> entries)
{
entries = null;


if (ReferenceEquals(key, null))
{
if (_nullStorage.IsValueCreated)
{
entries = _nullStorage.Value;
return true;
}
else return false;
}
else
{
if (_innerDictionary.ContainsKey(key))
{
entries = _innerDictionary[key];
return true;
}
else return false;
}
}
}

用法示例:

var dictionary = new OpenDictionary<string, int>();
dictionary.Add("1", 1);
// The next line won't throw an exception;
dictionary.Add("1", 2);


dictionary.TryGetEntries("1", out List<int> result);
// result is { 1, 2 }


dictionary.Add(null, 42);
dictionary.Add(null, 24);
dictionary.TryGetEntries(null, out List<int> result);
// result is { 42, 24 }

由于新的c#(我相信它是从7.0开始的),你也可以做这样的事情:

var duplicatedDictionaryExample = new List<(string Key, string Value)> { ("", "") ... }

你使用它作为一个标准的列表,但有两个值命名为任何你想

foreach(var entry in duplicatedDictionaryExample)
{
// do something with the values
entry.Key;
entry.Value;
}