3

这是我做的一堂课:

public class ItemTree
{

    public Int32 id { get; set; }

    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public String text { get; set; }

    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public List<ItemTree> item { get; set; }

    public int parentId { get; set; }

}

这是我使用它的方式:

var tree = new ItemTree();
tree.id = 0;
tree.text = "sometext";
tree.item = new List<ItemTree>();

foreach (...)
{
    if (tree.item.Count == 0)
    {
      tree.item.Add(new ItemTree
      {
        id = my_id,
        text = my_name,
        item = new List<ItemTree>(),
        parentId = my_par
      });
    }
    else
    {
      tree.item.Where(x => x.id == my_par)
               .Select(x => x.item)
               .First()
               .Add(new ItemTree 
               {
                 id = my_id,
                 text = my_name,
                 item = new List<ItemTree>(),
                 parentId = my_par
               });
    }
}

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

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

4

6 回答 6

4

将树结构展平为列表可能会很方便。IEnumerable<ItemTree>如果您只有一个包含树的所有节点的逻辑,则某些逻辑将更容易表达。您不会丢失任何信息,因为您在每个节点上仍然拥有父 ID。

这是一个自然递归的问题。使用递归 lambda,尝试类似:

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

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

您还可以使用迭代器块方法展平列表:

public static IEnumerable<ItemTree> 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<ItemTree>();
itemOfInterest.item.Add(myItemToAdd);

使用我们flattenedTree的两种扁平化策略之一生成的位置。

我还想指出,item对于作为列表的属性来说,这不是一个好名字。这些属性通常是复数形式(items)。此外,属性通常大写 ( Items)。

于 2013-06-13T12:01:41.553 回答
1

这可能会有所帮助:

public static IEnumerable<T> SelectRecursively<T>(this IEnumerable<T> e,
    Func<T, IEnumerable<T>> memberSelector)
{
    foreach (T item in e)
    {
        yield return item;

        IEnumerable<T> inner = memberSelector(item);

        if (inner != null)
            inner.SelectRecursively(memberSelector);
    }
}

使用如下:

List<ItemTree> tree = GetTree();
List<ItemTree> flattenedTree = tree.SelectRecursively(T => T.Items).ToList();

这将启动递归选择(深度遍历),您可以在其中使用其他LinQ功能,例如.Where().

于 2013-06-13T12:14:17.207 回答
1

2013-06-13 12:14 的@AgentFire 解决方案必须扩展到

public static IEnumerable<T> SelectRecursively<T>(this IEnumerable<T> e,
                                                  Func<T, IEnumerable<T>> memberSelector)
{
    foreach (T item in e)
    {
        yield return item;

        IEnumerable<T> inner = memberSelector(item);

        if (inner != null)
        {
            foreach(T innerItem in inner.SelectRecursively(memberSelector))
            {
                yield return innerItem;
            }
        }
    }
}

将内部项目放入您的结果列表中。

感谢@AgentFire 这个好主意。

于 2016-05-10T15:53:04.303 回答
0

您正在使用First()而不是FirstOrDefault(). 您应该改为执行以下操作。

var item = tree.item.Where(x => x.id == my_par)
           .Select(x => x.item)
           .FirstOrDefault();

if (item != null)
           .Add(new ItemTree 
           {
             id = my_id,
             text = my_name,
             item = new List<ItemTree>(),
             parentId = my_par
           });
于 2013-06-13T11:52:57.130 回答
0

您将需要以某种方式递归树。一种解决方案是为ItemTree对象创建一个迭代器,例如:

public class ItemTree
{
  //simple DFS walk of the tree
  public IEnumerable<ItemTree> GetChildren()
  {
     //no items, stop execution
     if ((item == null) || (item.Count == 0))
       yield break;
     foreach (var child in item)
     {
        //return the child first
        yield return child;
        //no way to yield return a collection
        foreach (var grandchild in child.GetChildren())
        {
           yield return grandchild;
        }
     }
  }
}

现在找到父母是微不足道的,比如

var parent = tree.GetChilden().First(c => c.id == my_par);
parent.Add(new ItemTree 
{
  id = my_id,
  text = my_name,
  item = new List<ItemTree>(),
  parentId = my_par
});
于 2013-06-13T12:03:13.200 回答
0
  1. 您应该在 ItemTree 中添加一个方法 HasId
  2. 该方法应实现对特定 ID 的递归搜索并返回答案 true 或 false
  3. 使用 (x => x.HasId(my_par))
于 2013-06-13T12:01:33.670 回答