如何遍历字典并更改值?

Posted

技术标签:

【中文标题】如何遍历字典并更改值?【英文标题】:How to iterate through Dictionary and change values? 【发布时间】:2011-01-16 15:44:42 【问题描述】:
Dictionary<string,double> myDict = new Dictionary();
//...
foreach (KeyValuePair<string,double> kvp in myDict)
 
     kvp.Value = Math.Round(kvp.Value, 3);

我收到一个错误: “无法将属性或索引器‘System.Collections.Generic.KeyValuePair.Value’分配给——它是只读的。” 如何遍历 myDict 并更改值?

【问题讨论】:

如果从性能角度(间接和更多 gc 压力)可以接受添加大量引用对象并且您可以控制字典,然后为您的值创建一个框将解决问题,例如:class Box&lt;T&gt; public T boxed; ,然后使用Dictionary&lt;string, Box&lt;double&gt;&gt;,然后您可以在 foreach 中“修改”类似:kvp.Value.boxed = 123.456;。并不是说这是“最好”的方法,但它有它的用途。 这能回答你的问题吗? Editing dictionary values in a foreach loop 【参考方案1】:

根据MSDN:

foreach 语句是一个包装器 在枚举器周围,它允许 只从收藏中读取,而不是 写信给它。

使用这个:

var dictionary = new Dictionary<string, double>();
// TODO Populate your dictionary here
var keys = new List<string>(dictionary.Keys);
foreach (string key in keys)

   dictionary[key] = Math.Round(dictionary[key], 3);

【讨论】:

我在 .NET 2 和 3.5 中测试了您的示例,它抛出“集合已修改异常”。请参阅:***.com/questions/1562729/… 这在 .NET 4 中是否发生了变化,还是您没有测试您的示例? 多么尴尬 - 我忽略了填充列表的部分。现在修好了。这里的想法是您可以更改字典条目的值,而不是它的引用。 希望您在问题注释行中保留“//...(填充)”。一眼看去,列表是空的。 顺便说一句,你可以做 dictionary.Keys.ToList() 在 .NET 5 中,您不再需要额外的列表。【参考方案2】:

对于懒惰的程序员

Dictionary<string, double> dictionary = new Dictionary<string, double>();
foreach (var key in dictionary.Keys.ToList())

   dictionary[key] = Math.Round(dictionary[key], 3);

【讨论】:

由于我现在使用 .NET 4.5 进行编码,ToList() 方法不可用,但 Keys 成员是可迭代的,因此 .ToList() 是不必要的。 @MikeC 参见***.com/a/2260462/1037948 上的评论——你不能直接枚举键,.ToList() 是解决这个问题的“黑客” ToList() 是一种 LINQ 扩展方法。如果您添加“使用 System.Linq;”,它应该可用到您的 using 语句。 @drzaus:你为什么不能枚举 Keys ? Keys 是一个字典,就像任何其他字典一样可以枚举。我在没有 .ToList() 的情况下这样做。 我知道这是一个旧的答案,但我更喜欢它而不是接受的答案 - 我想说与其懒惰它更简洁(删除明确声明键列表的行)。因此,要回答上面的问题:您可以枚举键集合,但问题是关于枚举和进行更改,这是您无法做到的。添加 ToList() 意味着您实际上是在枚举一个列表,该列表恰好包含与字典中的键相同的对象。这使得字典本身是可变的,因此允许您进行更改。【参考方案3】:

你不应该在迭代字典的时候改变它,否则你会得到一个异常。

所以首先将键值对复制到一个临时列表中,然后遍历这个临时列表,然后更改你的字典:

Dictionary<string, double> myDict = new Dictionary<string, double>();

// a few values to play with
myDict["a"] = 2.200001;
myDict["b"] = 77777.3333;
myDict["c"] = 2.3459999999;

// prepare the temp list
List<KeyValuePair<string, double>> list = new List<KeyValuePair<string, double>>(myDict);

