【问题标题】:Linq-to-objects: creating a two-level hierarchy from a flat sourceLinq-to-objects:从平面源创建两级层次结构
【发布时间】:2011-06-02 01:53:57
【问题描述】:

假设我有这个简单的结构

class FooDefinition
{
    public FooDefinition Parent { get; set; }
}

class Foo
{
    public FooDefinition Definition { get; set; }
}

class Bar
{
    public ICollection<Foo> Foos { get; set; }
}

Bar 有一个 Foos 列表,可以是简单的(无父/子关系)或仅嵌套一层(即父 Foo 有许多子 Foos)。从这里可以看出,关系是在FooDefinition 中指定的,而不是Foo itself

我需要做的是生成一个按此层次结构正确分组的Foos 列表。考虑以下源数据:

var simpleDefinition = new FooDefinition();
var parentDefinition = new FooDefinition();
var childDefinition = new FooDefinition { Parent = parentDefinition };

var bar = new Bar { Foos = new[]
                           {
                               new Foo { Definition = simpleDefinition },
                               new Foo { Definition = parentDefinition },
                               new Foo { Definition = childDefinition }
                           }};

我想和他们的孩子一起收集顶级物品。一个合适的数据结构可能是IEnumerable&lt;IGrouping&lt;Foo, Foo&gt;&gt;

结果如下:

  • 第 1 项(简单)
  • 项目 2(父)
    • 第 3 项(儿童)

当然,我想用纯功能的 Linq 查询来做到这一点。我做了很多这些,但今天我的大脑似乎卡住了。

【问题讨论】:

  • @Daniel 感谢您的编辑;我无意中留下了原来的名字。
  • 没问题。在评论我的答案之前请稍候。我已经发现了它的问题。
  • 修正了我的答案。请看一看。

标签: c# linq-to-objects


【解决方案1】:
bar.Foos.Where(x => x.Definition.Parent == null)
        .Select(x => Tuple.Create(x, 
                                  bar.Foos.Where(c => c.Definition
                                                       .Parent == x.Definition
                                                ))); 

这将返回一个IEnumerable&lt;Tuple&lt;Foo, IEnumerable&lt;Foo&gt;&gt;&gt;,其中TupleItem2 包含Item1 中父级的子级。对于您的示例,这将返回两个元组:

  • Item1 = simpleDefinitionItem2 包含一个空的枚举
  • Item1 = parentDefinitionItem2 包含一个包含 childDefinition 的枚举

可能有更优雅或更快的方法,但我想不出...

哦,好吧,我与我自己的评论有点矛盾,但GroupBy 是可能的 - 至少几乎是:

bar.Foos.Where(x => x.Definition.Parent == null)
        .GroupBy(x => x,
                 x => bar.Foos.Where(c => c.Definition.Parent == x.Definition));

这将返回一个IEnumerable&lt;IGrouping&lt;Foo, IEnumerable&lt;Foo&gt;&gt;&gt;

更新:
我想知道,您想要的解决方案是否可行。
是的,它是:

bar.Foos.Where(x => x.Definition.Parent != null)
        .GroupBy(x => bar.Foos.Where(y => y.Definition == x.Definition.Parent)
                              .Single(),
                 x => x)
        .Union(bar.Foos.Where(x => x.Definition.Parent == null && 
                                   !bar.Foos.Any(c => c.Definition.Parent == 
                                                      x.Definition))
                       .GroupBy(x => x, x => (Foo)null));

但是我真的不想知道这个的big O而且真的不应该使用它;-)

【讨论】:

  • 我确实到了那里,但我真的想要IGrouping&lt;Foo, Foo&gt; 而不是Tuple&lt;Foo, IEnumerable&lt;Foo&gt;&gt;
  • 你为什么想要IGrouping
  • @DanielHilgarth:因为在 IGrouping 上进行迭代比在 Item2 上进行迭代“更漂亮”(而且,由于这些实际上称为 Items,因此对读者来说不太容易混淆)
  • @Diego:分组没有多大意义,因为组总是有一个键一个非空的关联值列表。但是您想要一个没有关联值的键(simpleDefinition 的情况)。这就是不匹配。
  • 我正在使用类似的解决方案,但请注意,如果您从 bar.Foos 中删除 new Foo { Definition = parentDefinition },则使用 childDefinitionFoo 将不再出现在结果中。在这种情况下,是否应该遍历Bar 中的FoosDefinitions 直到找到顶层,或者应该排除使用childDefinitionFoo,因为顶层项目本身不是在bar.Foos?
【解决方案2】:

如果你添加一个类和一个方法,你可以到达IEnumerable&lt;IGrouping&lt;Foo,Foo&gt;&gt;

    class FooRelation{
        public Foo Parent {get; set;}
        public Foo Child  {get; set;}
    }

    static IEnumerable<FooRelation> GetChildren(Bar source, Foo parent){
        var children = source.Foos.Where(c => c.Definition.Parent == parent.Definition);
        if(children.Any())
            return children.Select(c => new FooRelation{Parent = parent, Child = c});
        return new FooRelation[]{new FooRelation{Parent = parent}};
    }

您甚至可以将该静态方法折叠到此查询中......但它会变得混乱:

     var r = bar.Foos.Where(x => x.Definition.Parent == null)
                .SelectMany(x => GetChildren(bar, x))
                .GroupBy(fr => fr.Parent, fr => fr.Child);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多