加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

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

发布时间:2020-12-15 17:44:52 所属栏目:百科 来源:网络整理
导读:当一个部署项目包含第二个部署项目的项目输出时,通常会发生此错误,而第二个项目包含第一个项目的输出. 我有一个检查循环依赖的方法.在输入中,我们有一个字典,其中包含例如“A”, “B”,“C”和“B”, “A”,“D”,这意味着A取决于B和C,并且我们具有与A- B的
当一个部署项目包含第二个部署项目的项目输出时,通常会发生此错误,而第二个项目包含第一个项目的输出.

我有一个检查循环依赖的方法.在输入中,我们有一个字典,其中包含例如<“A”,< “B”,“C”>>和<“B”,< “A”,“D”>,这意味着A取决于B和C,并且我们具有与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,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>(),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,out value))
            return value;
        return defaultValue;
    }

    private static void TryPush<T>(T node,Stack<KeyValuePair<T,IEnumerator<T>>> stack,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,VisitState> visited)
    {
        var stack = new Stack<KeyValuePair<T,IEnumerator<T>>>();
        var cycles = new List<List<T>>();

        TryPush(root,stack,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,cycles);
            }
        }
        return cycles;
    }

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

    public static List<List<T>> FindCycles<T,null) ?? Enumerable.Empty<T>());
    }
}

这在N * log(N)E处应该是相当有效的,其中N是节点数,E是边数. Log(N)来自构建访问哈希表,可以通过使每个节点记住它的VisitState来消除.这似乎是合理的;在以下测试工具中,找到17897个周期的平均长度4393个10000个节点,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;
    }
}

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读