2010-10-06 54 views
1

我有一本字典,我需要在传入数据解析传入数据后保持更新,我必须检查字典中是否存在传入数据中不存在的任何条目(传入数据当解析是一个列表,我需要映射它与字典条目)。Enumerable.ElementAt vs foreach

为了避免多个循环去除条目,我运行了一个递减的字典计数循环,然后我使用ElementAt获取索引的字典键,然后检查输入数据中是否存在条目,如果不是那么我从列表中删除该条目。我这样做是因为在字典键上运行foreach循环并从字典键中移除会引起异常,因为字典键集合将被修改。

我想明白,这样做会对执行时间产生任何影响。我想了解ElementAt操作的顺序是什么。

回答

6

ElementAt如果您需要提供索引语义,无法保证语义索引将在下面的枚举提供非常有用。当枚举的作用是IList<T>(其中包括List和数组)时,它确实使用了O(1)索引,但是否则是O(n)*,这使得它在从O(n)操作它将与一个列表O(n * n)。

但是,如果您获得了dict.Keys.ToList()的密钥副本,那么您可以安全地使用foreach,因为它不会因字典更改而发生更改。

不清楚的是为什么你不只是用新的字典替换旧的字典,这会再快得多(简单的引用分配)。

*更新:在Linq的.NET Core版本中,有更多的情况,其中ElementAt()是O(1),例如在IList<T>上完成的Select()的结果。此外,OrderBy(…).ElementAt(…)现在是O(n)而不是O(n log n),因为组合的序列变为快速选择而不是快速排序,然后是迭代。

+0

谢谢!我重构了一些已经存在的代码,然后感到困惑,错过了一些明显的方式 – whoisthis 2010-10-06 18:16:02

1

我认为ElementAt()使用枚举器来获取所需的元素。

这将是一样的:

object returnedElement = null; 
int i = 0; 
foreach (var obj in dictionary.Keys) 
{ 
    if (i++ == at) 
    { 
     returnedElement = obj; 
     break; 
    } 
} 
+0

谢谢! Itay为你的回应我发现它确实使用枚举器 – whoisthis 2010-10-06 11:51:41

2

ElementAt()确实使用枚举作为所指出的,所以如果你想快速索引访问你应该使用数组。当然,这是以一个固定长度的价格出现的,但如果阵列的大小不是经常变化的话,那么Array.Resize()可能是一条可行的路线。

+0

我可以做到这一点,但我也有与传入数据映射的任务,这意味着我将不得不有排序的数组,并且我必须再次暴露它作为一个集合,以便将需要复制操作,我猜这将是昂贵的。如果我在这里错了,请纠正我。 – whoisthis 2010-10-06 11:53:36

+0

不确定为什么你需要一个副本。 Array实现了ICollection和IEnumerable,所以它应该没问题。 – danijels 2010-10-06 11:57:01

3

使用“标记然后删除”技巧作为无法在迭代时修改集合的解决方法。

var dict = new Dictionary<int, string> 
{ 
    {3, "kuku" }, 
    {1, "zOl"} 
}; 

var newKeys = new List<int> { 1, 2, 4 }; 

var toRemove = dict.Keys.Except(newKeys).ToList(); 

foreach (var k in toRemove) 
    dict.Remove(k); 
+0

是的,我以前做过,然后更改为ElementAt,我想知道最佳方法来实现最佳性能。我正在分析发现,这比Elementay的itay快,danijels指出我们使用枚举器,占用了额外的时间。 – whoisthis 2010-10-06 11:51:06

0

你可以在目标的匹配条目的字典(词典)和源(列表)如下:

using System; 
using System.Collections.Generic; 
using System.Linq; 

Dictionary<string, string> target = new Dictionary<string, string>(); 
List<string> source = new List<string>(); 
target.Add("a", "this is a"); 
target.Add("b", "this is b"); 
source.Add("a"); 
source.Add("c"); 

target = Enumerable.Select(target, n => n.Key). 
    Where(n => source.Contains(n)).ToDictionary(n => n, k => target[k]); 

它,如果你想包括来自列表进入新条目不是很清楚,我词典 - 如果是这样,我不确定新条目值是什么,如果你只有一个新的传入数据列表。

2

Except()似乎将在这里工作:

Dictionary<int, string> dict = new Dictionary<int, string> 
{ 
    {3, "kuku" }, 
    {1, "zOl"} 
}; 

IEnumerable<int> data = new List<int> { 1, 2, 4 }; 

IEnumerable<int> toRemove = dict.Keys.Except(data); 

foreach(var x in toRemove) 
    dict.Remove(x); 
+0

这个答案试图改进[由Grozz回答](http://stackoverflow.com/a/3872056/1497596)。但是,我相信'除了'是和这里所需要的相反的。正如所写的,'toRemove'将包含'{2,4}'代表'dict'中的* not *键。相反,我相信'Intersect(dict.Keys)'会产生所需的结果......这是删除'dict'中与'data'匹配的键。 – DavidRR 2014-02-14 16:24:56

+0

你是对的,我不正确,但仅仅是因为我的'Except'的参数被翻转了:“检查字典中是否有任何输入数据中不存在的条目”建议我需要全部删除键*除*包含在'data'中的键。 – dahlbyk 2014-02-20 12:57:02

+0

是的,'toRemove = dict.Keys.Except(data);'很好地阅读。 – DavidRR 2014-02-20 13:04:31