【问题标题】:C# Recursion Duplicate IdC# 递归重复 ID
【发布时间】:2017-03-10 20:21:37
【问题描述】:

我有一个递归方法,可以使用一种索引设置节点 ID,我一直在努力挠头,因为它一直抱怨我有重复的 ID。

我确定整数类型是按值传递的,所以我不确定这里有什么问题。

IList<DocumentNode> contents = new List<DocumentNode>();
int index = 0;
LoadContents(null, index++, result, contents);  

private void LoadContents(DocumentNodeparentNode, int nodeId, WordContent wordContent, IList<DocumentNode> contents)
{    
    foreach (OtheClassContent childContent in wordContent.ChildrenContent)
    {
        var node = new DocumentNodeViewModel(nodeId,
            parentNode?.NodeId,
            childContent.SortOrder,
            childContent.Depth);

        contents.Add(node);
        nodeId++;
        LoadContents(node, nodeId, childContent, contents);
    }
}

// This line wont work
IDictionary<int, int?> myDictionary = contents.ToDictionary(a => a.Id, a => a.ParentId);

文档节点是简单的树节点结构

public class DocumentNode
{
    public DocumentNode(
        int nodeId,
        int? parentNodeId, 
        short sortOrder,
        short branchLevel)
    {
        NodeId = nodeId;

        SortOrder = sortOrder;
        BranchLevel = branchLevel;
        ParentNode = null;
        ParentNodeId = parentNodeId;
        ChildNodes = Enumerable.Empty<DocumentNode>();
    }

    public int NodeId { get; private set; }
    public int? ParentNodeId { get; private set; }
    public short SortOrder { get; private set; }
    public DocumentNode ParentNode { get; set; }
    public IEnumerable<DocumentNode> ChildNodes { get; set; }
    public short BranchLevel { get; private set; }
}

有谁知道我做错了什么?

【问题讨论】:

  • 示例:您使用nodeID = 3 创建节点并递增到 4。然后递归调用创建节点 4、5、6。但回到原来的nodeID 仍然是 4。循环继续,您创建节点 4 - 但它已经创建。如果您通过ref 传递nodeID,问题就解决了。

标签: c# .net recursion data-structures tree


【解决方案1】:

您的问题是整数是按值传递的。似乎在您的 foreach 循环中添加一个节点时,您添加了一个节点,然后发送递增的 id 进行递归,并且主调用和递归调用正在使用相同的 id。

比如说,你第一次调用 wordContent.ChildrenContent 中有 2 个元素,它会创建第一个 id = 0 的节点并将其递增到 1,然后调用 id = 1 的递归。假设你有 2递归调用中 wordContent.ChildrenContent 中的元素也是如此,在这种情况下,递归会添加 id 为 1 和 2 的新节点,并且控制返回到 main 方法。在这里,id 仍然是 1,现在创建了另一个 id 为 1 的节点。

如果你通过引用传递 id,我认为你会没事的。

IList<DocumentNode> contents = new List<DocumentNode>();
int index = 0;
LoadContents(null, ref index++, result, contents);  

private void LoadContents(DocumentNode parentNode, ref int nodeId, WordContent wordContent, IList<DocumentNode> contents)
{    
    foreach (OtheClassContent childContent in wordContent.ChildrenContent)
    {
        var node = new DocumentNodeViewModel(nodeId,
            parentNode?.NodeId,
            childContent.SortOrder,
            childContent.Depth);

        contents.Add(node);
        nodeId++;
        LoadContents(node, ref nodeId, childContent, contents);
    }
}

// This line wont work
IDictionary<int, int?> myDictionary = contents.ToDictionary(a => a.Id, a => a.ParentId);

【讨论】:

    【解决方案2】:

    您正在按值传递nodeId,所以会发生以下情况:

    Child1: nodeId = 0
        Grandchild1_1: nodeId = 1
        Grandchild1_2: nodeId = 2
    Child2: nodeId = 1
        Grandchild2_1: nodeId = 2
        Grandchild2_2: nodeId = 3
    ...
    

    如果您通过引用传递它,所有递归调用都可以增加相同的值,从而产生:

    Child1: nodeId = 0
        Grandchild1_1: nodeId = 1
        Grandchild1_2: nodeId = 2
    Child2: nodeId = 3
        Grandchild2_1: nodeId = 4
        Grandchild2_2: nodeId = 5
    ...
    

    基本上,通过作为值传递,您正在为每个孩子创建nodeId 的新副本。从较低级别的递归调用返回后,较高级别的调用无法看到对 nodeId 的任何更改,因为所有更改都已对副本进行。

    使用ref 的替代方法是从函数返回最后一个节点计数。因此,这是您的两个替代方案的简化想法。

    参考资料:

    private void RecursiveCallWithRefs (Node current, ref int nodeId)
    {
        foreach (var child in current.Children)
        {
            child.FillOutContents(nodeId);
            ++nodeId;
            RecursiveCallWithRefs(child, ref nodeId);
        }
    }
    

    返回值:

    private int RecursiveCallWithReturns (Node current, int nodeId)
    {
        foreach (var child in current.Children)
        {
            child.FillOutContents(nodeId);
            nodeId = RecursiveCallWithReturns(child, nodeId + 1);
        }
    
        return nodeId;
    }
    

    当然,如果我是你,我会把它封装在一个更抽象的调用中:

    private void FillOutNodesWithRefs (Node startingNode)
    {
        int startingId = 0;
        RecursiveCallWithRefs(startingNode, ref startingId);
    }
    
    private void FillOutNodesWithReturns (Node startingNode)
    {
        RecursiveCallWithReturns(startingNode, 0);
    }
    

    【讨论】:

      猜你喜欢
      • 2015-09-10
      • 2018-11-12
      • 1970-01-01
      • 1970-01-01
      • 2013-08-17
      • 2017-07-26
      • 2022-01-26
      • 1970-01-01
      • 2018-10-03
      相关资源
      最近更新 更多