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

本文关键字:高效 分层 Linq 查询 属性 | 更新日期: 2024-11-04 15:15:54

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

我正在寻找收集foo对象的列表,其中对象的名称为"bar3",并且是名为"bar2"的对象的子对象,该对象是 ID 为 1 的对象的子对象。

我应该

使用什么样的集合(我一直在玩查找和字典,但并没有取得很大的成功),我应该如何编写它来制作一个有效的函数? 大约有30K foo物体,我的方法被窒息而死。

谢谢!

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

如果我真的必须坚持这种布局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元组。这被用作查找的键,因此对于每个父 ID 名称组合,我们可以使用该组合检索 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")]);

第一行只是在我们的查找中进行搜索,因此它返回那些父 ID 为 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,然后查找将其作为父级 id 并具有名称为"bar2"的任何元素。

因此,对于父项 ID 为 1 和名称栏 2 的每个项目,我们找到将第一个项目的 id 作为其父项 ID 和名称栏 bar3 的每个项目。这就是想要的。

看看这个: QuickGraph我从未真正使用过它,但它似乎有据可查。或者,您可以尝试 C5 通用集合库

我从这个胎面上得到了这个

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

我可以建议这样的解决方案,它不是最好的,但它有效(第三级元素将包含所需的元素)。我用foreachs来说明,这个逻辑可以写在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"));
            }