草庐IT

c# - 在C#中,如何找到循环依赖链?

coder 2024-05-24 原文

当一个部署项目包含第二个部署项目的项目输出,而第二个项目包含第一个项目的输出时,通常会发生此错误。

我有一个检查循环依赖的方法。在输入中,我们有一个包含例如 <"A", < "B", "C" >> 的字典。和 <"B", < "A", "D" >> ,这意味着 A取决于 BC我们与 A->B 有循环依赖关系.

但通常情况更复杂,有一个依赖链。 如何修改此方法以找到依赖链?例如,我想要一个包含链 A->B->A 的变量, 而不是类 A与类 B 有冲突.

private void FindDependency(IDictionary<string, IEnumerable<string>> serviceDependence)

最佳答案

在图中查找循环的一种简单方法是使用递归深度优先图着色算法,其中节点被标记为“正在访问”或“已访问”。如果,当访问一个节点时,发现它已经处于“正在访问”状态,那么你就有了一个循环。可以跳过标记为“已访问”的节点。例如:

public class DependencyExtensions
{
    enum VisitState
    {
        NotVisited,
        Visiting,
        Visited
    };

    public static TValue ValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue)
    {
        TValue value;
        if (dictionary.TryGetValue(key, out value))
            return value;
        return defaultValue;
    }

    static void DepthFirstSearch<T>(T node, Func<T, IEnumerable<T>> lookup, List<T> parents, Dictionary<T, VisitState> visited, List<List<T>> cycles)
    {
        var state = visited.ValueOrDefault(node, VisitState.NotVisited);
        if (state == VisitState.Visited)
            return;
        else if (state == VisitState.Visiting)
        {
            // Do not report nodes not included in the cycle.
            cycles.Add(parents.Concat(new[] { node }).SkipWhile(parent => !EqualityComparer<T>.Default.Equals(parent, node)).ToList());
        }
        else
        {
            visited[node] = VisitState.Visiting;
            parents.Add(node);
            foreach (var child in lookup(node))
                DepthFirstSearch(child, lookup, parents, visited, cycles);
            parents.RemoveAt(parents.Count - 1);
            visited[node] = VisitState.Visited;
        }
    }

    public static List<List<T>> FindCycles<T>(this IEnumerable<T> nodes, Func<T, IEnumerable<T>> edges)
    {
        var cycles = new List<List<T>>();
        var visited = new Dictionary<T, VisitState>();
        foreach (var node in nodes)
            DepthFirstSearch(node, edges, new List<T>(), visited, cycles);
        return cycles;
    }

    public static List<List<T>> FindCycles<T, TValueList>(this IDictionary<T, TValueList> listDictionary)
        where TValueList : class, IEnumerable<T>
    {
        return listDictionary.Keys.FindCycles(key => listDictionary.ValueOrDefault(key, null) ?? Enumerable.Empty<T>());
    }
}

然后,您可以像这样使用它:

        var serviceDependence = new Dictionary<string, List<string>>
        {
            { "A", new List<string> { "A" }},
            { "B", new List<string> { "C", "D" }},
            { "D", new List<string> { "E" }},
            { "E", new List<string> { "F", "Q" }},
            { "F", new List<string> { "D" }},
        };
        var cycles = serviceDependence.FindCycles();
        Debug.WriteLine(JsonConvert.SerializeObject(cycles, Formatting.Indented));
        foreach (var cycle in cycles)
        {
            serviceDependence[cycle[cycle.Count - 2]].Remove(cycle[cycle.Count - 1]);
        }
        Debug.Assert(serviceDependence.FindCycles().Count == 0);

更新

您的问题已更新为请求用于查找循环依赖项的“最有效算法”。原始答案中的代码是递归的,因此对于数千级深度的依赖链,有可能出现 StackOverflowException。这是一个带有显式堆栈变量的非递归版本:

public static class DependencyExtensions
{
    enum VisitState
    {
        NotVisited,
        Visiting,
        Visited
    };

