如何使用 c# 查找递归函数采用的路径

本文关键字:路径 递归函数 何使用 查找 | 更新日期: 2023-09-27 17:56:29

我有一个对象,它有一个属性,允许我创建同一对象(嵌套对象)的另一个实例。我需要搜索一个列表并找出嵌套中对象的第一个出现。

一旦找到一个,我想找出找到它的确切路径。

我有以下课程

public class ReportRelationMapping : IReportRelationMapping
{
    string Name { get; set; }
    IReportRelation LocalRelation { get; set; }
    IReportRelation ForeignRelation { get; set; }
    IReportRelationMapping RelatedThrough { get; set; } // This creates and instance of itself creating a chain
}

假设我有上述类的以下列表

List<IReportRelationMapping> myList = new List<ReportRelationMapping> 
{
    new ReportRelationMapping 
    {
        Name = "A",
        LocalRelation = ..., 
        ForeignRelation = ...
        RelatedThrough = new ReportRelationMapping
        {
            Name = "B",
            LocalRelation = ..., 
            ForeignRelation = ...
            RelatedThrough = new ReportRelationMapping
            {
                Name = "C",
                LocalRelation = ..., 
                ForeignRelation = ...
                RelatedThrough = new ReportRelationMapping
                {
                    Name = "D",
                    LocalRelation = ..., 
                    ForeignRelation = ...
                    RelatedThrough = new ReportRelationMapping
                }
            }
        }
    }
    ,new ReportRelationMapping 
    {
        Name = "E",
        LocalRelation = ..., 
        ForeignRelation = ...
        RelatedThrough = new ReportRelationMapping
        {
            Name = "F",
            LocalRelation = ..., 
            ForeignRelation = ...
        }
    }
    ,new ReportRelationMapping 
    {
        Name = "G",
        LocalRelation = ..., 
        ForeignRelation = ...
    }
}

我需要找出找到第一个"C"所需的确切路径。我需要获取以下列表

List<ReportRelationMapping> pathToTarget = new List<ReportRelationMapping> 
{
    new ReportRelationMapping 
    {
        Name = "A",
        LocalRelation = ..., 
        ForeignRelation = ...
    }
    ,new ReportRelationMapping 
    {
        Name = "B",
        LocalRelation = ..., 
        ForeignRelation = ...
    }
    ,new ReportRelationMapping 
    {
        Name = "C",
        LocalRelation = ..., 
        ForeignRelation = ...
    }
}

我编写了一个递归方法,可以正确找到"C",但它没有正确捕获它所采用的路径。这是我所做的

private void GetAvailableRelation(List<IReportRelationMapping> relationsMappings, string belongsTo, ref List<IReportRelationMapping> pathToTarget)
{
    var requiredRelation = relationsMappings.Where(x => x.LocalRelation.TableAlias == belongsTo || x.ForeignRelation.TableAlias == belongsTo).FirstOrDefault();
    if (requiredRelation == null)
    {
        //At this point we know there is no match on the top level, lets check the nested level
        var relatedRelations = new List<IReportRelationMapping>();
        foreach (var relationsMapping in relationsMappings)
        {
            if (relationsMapping.RelatedThrough != null)
            {
                relatedRelations.Add(relationsMapping.RelatedThrough);
            }
        }
        if (relatedRelations.Any())
        {
            GetAvailableRelation(relatedRelations, belongsTo, ref pathToTarget);
        }
        else
        {
            // Since we reached the last node and count not find a matching, reset the pathToTarget list
            pathToTarget.Clear();
        }
    }
    if (requiredRelation != null)
    {
        //Check if relation exists before adding it.
        pathToTarget.Add(requiredRelation);
    }
}

The problem seems to be that after I call the method `GetAvailableRelation` recursively, the value of `requiredRelation` will become `null` when the last line come there is nothing to add to my `pathToTarget`. 

问:如何正确生成pathToTarget列表?

如何使用 c# 查找递归函数采用的路径

简短的回答:停止变异事物。

您正在传递对变量的引用,

