3

我在分层列表中有一组 NodeObject 类。该列表可以是任意数量的深度。

public class NodeModel : ViewModelBase
{
    public Guid Id { get; set; }
    public string Caption { get; set; }
    public string Description { get; set; }
    public NodeType Type { get; set; }
    public List<NodeModel> Children { get; set; }
}

如何使用其 Guid Id 从列表中删除一个项目,而不管它在列表中的哪个位置?

4

3 回答 3

11

这是一种递归方式:

private void DeleteNode(IList<Node> nodes, Guid id)
{
    Node nodeToDelete = null;
    foreach (var node in nodes)
    {
        if (node.Id == id)
        {
            nodeToDelete = node;
            break;
        }
        DeleteNode(node.Children, id);
    }
    if (nodeToDelete != null)
    {
        nodes.Remove(nodeToDelete);
    }
}

如果您想在一个循环中完成所有操作,请使用 for 循环。不过,在我看来,阅读起来要困难得多。

private void DeleteNode(IList<Node> nodes, int id)
{
    for (var index = 0; index < nodes.Count; index++)
    {
        var currentNode = nodes[index];
        if (currentNode.Id == id)
        {
            nodes.Remove(currentNode);
            break;
        }
        DeleteNode(currentNode.Children, id);
    }
}

另一种方法是使用包含所有元素的平面(非分层)列表甚至字典(最快的方式!)。您可以添加另一个属性,其中包含孩子的父 ID。在某些情况下,尤其是当您拥有包含大量项目的深层树时,这种方式的性能会更高。如果要删除某个项目,请执行以下操作:

private void DeleteNode(IList<Node> flatNodes, Guid id)
{
    var nodeToDelete = flatNodes.FirstOrDefault(n => n.Id == id);
    if (nodeToDelete != null)
    {
        var parent = flatNodes.First(n => n.Id == nodeToDelete.ParentId);
        parent.Children.Remove(nodeToDelete);
    }
}

private void DeleteNodeFromFlatDictionary(IDictionary<Guid, Node> flatNodes, Guid id)
{
    if (!flatNodes.ContainsKey(id)) return;
    var nodeToDelete = flatNodes[id];
    flatNodes[nodeToDelete.ParentId].Children.Remove(id);
}

但是,如果您希望 UI 识别您需要使用的更改ObservableCollection<Node>

于 2012-05-09T22:37:01.223 回答
1

换句话说,您要遍历一个图形并删除该项目。这里有一些问题:

  • 它可以有循环吗?节点 A 有一个子节点,它有一个子节点 B,B 有 C 并且 C 指向 A(A -> B -> C -> A 等等)
  • 有多个根吗?
  • 是多图吗?

删除项目的问题是您如何处理孩子?如果 root 有 sam Guid 怎么办?最好的解决方案是遍历树并获取节点集合。

public static IEnumerable<T> Traverse<T>(T root, Func<T, IEnumerable<T>> children)
{
    var seen = new HashSet<T>();
    var stack = new Stack<T>();
    stack.Push(root);

    while(stack.Count != 0)
    {
        T item = stack.Pop();
        if (seen.Contains(item))
            continue;
        seen.Add(item);
        yield return item;
        foreach(var child in children(item))
            stack.Push(child);
    }
}

然后打电话

var nodes = Traverse<NodeModel>(root, node => node.Children).ToList();

现在您可以从列表中删除()元素或使用 Where()过滤它。

于 2012-05-09T22:42:03.977 回答
0

我确信 LINQ 忍者可以编写一些不错的脚本,但我还不够精明。至少这里有一些可能对您有用的非递归、未经测试的代码:

public void RemoveNodeModelByGuid(NodeModel root, Guid guid)
{
    Stack<NodeModel> nodes = new Stack<NodeModel>();
    nodes.Add(root);

    while (nodes.Count > 0)
    {
        var currentNode = nodes.Pop();
        for (int i = currentNode.Children.Count - 1; i >= 0; i--)
        {
            if (currentNode.Children[i].Id == guid)
                currentNode.Children.RemoveAt(i);
            else
                nodes.Push(currentNode.Children[i]);
        }
    }
}

请注意,它不会检查“根”节点的 ID(如果需要,您可以添加该检查),只是不知道在这种情况下该怎么做,因为没有任何东西可以删除。此外,如果其中一个与 Guid 匹配,它会停止检查分支(因此,如果父节点的 Id 匹配,它不会检查该节点的可能也具有匹配 Id 的子节点)。如果您确实需要检查已删除节点的子节点,只需将子节点推入堆栈,然后再将其删除。

于 2012-05-09T22:26:10.630 回答