簡體   English   中英

從分層集合中刪除對象

[英]Remove Object From Hierarchical Collection

我在分層列表中有一組 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 從列表中刪除項目,而不管它在列表中的哪個位置?

這是執行此操作的遞歸方式:

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>

換句話說,您要遍歷一個圖形並刪除和該項目。 這里有一些問題:

  • 可以有周期嗎? 節點 A 有一個子節點,它有一個子節點 B,B 有 C,C 指向 A(A -> B -> C -> A 等等)
  • 有多個根嗎?
  • 是多圖嗎?

刪除項目的問題是您如何處理 childern? 如果 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() 對其進行過濾。

我確信 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 的節點的子節點)。 如果您確實需要檢查已刪除節點的子節點,只需在刪除之前將子節點推入堆棧即可。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM