score:11

Accepted answer

Here a recursive way of doing this:

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);
    }
}

If you'd like to have all the operations in one loop, do it with a for loop. In my opinion it's much harder to read, though.

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);
    }
}

Another method would be to have a flat (non-hierarchical) list or even dictionary (quickest way!), which contains all the elements. You could add another property, which contains the Parent ID of the child. In some cases, especially when you have deep trees with lots of items, this way would be much more performant. If you want to remove a certain item, do it like this:

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);
}

If you want the UI to recognize changes you need to use ObservableCollection<Node>, though.

score:0

I'm sure a LINQ-ninja can whip up some nice script, but I'm not savvy enough yet for that. At least here's some non-recursive, non-tested code that might work for you:

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]);
        }
    }
}

Note that it doesn't check the "root" node's Id (you can add that check if you want), just didn't know what to do in that case as there wouldn't be anything to remove. Furthermore, it stops checking down the branches if one of them matches the Guid (so if a parent's Id matches, it does not check that node's children which may have matching Ids as well). If you did need to check children of removed nodes, just push the child node onto the stack before removing it.

score:1

In other words, you want to traverse a graph and delete and the item. Here are some question:

  • Can it have cycles? Node A has a child, which has a child B, B has C and C points to A (A -> B -> C -> A and so forth)
  • Are there multiple roots?
  • Is it multigraph?

The problem with deleting an item is what do you do with childern? What if root got the sam Guid? The best solution is to traverse the tree and get a collection of nodes.

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);
    }
}

Then to call

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

And now you can Remove() the element form the list or filter it with Where().


Related Query

More Query from same tag