// iterate through the list and then change the dictionary object
foreach (KeyValuePair<string, double> kvp in list)

    myDict[kvp.Key] = Math.Round(kvp.Value, 3);



// print the output
foreach (var pair in myDict)

    Console.WriteLine(pair.Key + " = " + pair.Value);


// uncomment if needed
// Console.ReadLine();

输出(在我的机器上):

a = 2.2 b = 77777.333 c = 2.346

注意:在性能方面,这个解决方案比目前发布的解决方案要好一些,因为值已经分配了键,不需要从字典对象中再次获取它.

【讨论】:

这可能是我将遵循的方法,但很想知道复制完整字典的开销。【参考方案4】:

过了一段时间,但也许有人对此感兴趣:

yourDict = yourDict.ToDictionary(kv => kv.Key, kv => Math.Round(kv.Value, 3))

【讨论】:

非常易读的版本!添加错误检查时可能会变得有点混乱。【参考方案5】:

我注意到(此时)用 modify 遍历 Dictionary 的最快方法是:

//Just a dumb class
class Test<T>

    public T value;

    public Test()  
    public Test(T v)  value = v; 


Dictionary<int, Test<object>> dic = new Dictionary<int, Test<object>>();
//Init dictionary
foreach (KeyValuePair<int, Test> pair in dic)

    pair.Value.value = TheObject;//Modify

VS

List<int> keys = new List<int>(dic.Keys); //This is fast operation   
foreach (int key in keys)

    dic[key] = TheObject;

第一个大约需要 2.2 秒,第二个大约需要 4.5 秒(测试字典大小为 1000 并重复 10k 次,将字典大小更改为 10 不会改变比率)。获取密钥列表也没什么大不了的,字典 [key] 值获取只是慢 VS 内置迭代。此外,如果您想要更快的速度,请使用硬编码类型来哑(“测试”)类,我得到它大约 1.85 秒(硬编码为“对象”)。

编辑:

Anna 之前也发布过同样的解决方案:https://***.com/a/6515474/766304

【讨论】:

【参考方案6】:

一种解决方案是预先将键放入列表(或另一个集合)中,并在更改字典时遍历它们:

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

// Populate it
List<string> keys = new List<string>(dictionary.Keys);

foreach (string key in keys)

   dictionary[key] = Math.Round(dictionary[key], 3);

【讨论】:

【参考方案7】:

虽然直接遍历字典是不可能的,因为你得到一个异常(就像 Ron 已经说过的那样),你不需要使用临时列表来解决问题。

而不是使用foreach,而是使用for 循环来遍历字典并使用索引访问更改值:

Dictionary<string, double> myDict = new Dictionary<string,double>();
//...    
for(int i = 0; i < myDict.Count; i++) 
    myDict[myDict.ElementAt(i).Key] = Math.Round(myDict.ElementAt(i).Value, 3);

【讨论】:

虽然这可行,但Enumerable.ElementAt() - 这是您正在使用的扩展方法 - 是O(n) operation for non-IList&lt;&gt;s。 会明确说出 Eugene Beresovsky 的意思。这最终至少是 O(n^2)。所以这是一个非常糟糕的解决方案。【参考方案8】:

遍历字典中的键,而不是 KeyValuePairs。

Dictionary<string, double> myDict = new Dictionary<string, double>();
//...
foreach (string key in myDict.Keys)

    myDict[key] = Math.Round(myDict[key], 3);

【讨论】:

令人惊讶的是它抛出了一个“Collection modified...”异常。 @Slauma 确实令人惊讶地,因为这种变化不是结构变化,因此实际上没有必要使用枚举器。

以上是关于如何遍历字典并更改值?的主要内容,如果未能解决你的问题,请参考以下文章

如何遍历值是元组的字典熊猫,并找到第一个 True 和 False 值

如何遍历对象字典及其键

循环遍历 python 字典并操作每个值

遍历javascript中的字典并访问值? [复制]

递归遍历带有列表的嵌套字典,并替换匹配的值

遍历字典值?