C#字典 如何为单个键添加多个值?

35

我已经创建了字典对象

Dictionary<string, List<string>> dictionary =
    new Dictionary<string,List<string>>();

我想要为给定的单个键添加字符串值到字符串列表中。如果该键不存在,则必须添加一个新键。List<string>没有预定义,我的意思是我没有创建任何列表对象并将其提供给dictionary.Add("key",Listname)。如何动态创建此列表对象,并将字符串添加到此列表中?如果我需要添加100个键,那么在执行dictionary.Add指令之前,我是否需要创建100个列表,还需要定义这些列表的内容吗?
谢谢。

1
еҫҲйҒ—жҶҫ他们没жңүеҢ…еҗ«еҸҜеҸҳзҡ„Lookupе®һзҺ°гҖӮеӨ§йғЁеҲҶйҖ»иҫ‘е·Із»ҸеӯҳеңЁпјҢеҸӘжҳҜж— жі•ж·»еҠ йЎ№зӣ®гҖӮ - Jeff Mercado
13个回答

42

更新: 在你拥有列表的情况下,使用TryGetValue 来检查存在性,以便只进行一次查找:

List<int> list;

if (!dictionary.TryGetValue("foo", out list))
{
    list = new List<int>();
    dictionary.Add("foo", list);
}

list.Add(2);


翻译: 检查是否存在,如果不存在则添加,然后通过键(key)访问字典(dictionary)获取列表(list),并像平常一样将元素添加到列表中:

var dictionary = new Dictionary<string, List<int>>();

if (!dictionary.ContainsKey("foo"))
    dictionary.Add("foo", new List<int>());

dictionary["foo"].Add(42);
dictionary["foo"].AddRange(oneHundredInts);

或者像您的情况一样使用List<string>

另外,如果您知道将要添加到动态集合(例如List<T>)中的项目数量,建议使用带有初始列表容量的构造函数:new List<int>(100);

这将提前获取满足指定容量所需的内存,而不是每次开始填充时都获取小块。如果您知道您有100个键,则可以对字典执行相同操作。


这个(总是)需要2次查找。 - H H
2
使用 TryGetValue 比 ContainsKey 和重新索引字典更高效。 - roken
1
@Roken 我知道,但这不是问题的关键。我从来没有看到过使用字典这种方式会导致任何值得的性能问题。最多只是过早或微观优化。 - Adam Houldsworth
1
@AdamHouldsworth 我们在纳秒级别上对我们的代码进行基准测试。仅因为您看不到最高效解决方案的价值,这并不意味着这对于在不同领域工作的其他人没有价值。 - roken
4
如果你不能个人控制C#、.NET和CLR,我就不明白为什么你要使用它们。在这种语言中,我不会过分担心一两秒钟的差距;请不要误解我的回答,我当然看得到这些东西的价值,只是相比于其他因素,我更加重视它们。 - Adam Houldsworth

10

如果我理解你的要求:

dictionary.Add("key", new List<string>()); 
稍后...
dictionary["key"].Add("string to your list");

7
Dictionary<string, List<string>> dictionary = new Dictionary<string,List<string>>();

foreach(string key in keys) {
    if(!dictionary.ContainsKey(key)) {
        //add
        dictionary.Add(key, new List<string>());
    }
    dictionary[key].Add("theString");
}

如果这个键不存在,就会添加一个新的列表(在if内部)。否则,这个键已经存在,只需要在该键下添加一个新值到列表中。

4
您可以使用我的多重映射实现,它派生自一个Dictionary<K, List<V>>。虽然不是完美的,但它做得很好。
/// <summary>
/// Represents a collection of keys and values.
/// Multiple values can have the same key.
/// </summary>
/// <typeparam name="TKey">Type of the keys.</typeparam>
/// <typeparam name="TValue">Type of the values.</typeparam>
public class MultiMap<TKey, TValue> : Dictionary<TKey, List<TValue>>
{

    public MultiMap()
        : base()
    {
    }

    public MultiMap(int capacity)
        : base(capacity)
    {
    }

    /// <summary>
    /// Adds an element with the specified key and value into the MultiMap. 
    /// </summary>
    /// <param name="key">The key of the element to add.</param>
    /// <param name="value">The value of the element to add.</param>
    public void Add(TKey key, TValue value)
    {
        List<TValue> valueList;

        if (TryGetValue(key, out valueList)) {
            valueList.Add(value);
        } else {
            valueList = new List<TValue>();
            valueList.Add(value);
            Add(key, valueList);
        }
    }