    public static TValue ValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue)
    {
        TValue value;
        if (dictionary.TryGetValue(key, out value))
            return value;
        return defaultValue;
    }

    private static void TryPush<T>(T node, Func<T, IEnumerable<T>> lookup, Stack<KeyValuePair<T, IEnumerator<T>>> stack, Dictionary<T, VisitState> visited, List<List<T>> cycles)
    {
        var state = visited.ValueOrDefault(node, VisitState.NotVisited);
        if (state == VisitState.Visited)
            return;
        else if (state == VisitState.Visiting)
        {
            Debug.Assert(stack.Count > 0);
            var list = stack.Select(pair => pair.Key).TakeWhile(parent => !EqualityComparer<T>.Default.Equals(parent, node)).ToList();
            list.Add(node);
            list.Reverse();
            list.Add(node);
            cycles.Add(list);
        }
        else
        {
            visited[node] = VisitState.Visiting;
            stack.Push(new KeyValuePair<T, IEnumerator<T>>(node, lookup(node).GetEnumerator()));
        }
    }

    static List<List<T>> FindCycles<T>(T root, Func<T, IEnumerable<T>> lookup, Dictionary<T, VisitState> visited)
    {
        var stack = new Stack<KeyValuePair<T, IEnumerator<T>>>();
        var cycles = new List<List<T>>();

        TryPush(root, lookup, stack, visited, cycles);
        while (stack.Count > 0)
        {
            var pair = stack.Peek();
            if (!pair.Value.MoveNext())
            {
                stack.Pop();                    
                visited[pair.Key] = VisitState.Visited;
                pair.Value.Dispose();
            }
            else
            {
                TryPush(pair.Value.Current, lookup, stack, visited, cycles);
            }
        }
        return cycles;
    }

    public static List<List<T>> FindCycles<T>(this IEnumerable<T> nodes, Func<T, IEnumerable<T>> edges)
    {
        var cycles = new List<List<T>>();
        var visited = new Dictionary<T, VisitState>();
        foreach (var node in nodes)
            cycles.AddRange(FindCycles(node, edges, visited));
        return cycles;
    }

    public static List<List<T>> FindCycles<T, TValueList>(this IDictionary<T, TValueList> listDictionary)
        where TValueList : class, IEnumerable<T>
    {
        return listDictionary.Keys.FindCycles(key => listDictionary.ValueOrDefault(key, null) ?? Enumerable.Empty<T>());
    }
}

这在 N*log(N) + E 时应该相当有效,其中 N 是节点数,E 是节点数的边缘。 Log(N) 来自构建visited 哈希表,可以通过让每个节点记住它的VisitState 来消除。这似乎是合理的表现;在下面的测试工具中,在 10000 个节点中找到平均长度为 4393 的 17897 个循环,总依赖项为 125603,所需时间约为 10.2 秒:

public class TestClass
{
    public static void TestBig()
    {
        var elapsed = TestBig(10000);
        Debug.WriteLine(elapsed.ToString());
    }

    static string GetName(int i)
    {
        return "ServiceDependence" + i.ToString();
    }

    public static TimeSpan TestBig(int count)
    {
        var serviceDependence = new Dictionary<string, List<string>>();
        for (int iItem = 0; iItem < count; iItem++)
        {
            var name = GetName(iItem);
            // Add several forward references.
            for (int iRef = iItem - 1; iRef > 0; iRef = iRef / 2)
                serviceDependence.Add(name, GetName(iRef));
            // Add some backwards references.
            if (iItem > 0 && (iItem % 5 == 0))
                serviceDependence.Add(name, GetName(iItem + 5));
        }

        // Add one backwards reference that will create some extremely long cycles.
        serviceDependence.Add(GetName(1), GetName(count - 1));

        List<List<string>> cycles;

        var stopwatch = new Stopwatch();
        stopwatch.Start();
        try
        {
            cycles = serviceDependence.FindCycles();
        }
        finally
        {
            stopwatch.Stop();
        }

        var elapsed = stopwatch.Elapsed;

        var averageLength = cycles.Average(l => (double)l.Count);
        var total = serviceDependence.Values.Sum(l => l.Count);

        foreach (var cycle in cycles)
        {
            serviceDependence[cycle[cycle.Count - 2]].Remove(cycle[cycle.Count - 1]);
        }
        Debug.Assert(serviceDependence.FindCycles().Count == 0);

        Console.WriteLine(string.Format("Time to find {0} cycles of average length {1} in {2} nodes with {3} total dependencies: {4}", cycles.Count, averageLength, count, total, elapsed));
        Console.ReadLine();
        System.Environment.Exit(0);

        return elapsed;
    }
}

关于c# - 在C#中,如何找到循环依赖链?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29703972/

有关c# - 在C#中,如何找到循环依赖链?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  5. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  6. ruby - 树顶语法无限循环 - 2

    我脑子里浮现出一些关于一种新编程语言的想法,所以我想我会尝试实现它。一位friend建议我尝试使用Treetop(Rubygem)来创建一个解析器。Treetop的文档很少,我以前从未做过这种事情。我的解析器表现得好像有一个无限循环,但没有堆栈跟踪;事实证明很难追踪到。有人可以指出入门级解析/AST指南的方向吗?我真的需要一些列出规则、常见用法等的东西来使用像Treetop这样的工具。我的语法分析器在GitHub上,以防有人希望帮助我改进它。class{initialize=lambda(name){receiver.name=name}greet=lambda{IO.puts("He

  7. ruby-on-rails - 在 Ruby 中循环遍历多个数组 - 2

    我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代

  8. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  9. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  10. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

随机推荐