使用多个属性的高效Hierarchal Linq查询

时间:2012-08-08 13:07:22

标签: c# linq collections

我有一个相当大的foo { int id, int parentid, string name}集合。

我希望收集一个foo对象列表,其中对象的名称为“bar3”,并且是名为“bar2”的对象的子节点,该对象是具有ID的对象的子节点1

我应该使用什么样的集合(我一直在使用查找和词典但没有取得多大的成功)我应该如何编写这个来创建一个有效的函数呢?大约有30K foo个物体,我的方法窒息死亡。

谢谢!

3 个答案:

答案 0 :(得分:3)

如果我真的不得不坚持foo的这种布局,我真的必须尽快进行查找(我不关心内存大小,并且会重复使用相同的对象,所以在内存中设置一组大型结构的成本是值得的,然后我会这样做:

var byNameAndParentLookup = fooSource.ToLookup(f => Tuple.Create(f.parentid, f.name)); //will reuse this repeatedly
var results = byNameAndParentLookup[Tuple.Create(1, "bar2")].SelectMany(f => byNameAndParentLookup[Tuple.Create(f.id, "bar3")]);

那就是说,如果我要将树数据存储在内存中,我宁愿创建一个树结构,其中每个foo都有一个children集合(可能是一个字典上的字典) )。

编辑:解释一下。

fooSource.ToLookup(f => Tuple.Create(f.parentid, f.name))

浏览fooSource中的所有项目(我们foo对象的来源),每个项目都会创建parentidname的元组。这用作查找的键,因此对于每个parentid-name组合,我们可以使用该组合检索0个或更多foo对象。 (这将使用默认的字符串比较,如果你想要其他的东西,如不区分大小写,创建一个IEqualityComparer<Tuple<int, string>>实现,进行你想要的比较并使用.ToLookup(f => Tuple.Create(f.parentid, f.name), new MyTupleComparer()))。

第二行可以分解为:

var partWayResults = byNameAndParentLookup[Tuple.Create(1, "bar2")];
var results = partWayResults.SelectMany(f => byNameAndParentLookup[Tuple.Create(f.id, "bar3")]);

第一行只是搜索我们的查找,因此它返回那些parentid为1且名称为“bar2”的foo对象的枚举。

SelectMany获取枚举或可查询的每个项,并计算返回枚举的表达式,然后将其展平为单个枚举。

换句话说,它有点像这样:

public static SelectMany<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> func)
{
  foreach(TSource item in source)
    foreach(TResult producedItem in func(item))
      yield return producedItem;
}

在我们的例子中,传递的表达式获取在第一个查找中找到的元素的id,然后查找具有该parentid并且名称为“bar2”的任何元素。

因此,对于每个具有parentid 1和name bar2的项目,我们会发现每个项目的第一个项目的id为其parentid,名称为bar3。这是想要的。

答案 1 :(得分:0)

检查一下:QuickGraph 我从来没有真正使用它,但它似乎有很好的记录。 或者,您可以尝试C5 Generic Collection Library

我从这个tread

得到了这个

答案 2 :(得分:-1)

我建议您先按parentId对所有项目进行分组,然后对其应用条件。首先,您需要找到包含bar1元素的组,而不是应该选择所有子元素并尝试查找名称为2的元素...

我可以建议这样的解决方案,它不是最好的但它有效(thirdLevelElements将包含所需的元素)。我使用了foreach来说清楚,这个逻辑可以用linq语句编写,但对我来说理解起来会很复杂。

var items = new[]
                            {
                                new Foo{id=1,parentid = 0, name="bar1"},
                                new Foo{id=2,parentid = 1, name="bar2"},
                                new Foo{id=3,parentid = 2, name="bar3"},
                                new Foo{id=4,parentid = 0, name="bar12"},
                                new Foo{id=5,parentid = 1, name="bar13"},
                                new Foo{id=6,parentid = 2, name="bar14"},
                                new Foo{id=7,parentid = 2, name="bar3"}
                            };

            var groups = items.GroupBy(item => item.parentid).ToList();
            var firstLevelElements = items.Where(item => item.name == "bar1");
            List<Foo> secondLevelElements = new List<Foo>();
            foreach (var firstLevelElement in firstLevelElements)
            {
                secondLevelElements.AddRange(groups[firstLevelElement.id]
                    .Where(item => item.name == "bar2"));
            }
            List<Foo> thirdLevelElements = new List<Foo>();
            foreach (var secondLevelElement in secondLevelElements)
            {
                thirdLevelElements.AddRange(groups[secondLevelElement.id]
                    .Where(item => item.name == "bar3"));
            }