【问题标题】:Steps to merge original tree into result tree将原始树合并到结果树的步骤
【发布时间】:2017-11-16 13:48:54
【问题描述】:

拥有原始和“最终”/结果树。我想比较这些并“重现”这些步骤,以得到相同的结果。

真实示例:在数据库中有原始树。工作人员已准备好更改(在 App 中生成新的结果树),现在我们需要更新数据库。我们无法删除数据库并重新上传,因为可能存在尚未生成的数据。

类/表定义:

class TreeNode
{
    public string Text { get; set; }
    public TreeNode Parent { get; set; }

    /* some other properties */
}

示例树:

Origin                         Result
|A                              |A
| -1                            | -2
| -2                            |C
|B                              | -3
| -5                            |D
|  --£                          | -1
|C                              |  --£
|F                              | -5
| -7                            |E
|H                              | -6
                                |G
                                | -4
                                |H

我希望有一个算法,当对象被添加删除移动时,我可以通过该算法进行处理.

重要提示:具有其他父对象的对象不应删除添加回来,而应仅在其他父对象下移动!删除会导致数据丢失。

例子:

Mark B as removed
Mark F as removed
Add D
Add E
Add G
Move 1 under D
Move 5 under D
Mark 7 as removed
Add 3 under C
Add 6 under E
Add 4 under G
Move £ under 1
Removed 7
Removed F
Removed B

自己的解决方案

我使用 Win-FormsTreeView 创建了示例。我的算法仅适用于每个级别的基础(例如,将 1 从 A 移动到 D),但不能跨越。元素是第一个市场被删除,最后被删除。

代码:

//Recursive loop to find all nodes in Nth level
private IEnumerable<TreeNode> getNodesOnLevel(TreeNodeCollection aCollection, int aLevel)
{
    var lResultTreeNodeCol = new List<TreeNode>();

    if (aLevel == 1)
        return aCollection.Cast<TreeNode>();

    foreach(TreeNode nNode in aCollection)
    {
        lResultTreeNodeCol.AddRange(getNodesOnLevel(nNode.Nodes, aLevel - 1));
    }

    return lResultTreeNodeCol;
}

//Called once
public void UpdateTrees(TreeNodeCollection aCollectionA, TreeNodeCollection aCollectionB)
{
    List<TreeNode> lRemoved = new List<TreeNode>();
    for (int i = 1; UpdateWithLevel(aCollectionA, aCollectionB, i, ref lRemoved) > 0; i++)
    {
    }
    var lRem = lRemoved.LastOrDefault();
    do
    {
        W($"Removed {lRem.Text}");
        lRemoved.Remove(lRem);
    } while ((lRem = lRemoved.LastOrDefault()) != null);

}

//Called per level
private int UpdateWithLevel(TreeNodeCollection aCollectionA, TreeNodeCollection aCollectionB, int level, ref List<TreeNode> aRemoved)
{
    int lNumOfUpdates = 0;
    var colA = getNodesOnLevel(aCollectionA, level);
    var colB = getNodesOnLevel(aCollectionB, level);

    //Search Original collection, compare to Result collection
    foreach (TreeNode nodeA in colA)
    {
        //Find nodeA in Result collection
        var lNodeAinColB = colB.FirstOrDefault((a) => a.Text == nodeA.Text);

        if(lNodeAinColB == null) //NodeA not found in result collection - delete
        {
            aRemoved.Add(nodeA);
            W($"Mark {nodeA.Text} as removed");
            lNumOfUpdates++;
        }
        else if((lNodeAinColB.Parent?.Text ?? "") != (nodeA.Parent?.Text ?? "")) //NodeA exists in Result collection, different parrent -> must be moved
        {
            W($"Move {nodeA.Text} under {lNodeAinColB.Parent.Text}");
            lNumOfUpdates++;
        }
    }

    //Search Result collection, if Original collection does not have nodeB, we must create it (add)
    foreach (TreeNode nodeB in colB)
    {
        if (!colA.Contains(nodeB, new TestNodeEquality()))
        {
            W($"Add {nodeB.Text}" + ((nodeB.Parent != null)?$" under {nodeB.Parent.Text}":""));
            lNumOfUpdates++;
        }
    }

    return lNumOfUpdates;
}

