.NET词典中的重复键?


256

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

Dictionary<string, List<object>>

但这实际上使人恼火。在Java中,我相信MultiMap可以完成此任务,但是无法在.NET中找到类似物。


3
这个重复键怎么样,它是重复值(列表),对吗?
Shamim Hafiz 2010年

1
@ShamimHafiz,不,这些值不必重复。如果你有存储复本{ a, 1 },并{ a, 2 }在哈希表,其中a是关键,一个选择是有{ a, [1, 2] }
nawfal 2014年

1
实际上,我相信这里真正想要的是一个集合,其中每个键都可以映射到一个或多个值。我认为“重复键”一词并没有真正传达出这一点。
DavidRR 2014年

为了将来参考,您应该考虑保持1个键只是向其中添加值,而不是一遍又一遍地添加相同的键。
Ya Wang

如果键和值都是字符串,则存在NameValueCollection(可将多个字符串值与每个字符串键关联)。
AnorZaken

Answers:


228

如果您使用的是.NET 3.5,请使用Lookup该类。

编辑:您通常创建一个Lookupusing Enumerable.ToLookup。这确实假定您以后不需要更改它-但我通常会发现它足够好。

如果为你工作,我不认为有一个在任何框架,这将有助于-并使用字典是尽善尽美:(


感谢您对Lookup的注意。它提供了一种很好的方法,可以对不是标准orderby条件的linq查询的结果进行分区(分组)。
罗伯特·保尔森

3
@Josh:您使用Enumerable.ToLookup创建一个。
乔恩·斯基特

29
警告Lookup不可序列化
SliverNinja-MSFT 2011年

1
我们应该如何向该Lookup添加项目?
Moldovanu 2012年

171

实际上,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);
}

31
该解决方案在功能上有效,但是List的实现不知道键或值,也根本无法优化搜索键
Spencer Rose

41

这是使用List <KeyValuePair <string,string>>的一种方法

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


在我的情况下效果很好,并且易于使用。
user6121177

21

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


6
NameValueCollection不允许多个键。
珍妮·奥雷利

@Jenny O'Reilly:当然,您可以添加重复的密钥。
isHuman

严格来说@ JennyO'Reilly是正确的,因为链接的MSDN页面上的注释明确指出:此类在单个键下存储多个字符串值
罗纳德

它将允许但将返回多个值,我尝试使用索引和键。
user6121177

18

我刚遇到PowerCollections库,该库除其他外包括一个称为MultiDictionary的类。这巧妙地包装了这种类型的功能。


14

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

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

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

(来自MSDN)

我认为这将是大多数用途的止挡物。


6
我能想到的极少数用途是将其作为展示用塞子。但是,我认为不变的对象很棒。
乔尔·穆勒

1
@JoelMueller我可以想到很多情况下,这是一个表演停止者。必须重新创建字典以添加项目并不是一种特别有效的解决方案……
reirab 2014年

12

我认为类似List<KeyValuePair<object, object>>的工作会做。


您如何通过关键字查找该列表中的内容?
韦恩·布鲁斯

您必须遍历它:但是我不知道.NET 3.5的LookUp-Class:也许这对于搜索其内容更有用。
MADMap

2
@wizlib:唯一的方法是遍历列表,其效率几乎不及哈希。-1
彼得·k。

@petrk。那真的取决于您的数据是什么。之所以使用此实现,是因为我只有很少的唯一键,并且不想招致哈希冲突的开销。+1
Evan M

9

如果使用> = .NET 4,则可以使用TupleClass:

// 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());
}

这看起来像List<KeyValuePair<key, value>>上面的解决方案。我错了吗?
弥敦道(Nathan Goings)

6

“滚动自己的”字典版本允许“重复键”条目很容易。这是一个简单的简单实现。您可能要考虑增加对上大多数(如果不是全部)的支持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));
}


3

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

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


3

使用该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 }

2
是的,但这并不能使其更快(仍然没有哈希)
Haukman 2011年

3

由于使用了新的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;
}

2

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

Congruent表示两个单独的键可以哈希为等效值,但键不相等。

例如:假设您的哈希表的哈希函数只是hashval = key mod3。1和4都映射为1,但是值不同。这就是您的列表想法发挥作用的地方。

当您需要查找1时,该值将散列为1,则遍历该列表,直到找到Key = 1。

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


2
哈希表已经处理了碰巧哈希到相同值的键(这称为冲突)。我指的是您要将多个值映射到相同的确切键的情况。

2

我使用的方式仅仅是

Dictionary<string, List<string>>

这样,您就可以拥有一个包含字符串列表的单个键。

例:

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

很好很干净。
杰里·尼克松

这是处理我的用例的绝佳解决方案。
dub stylee

1

我偶然发现了该帖子以寻找相同的答案,但没有找到答案,因此我使用字典列表来组装一个简单的示例解决方案,并在所有其他单词都包含[]的情况下覆盖[]运算符以将新字典添加到列表给定键(集合),并返回值列表(获取)。
它很丑陋且效率低下,它只能通过键来获取/设置,并且总是返回一个列表,但是它可以工作:

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

1

我将@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();
    }
  }

0

这是拖曳方式并发字典,我认为这对您有帮助:

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
            }

0

我使用这个简单的类:

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;

0

您可以创建自己的字典包装,类似这样的包装,作为奖励,它支持空值作为键:

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

您能否围绕代码的功能,代码如何回答问题以及一些示例用法进行一些解释?
谜团

@Enigmativity,它确实按照要求进行了操作,问题是建议一些支持重复的字典,因此我提出围绕.net字典创建一个包装器,以支持此功能,并举例说明创建此类包装器,作为奖励,它可以将null作为密钥来处理(即使是不好的做法,当然也可以)。用法非常简单: 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 }
Alexander Tolstikov

您可以在答案中添加细节吗?
谜团

@谜题,如果您要回答的是原始答案,那么可以肯定
亚历山大·托尔斯蒂科夫

-1

您可以定义一个方法来在您要使用字典的任何地方构建复合字符串键,您必须使用此方法来构建您的键,例如:

private string keyBuilder(int key1, int key2)
{
    return string.Format("{0}/{1}", key1, key2);
}

用于:

myDict.ContainsKey(keyBuilder(key1, key2))

-3

重复的密钥破坏了字典的整个契约。在字典中,每个键都是唯一的,并映射到单个值。如果要将一个对象链接到任意数量的其他对象,最好的选择可能是类似于DataSet(通常是表)。将密钥放在一列中,将值放在另一列中。这比字典慢得多,但这是您失去对关键对象进行哈希处理的能力的折衷方案。


5
使用字典来提高性能不是全部目的吗?使用DataSet似乎并不比List <KeyValuePair <T,U >>好。
道格

-4

这也是可能的:

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

这样,我们可以拥有唯一的键。希望这对您有用。


OP要求提供允许重复键的字典。
Skaparate

-10

您可以使用不同的大小写添加相同的键,例如:

键1
键1
KEY1
键1
键1
键1

我知道是假的答案,但为我工作。


4
不,它对您不起作用。字典允许按键进行非常快速的查找-也被归类为O(1),当添加大小写不同的多个键时,您会丢失查找的原因,因为如何检索它们?尝试每种大小写组合吗?无论您如何执行,其性能都不会像普通的单个字典查找那样。除此之外,还有其他更明显的缺点,例如每个键的值限制,具体取决于键中可转换字符的数量。
尤金·别列索夫斯基
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.