    /// <summary>
    /// Removes first occurence of an element with a specified key and value.
    /// </summary>
    /// <param name="key">The key of the element to remove.</param>
    /// <param name="value">The value of the element to remove.</param>
    /// <returns>true if the an element is removed;
    /// false if the key or the value were not found.</returns>
    public bool Remove(TKey key, TValue value)
    {
        List<TValue> valueList;

        if (TryGetValue(key, out valueList)) {
            if (valueList.Remove(value)) {
                if (valueList.Count == 0) {
                    Remove(key);
                }
                return true;
            }
        }
        return false;
    }

    /// <summary>
    /// Removes all occurences of elements with a specified key and value.
    /// </summary>
    /// <param name="key">The key of the elements to remove.</param>
    /// <param name="value">The value of the elements to remove.</param>
    /// <returns>Number of elements removed.</returns>
    public int RemoveAll(TKey key, TValue value)
    {
        List<TValue> valueList;
        int n = 0;

        if (TryGetValue(key, out valueList)) {
            while (valueList.Remove(value)) {
                n++;
            }
            if (valueList.Count == 0) {
                Remove(key);
            }
        }
        return n;
    }

    /// <summary>
    /// Gets the total number of values contained in the MultiMap.
    /// </summary>
    public int CountAll
    {
        get
        {
            int n = 0;

            foreach (List<TValue> valueList in Values) {
                n += valueList.Count;
            }
            return n;
        }
    }

    /// <summary>
    /// Determines whether the MultiMap contains an element with a specific
    /// key / value pair.
    /// </summary>
    /// <param name="key">Key of the element to search for.</param>
    /// <param name="value">Value of the element to search for.</param>
    /// <returns>true if the element was found; otherwise false.</returns>
    public bool Contains(TKey key, TValue value)
    {
        List<TValue> valueList;

        if (TryGetValue(key, out valueList)) {
            return valueList.Contains(value);
        }
        return false;
    }

    /// <summary>
    /// Determines whether the MultiMap contains an element with a specific value.
    /// </summary>
    /// <param name="value">Value of the element to search for.</param>
    /// <returns>true if the element was found; otherwise false.</returns>
    public bool Contains(TValue value)
    {
        foreach (List<TValue> valueList in Values) {
            if (valueList.Contains(value)) {
                return true;
            }
        }
        return false;
    }

}

请注意,Add方法会检查键是否已存在。如果键是新的,则创建一个新列表,将值添加到列表中,并将列表添加到字典中。如果键已经存在,则将新值添加到现有列表中。

如果你要将它抽象化到这个层次,为什么不使用 Dictionary<TKey, HashSet<TValue>>。你只对内部集合执行添加/删除/包含检查,而这正是 HashSet 最适合的。 - Servy
1
语义略有不同。我的实现允许您为同一键插入相同的值多次。我不知道这两种变体是否有不同的术语。MultiMap 适用于哪一个呢?也许我的变体可以使用 MultiMap,而你的变体可以使用 MultiSet - Olivier Jacot-Descombes
我根本不会使用继承。这个类的用户希望完全隐藏字典接口。您可能希望使用 MultiMap 或 Dictionary,但不要两者都用。 - Trap
这是一个快速解决方案。当然,您可以从一个新类开始实现IDictionary<K,V>以及一些针对多重映射的特定内容,以获得完美的解决方案。在内部,您将使用Dictionary<K,List<V>>。实现IDictionary<K,V>需要您实现16个属性和方法。正如我在文章开头所写的那样,所呈现的解决方案并不完美。 - Olivier Jacot-Descombes
此外,这个实现允许你通过原始字典接口添加和检索整个列表。 - Olivier Jacot-Descombes

3

使用 NameValueCollection。

一个很好的起点是这里,直接从链接中获取信息。