该变量本身就是对可变列表的引用,然后对后者进行变异。这是导致递归代码混淆的秘诀。(您永远不会改变引用;如果你永远不会改变变量,为什么要通过 ref 传递变量???我认为您可能对 C# 中的引用语义有根本的误解。

相反,原因如下。从基础知识开始:

  • 我的函数有输入和输出
  • 输出完全取决于输入
  • 输入和输出永不变异

好的,现在想想:既然你选择遵循这些规则,输入和输出必须是什么?输入是某物的不可变序列。输出是某物的不可变序列。伟大。

由于这是一种递归方法,我们知道以下附加事实:

  • 将有一个简单的基本情况,我们检测到解决方案是微不足道的,并返回微不足道的解决方案。
  • 将有一个递归情况,我们将问题划分为较小的问题,递归地解决它们,然后将递归解决方案组合成较大问题的解决方案。

现在您可以回答以下问题:

  • 我的方法的参数类型应该是什么?
  • 我的方法的返回类型应该是什么?
  • 什么是微不足道的案例?
  • 如何将一个重要的案例分解为一个或多个更简单的问题?
  • 如何将这些问题的解决方案组合成更大问题的解决方案?

让我们勾勒出一些答案:

  • 该方法采用不可变的项序列和搜索项。
  • 该方法返回一个不可变的项序列,该序列是一个路径。
  • 有两个微不足道的案例。情况一是:当前项目序列包含搜索项目。简单的解决方案是包含匹配项的路径。 情况二是:当前项目序列为空。简单的解决方案是路径为空。
  • 递归的情况是:找到一个更简单的问题并解决它;这给了一条路径。如果路径为空,则返回一个空路径,因为这意味着未找到该项。否则,将当前项附加到路径前面并返回它。

现在你可以写你的方法了吗?

最好的解决方案是使用临时对象来模拟堆栈来递归代码,当您找到您的对象时,该堆栈将包含它的确切路径。

我想

我通过尝试遵循 Eric Lippert 正确答案中列出的规则解决了这个问题。

这是似乎正在工作的功能。

    private List<IReportRelationMapping> GetAvailableRelation(List<IReportRelationMapping> relationsMappings, string belongsTo)
    {
        List<IReportRelationMapping> pathToTarget = new List<IReportRelationMapping>();
        var requiredRelation = relationsMappings.Where(x => x.LocalRelation.TableAlias == belongsTo || x.ForeignRelation.TableAlias == belongsTo).FirstOrDefault();
        if (requiredRelation != null)
        {
            //Handle the top level
            pathToTarget.Add(requiredRelation);
        } else {
            //At this point we know there is no match on the top level, lets check the nested level
            var relatedRelations = new List<IReportRelationMapping>();
            foreach (var relationsMapping in relationsMappings)
            {
                if (relationsMapping.RelatedThrough != null)
                {
                    //Add the path in between previous and next
                    pathToTarget.Add(relationsMapping);
                    foreach (var subRelation in relationsMapping.RelatedThrough)
                    {
                        relatedRelations.Add(subRelation);
                    }
                }
            }
            if (relatedRelations.Any())
            {
                //Now we know there is at least one more nested level that we need to check
                var subPathsToTarget = GetAvailableRelation(relatedRelations, belongsTo);
              if (subPathsToTarget.Any())
                {
                    //prepend the current items to the path
                    pathToTarget = pathToTarget.Concat(subPathsToTarget).ToList();
                }
                else
                {
                    //At this point we know we reach the final node and the item was not found.
                    pathToTarget.Clear();
                }
            }
        }
        return pathToTarget;
    }

更新

现在我的对象接受这样的RelatedThrough列表

public class ReportRelationMapping : IReportRelationMapping
{
    public string Name { get; set; }
    public SqlJoinStatement JoinType { get; set; }
    public IReportRelation LocalRelation { get; set; }
    public IReportRelation ForeignRelation { get; set; }
    public List<IReportRelationMapping> RelatedThrough { get; set; }
}