Linq Where声明表现缓慢

时间:2013-01-08 17:25:31

标签: c# linq

我有一个List Objects(大约100k)必须迭代才能生成Dictionary。 但是代码执行速度非常慢,特别是在一行上

public class Item{
        public int ID;
        public int Secondary_ID;
        public string Text;
        public int Number;
}

数据看起来像(100k行)

ID  | Secondary_ID |      Text       | Number
1   |    1         | "something"     | 3
1   |    1         | "something else"| 7
1   |    1         | "something1"    | 4
1   |    2         | "something2"    | 344
2   |    3         | "something3"    | 74
2   |    3         | "something4"    | 1

我想在完成后看起来像这样。 (任何收藏都要诚实)

 Dictionary<int, string> 

Key             | Value
(secondary_ID)  | (Text : Number)

1               | "Something : 3, Something else : 7, Something1 : 4"
2               | "Something2 : 344"
3               | "Something3 : 74, Something4 : 1"

我的代码目前的工作原理如下ListAll包含所有数据。

var Final=new Dictionary<int, string>();
var id1s=ListAll.Select(x => x.ID).Distinct().ToList();

foreach(var id1 in id1s) {
    var shortList=ListAll.Where(x => x.ID==id1).ToList(); //99% of time spent is here
    var id2s=shortList.Select(x => x.Secondary_ID).Distinct().ToList();

    foreach(var id2 in id2s) {
        var s=new StringBuilder();
        var items=shortList.Where(x => x.Secondary_ID==id2).ToList();

        foreach(var i in items) {
            s.Append(String.Format("{0} : {1}", i.Text, i.Number));
        }

        Final.Add(id2, s.ToString());
    }
}

return Final;

现在输出是正确的,但正如上面评论中所述,这需要非常长的时间来处理(90秒 - 当然比我更舒服)并且想知道是否有更快的方法来实现这一点。

这段代码只会被使用一次所以不是真正的正常用法,通常我会因为这个原因而忽略它,但是为了学习目的而感到疑惑。

3 个答案:

答案 0 :(得分:8)

这就是我要做的事情(未经测试,但希望你能得到这个想法):

var final = ListAll.GroupBy(x => x.Secondary_ID)
                   .ToDictionary(x => x.Key, x => String.Join(", ", 
                       x.Select(y => String.Format("{0} : {1}", 
                           y.Text, y.Number)))

首先按Secondary_ID使用GroupBy进行分组,然后使用ToDictionary将结果放入字典中。

GroupBy会将您的数据分组到以下群组中:

Key = 1:

ID  | Secondary_ID |      Text       | Number
1   |    1         | "something"     | 3
1   |    1         | "something else"| 7
1   |    1         | "something1"    | 4

Key = 2:
ID  | Secondary_ID |      Text       | Number
1   |    2         | "something2"    | 344

Key = 3:
ID  | Secondary_ID |      Text       | Number
2   |    3         | "something3"    | 74
2   |    3         | "something4"    | 1

然后是.ToDictionary方法:

  • 选择密钥为x.Key(我们分组的密钥,即Secondary_ID)。
  • 选择String.Join操作的结果作为值。正在加入的是该组内部元素的“文本:数字”集合 - x.Select(y => String.Format("{0} : {1}", y.Text, y.Number)

答案 1 :(得分:7)

通过ID对项目进行分组的效率更高(甚至更容易编写)的方法是使用GroupBy

var query = ListAll.GroupBy(x => x.Secondary_ID)
    .ToDictionary(group => group.Key,
        group => string.Join(", ",
             group.Select(item => string.Format("{0} : {1}",item.Text , item.Number))),
    //consider refactoring part of this line out to another method
    });

至于你的代码太慢的原因,你在整个列表中搜索每个不同的ID。这是一个O(n ^ 2)操作。 GroupBy不这样做。它在内部使用基于散列的结构,基于您正在分组的内容,以便它可以快速(在O(1)时间内)找到任何给定项目所属的存储桶,而不是O(n)时间它采取你的方法。

答案 2 :(得分:0)

首先,删除所有地方ToList(),它应该变得更快;因为ToList()执行 渴望评估

我认为您的代码期望做的是:

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

foreach(var x in ListAll)
    if(Final.ContainsKey(x.Secondary_ID))
        Final[x.Secondary_ID]+=String.Format(", {0} : {1}", x.Text, x.Number);
    else
        Final.Add(x.Secondary_ID, String.Format("{0} : {1}", x.Text, x.Number));

return Final;

Dictionary 不能包含重复的密钥 ,所以不管你在这里用什么IDSecondary_ID,如果您的Secondary_ID必须在现有ID的范围内;你甚至不需要代码中的Distinct()

通过一些简化,原始代码将是:

foreach(var id1 in ListAll.Select(x => x.ID).Distinct()) {
    foreach(var id2 in ListAll.Where(x => x.ID==id1).Select(x => x.Secondary_ID).Distinct()) {
        var s=new StringBuilder();

        foreach(var i in ListAll.Where(x => x.ID==id1).Where(x => x.Secondary_ID==id2)) {
            s.Append(String.Format("{0} : {1}", i.Text, i.Number));
        }

        Final.Add(id2, s.ToString());
    }
}