System.Collections.Specialized.NameValueCollection myCollection
    = new System.Collections.Specialized.NameValueCollection();

  myCollection.Add(“Arcane”, “http://arcanecode.com”);
  myCollection.Add(“PWOP”, “http://dotnetrocks.com”);
  myCollection.Add(“PWOP”, “http://dnrtv.com”);
  myCollection.Add(“PWOP”, “http://www.hanselminutes.com”);
  myCollection.Add(“TWIT”, “http://www.twit.tv”);
  myCollection.Add(“TWIT”, “http://www.twit.tv/SN”);

  1. 这是一个NameValueCollection - 没有'd'。
  2. 请注意,应该使用GetValues(String)而不是索引器 - 索引器返回一个逗号分隔的字符串与您的值,如果您的值可能包含逗号,则会出现问题。
  3. 该集合不区分null作为值还是null作为未找到的键。
- toong

2

虽然与大多数其他响应几乎相同,但我认为这是实现它最有效和简洁的方法。 使用TryGetValue比使用ContainsKey和重新索引到字典中要快,正如其他一些解决方案所示。

void Add(string key, string val)
{
    List<string> list;

    if (!dictionary.TryGetValue(someKey, out list))
    {
       values = new List<string>();
       dictionary.Add(key, list);
    }

    list.Add(val);
}

0

当您添加字符串时,根据键是否已存在以不同的方式进行操作。要为键key添加字符串value

List<string> list;
if (dictionary.ContainsKey(key)) {
  list = dictionary[key];
} else {
  list = new List<string>();
  dictionary.Add(ley, list);
}
list.Add(value);

0

为什么不使用ILookup而不是使用字典呢?

var myData = new[]{new {a=1,b="frog"}, new {a=1,b="cat"}, new {a=2,b="giraffe"}};
ILookup<int,string> lookup = myData.ToLookup(x => x.a, x => x.b);
IEnumerable<string> allOnes = lookup[1]; //enumerable of 2 items, frog and cat

ILookup是一种不可变的数据结构,允许一个键对应多个值。如果您需要在不同时间添加项目,则可能没有太大用处,但如果您已经拥有了所有数据,那么这绝对是最好的选择。


谢谢。我需要在不同的时间添加项目。 - sailer

0

有一个 NuGet 包 Microsoft Experimental Collections,其中包含一个类 MultiValueDictionary,它恰好可以满足您的需求。

这里是该包创建者的博客文章,进一步描述了它。

这里是另一篇博客文章,如果您感到好奇的话。

示例用法:

MultiDictionary<string, int> myDictionary = new MultiDictionary<string, int>();
myDictionary.Add("key", 1);
myDictionary.Add("key", 2);
myDictionary.Add("key", 3);
//myDictionary["key"] now contains the values 1, 2, and 3

0

我正在改进这个答案,使用了我编写的一些扩展方法。第一个方法与@Bronek编写的方法类似,只是更加简洁。简单来说,如果键存在,则插入到已经存在的列表中(假设它已经初始化)。否则,将添加到列表中。

public static void AddToList<K, V>(this Dictionary<K, List<V>> multiValueDictionary,
    K key,
    V value)
{
    if (multiValueDictionary.TryGetValue(key, out List<V> lst))
        lst.Add(value);
    else
        multiValueDictionary.Add(key, new List<V> { value });
}

这个第二个函数是基于之前的一个函数。在 System.Linq 中,有一个扩展方法 ToDictionary 可以将任何 IEnumerable 转换为 Dictionary。但如果您有上述情况,即要为单个键存储多个值,那么下面的扩展将起作用!

public static Dictionary<K, List<V>> ToDictionaryValueList<T, K, V>(this IEnumerable<T> values, Func<T, K> keySelector, Func<T, V> elementSelector)
{
    var tmp = new Dictionary<K, List<V>>();

    foreach (var val in values)
        tmp.AddToList(keySelector(val), elementSelector(val));

    return tmp;

    // NOTE: You can also use the below IEnumerable extensions to accomplish the same goal, but the above might be (?) more efficient, not sure
    // return values
    //    .GroupBy(v => keySelector(v))
    //    .ToDictionary(v => v.Key, v => v.ToList());
}

有了上述两个方法,现在可以轻松地将任何IEnumerable转换为这些字典之一。

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

List<Person> test = new List<Person> 
{
    new Person { Name = "Bob", Age = 22 },
    new Person { Name = "Bob", Age = 28 },
    new Person { Name = "Sally", Age = 22 },
    new Person { Name = "Sally", Age = 22 },
    new Person { Name = "Jill", Age = 22 },
}

// Aggregate each person
Dictionary<string, List<int>> results = test.ToDictionaryValueList((p) => p.Name, (p) => p.Age);

// Use the AddToList extension method to add more values as neeeded
results.AddToList("Jill", 23);


需要考虑的一件事是,重复值不会被处理,这符合标准的 List 的预期。如果需要,您可以为不同的集合(如 HashSet)编写扩展方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接