我没有找到任何适合我的问题的主题,也没有找到有价值的资源,我真的很想避免重新发明轮子。

问题:

  • 是否存在现有和有效的算法(名称/参考)?这种算法/动作叫什么(Tree Diff/Merge/Lookup/..)?

  • 我可以以任何方式优化算法吗?

【问题讨论】:

标签: c# .net algorithm linq tree


【解决方案1】:

我认为您在这里不需要一些复杂的递归算法。只需将您的结果节点放入 name-parent 字典并检查:

  • 原始节点是否在字典中
  • 原节点的父节点是否改变
  • 结果中是否存在原始节点中不存在的节点

Dictionary 还提供了 O(1) 来搜索节点,所以这也是一个优化。 Except 操作也是如此,它是快速设置操作。

代码:

var originalNodes = new List<TreeNode>(); // TreeNodeCollection 
var nodes = new List<TreeNode>();         // TreeNodeCollection 
var parentByName = nodes.ToDictionary(n => n.Text, n => n.Parent);

foreach(var originalNode in originalNodes)
{
    TreeNode parent;
    if (!parentByName.TryGetValue(originalNode.Text, out parent))
    {
        // removed - there is no key for original node name
        continue;
    }

    if (originalNode.Parent?.Text != parent?.Text)
    {
        // moved from originalNode.Parent to parent
        continue;
    }
}

// these guys are added
var added = parentByName.Keys.Except(originalNodes.Select(n => n.Text))

【讨论】:

  • 简单而强大。谢谢!
【解决方案2】:

我周围没有 C# 环境,所以我想我可以在 Python 中实现它——他们称之为可执行伪代码,对吧? ;)

def node(id, children=[]):
    assert all(isinstance(child, dict) for child in children)
    return {'id': id, 'children': children}

tree1 = [
    node('a', [
        node('1'),
        node('2'),
    ]),
    node('b', [
        node('5', [
            node('*'),
        ]),
    ]),
    node('c'),
    node('f', [
        node('7'),
    ]),
    node('h'),
]


tree2 = [
    node('a', [
        node('2'),
    ]),
    node('c', [
        node('3'),
    ]),
    node('d', [
        node('1', [
            node('*'),
        ]),
        node('5'),
    ]),
    node('e', [
        node('6'),
    ]), 
    node('g', [
        node('4'),
    ]),
    node('h'),
]

def walk(tree, fn, parent=None):
    for node in tree:
        fn(node, parent)
        walk(node.get('children', ()), fn, parent=node)


def get_all_nodes_and_parents(tree):
    nodes = {}
    parents = {}
    def add_node(node, parent):
        nodes[node['id']] = node
        parents[node['id']] = (parent['id'] if parent else None)
    walk(tree, add_node)
    return (nodes, parents)


def treediff(t1, t2):
    n1, p1 = get_all_nodes_and_parents(t1)
    n2, p2 = get_all_nodes_and_parents(t2)
    new_nodes = set(n2.keys()) - set(n1.keys())
    del_nodes = set(n1.keys()) - set(n2.keys())

    for node_id in sorted(new_nodes):
        yield 'create node %s' % node_id

    for node_id in sorted(del_nodes):
        yield 'delete node %s' % node_id

    for node_id in n2:
        if p1.get(node_id) != p2.get(node_id):
            yield 'move node %s from %s to %s' % (node_id, p1.get(node_id), p2.get(node_id))

for op in treediff(tree1, tree2):
    print(op)

这个输出

create node 3
create node 4
create node 6
create node d
create node e
create node g
delete node 7
delete node b
delete node f
move node 3 from None to c
move node 1 from a to d
move node * from 5 to 1
move node 5 from b to d
move node 6 from None to e
move node 4 from None to g

进一步的改进是直接在其新父节点下创建新节点,但这需要增加复杂性来跟踪创建顺序,因此父节点在其新子节点之前创建。

【讨论】:

  • Sergey 在没有递归等的情况下让它变得更加简单。但是我很高兴有其他语言解决方案,感谢!
  • 当然,如果树有一个 API 来获取所有节点,而不管它们的深度如何(并且如果它们提供父属性),则不需要递归:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-03-29
  • 2017-04-05
  • 1970-01-01
  • 2019-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多