Linq查询用于从树形结构中选择一个项目,但要查看整个深度。

12 浏览
0 Comments

Linq查询用于从树形结构中选择一个项目,但要查看整个深度。

这是我创建的一个类:

public class ItemTree
{
    public Int32 id { get; set; }
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public String text { get; set; }
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public List item { get; set; }
    public int parentId { get; set; }
}

这是我使用它的方式:

var tree = new ItemTree();
tree.id = 0;
tree.text = "一些文本";
tree.item = new List();
foreach (...)
{
    if (tree.item.Count == 0)
    {
      tree.item.Add(new ItemTree
      {
        id = my_id,
        text = my_name,
        item = new List(),
        parentId = my_par
      });
    }
    else
    {
      tree.item.SelectMany(x => x.item)
               .Where(x => x.id == my_par)
               .First()
               .item.Add(new ItemTree 
               {
                 id = my_id,
                 text = my_name,
                 item = new List(),
                 parentId = my_par
               });
    }
}

它在带有Where子句的行上崩溃。

崩溃的原因是:树中有一个具有项目列表的项目,而我的查询只检查树的第一个项目,而不是它的子项。

如何在整个树的深度中搜索并在那里添加项目?

0
0 Comments

原因:该问题的出现原因是需要从树形结构中选择一个项目,但要查看整个深度。传统的Linq查询只会查看树的一层,无法深入到树的子节点。

解决方法:可以使用递归函数来实现深度遍历。上述代码提供了一个名为SelectRecursively的扩展方法,可以将深度遍历应用于任何实现了IEnumerable接口的集合。该方法接受一个函数参数memberSelector,用于选择每个项目的子集合。在遍历过程中,使用yield return关键字返回每个项目,并递归调用内部的子集合。

使用方法:通过调用SelectRecursively方法,并传入一个lambda表达式,来选择ItemTree对象的子集合。然后使用ToList方法将结果转换为列表。这样就可以开始递归选择(深度遍历),并可以使用其他Linq功能,例如Where方法。

如何添加新的ItemTree对象:在foreach循环中,在else分支中的ItemTree对象上调用Items.Add(...)方法即可将新的ItemTree对象添加到树中。

0
0 Comments

在处理树形结构时,有时将其展开成一个列表会更加方便。如果你只有一个包含树的所有节点的 IEnumerable,一些逻辑会更容易表达。你不会丢失任何信息,因为每个节点上仍然有父节点的 ID。

这是一个自然递归的问题。使用递归的 lambda,可以尝试以下方法:

Func> flattener = null;
flattener = t => new[] { t }
                .Concat(t.item == null 
                        ? Enumerable.Empty()
                        : t.item.SelectMany(child => flattener(child)));

请注意,当创建这样的递归 Func 时,必须先单独声明 Func,并将其设置为 null。

您还可以使用迭代块方法来展开列表:

public static IEnumerable Flatten(ItemTree node)
{
    yield return node;
    if (node.item != null)
    {
         foreach(var child in node.item)
             foreach(var descendant in Flatten(child))
                 yield return descendant;
    }
}

无论哪种方式,一旦树被展开,您可以在展开的列表上执行简单的 Linq 查询以查找节点:

flattener(tree).Where(t => t.id == my_id);

然后,为了向树中添加节点,可以执行以下操作:

var itemOfInterest = flattenedTree.Where(t => t.id == myId).Single();
itemOfInterest.item = itemOfInterest.item ?? new List();
itemOfInterest.item.Add(myItemToAdd);

其中 flattenedTree 是使用我们的两种展开策略之一生成的。

我还想指出,item 不是一个很好的属性名,因为它是一个列表。这样的属性通常是复数形式(items)。另外,属性通常以大写字母开头(Items)。

值得注意的是,由于这是一个递归定义的 lambda,声明行是必需的。`Func<...> flattener = ...` 是不起作用的。

我也回答了这个问题。但是你的回答让我想起了递归 lambda,所以加一!

那么我如何将新的 ItemTree 对象添加到这个 foreach 循环中的对象中呢?

_stankoski,请查看我更新的答案,关于如何向树中添加节点。

0
0 Comments

Linq查询以选择树结构中的一个项,但要查找整个深度的问题是由于需要在树结构中查找整个深度的项而引起的。为了解决这个问题,可以使用以下解决方案:

public static IEnumerable SelectRecursively(this IEnumerable e,
                                                  Func> memberSelector)
{
    foreach (T item in e)
    {
        yield return item;
        IEnumerable inner = memberSelector(item);
        if (inner != null)
        {
            foreach(T innerItem in inner.SelectRecursively(memberSelector))
            {
                yield return innerItem;
            }
        }
    }
}

通过使用这个扩展方法,可以将内部项添加到结果列表中。感谢这个好主意。

0