using System; using System.Collections.Generic; using System.Linq; using ICD.Common.Properties; using ICD.Common.Utils.Collections; using ICD.Common.Utils.Extensions; namespace ICD.Common.Utils { public static class RecursionUtils { /// /// Returns a sequence of the cliques in the graph. /// /// /// /// /// public static IEnumerable> GetCliques(IEnumerable nodes, Func> getAdjacent) { if (nodes == null) throw new ArgumentNullException("nodes"); if (getAdjacent == null) throw new ArgumentNullException("getAdjacent"); Dictionary> map = nodes.ToDictionary(n => n, getAdjacent); IcdHashSet visited = new IcdHashSet(); return map.Keys .Where(n => !visited.Contains(n)) .Select(node => GetClique(map, visited, node)); } /// /// Gets the clique containing the given node. /// /// /// /// /// public static IEnumerable GetClique(T node, Func> getAdjacent) { if (node == null) throw new ArgumentNullException("node"); if (getAdjacent == null) throw new ArgumentNullException("getAdjacent"); return BreadthFirstSearch(node, getAdjacent); } /// /// Gets the clique containing the given node. /// /// /// /// /// public static IEnumerable GetClique(IDictionary> map, T node) { if (map == null) throw new ArgumentNullException("map"); // ReSharper disable once CompareNonConstrainedGenericWithNull if (node == null) throw new ArgumentNullException("node"); return GetClique(map, new IcdHashSet(), node); } /// /// Gets the clique containing the node. /// /// /// /// /// /// private static IEnumerable GetClique(IDictionary> map, IcdHashSet visited, T node) { if (map == null) throw new ArgumentNullException("map"); if (visited == null) throw new ArgumentNullException("visited"); // ReSharper disable once CompareNonConstrainedGenericWithNull if (node == null) throw new ArgumentNullException("node"); return GetCliqueIterator(map, visited, node); } /// /// Gets the clique containing the node. /// /// /// /// /// /// private static IEnumerable GetCliqueIterator(IDictionary> map, IcdHashSet visited, T node) { if (!visited.Add(node)) yield break; IEnumerable adjacent; if (!map.TryGetValue(node, out adjacent)) yield break; yield return node; foreach (T item in adjacent.SelectMany(a => GetClique(map, visited, a))) yield return item; } /// /// Returns true if there is a path from the given root to the given destination node. /// /// /// /// /// /// public static bool BreadthFirstSearch(T root, T destination, Func> getChildren) { if (getChildren == null) throw new ArgumentNullException("getChildren"); return BreadthFirstSearch(root, destination, getChildren, EqualityComparer.Default); } /// /// Returns true if there is a path from the given root to the given destination node. /// /// /// /// /// /// /// public static bool BreadthFirstSearch(T root, T destination, Func> getChildren, IEqualityComparer comparer) { if (getChildren == null) throw new ArgumentNullException("getChildren"); if (comparer == null) throw new ArgumentNullException("comparer"); return BreadthFirstSearchPath(root, destination, getChildren, comparer) != null; } /// /// Returns all of the nodes in the tree via breadth-first search. /// /// /// /// /// public static IEnumerable BreadthFirstSearch(T root, Func> getChildren) { if (getChildren == null) throw new ArgumentNullException("getChildren"); return BreadthFirstSearchIterator(root, getChildren); } /// /// Returns all of the nodes in the graph via breadth-first search. /// /// /// /// /// private static IEnumerable BreadthFirstSearchIterator(T root, Func> getChildren) { IcdHashSet visited = new IcdHashSet {root}; Queue process = new Queue(); process.Enqueue(root); T current; while (process.Dequeue(out current)) { yield return current; foreach (T child in getChildren(current).Where(c => !visited.Contains(c))) { visited.Add(child); process.Enqueue(child); } } } /// /// Returns the shortest path from root to destination via breadth-first search. /// /// /// /// /// /// [CanBeNull] public static IEnumerable BreadthFirstSearchPath(T root, T destination, Func> getChildren) { if (getChildren == null) throw new ArgumentNullException("getChildren"); return BreadthFirstSearchPath(root, destination, getChildren, EqualityComparer.Default); } /// /// Returns the shortest path from root to destination via breadth-first search. /// /// /// /// /// /// /// [CanBeNull] public static IEnumerable BreadthFirstSearchPath(T root, T destination, Func> getChildren, IEqualityComparer comparer) { if (getChildren == null) throw new ArgumentNullException("getChildren"); if (comparer == null) throw new ArgumentNullException("comparer"); // Edge case - root and destination are the same if (comparer.Equals(root, destination)) return new[] {root}; Queue queue = new Queue(); queue.Enqueue(root); Dictionary nodeParents = new Dictionary(comparer); T current; while (queue.Dequeue(out current)) { foreach (T node in getChildren(current)) { if (nodeParents.ContainsKey(node)) continue; queue.Enqueue(node); nodeParents.Add(node, current); // Found a path to the destination if (comparer.Equals(node, destination)) return GetPath(destination, root, nodeParents, comparer).Reverse(); } } return null; } /// /// Returns the shortest path from root to each destination via breadth-first search. /// /// /// /// /// /// public static IEnumerable>> BreadthFirstSearchPathManyDestinations(T root, IEnumerable destinations, Func> getChildren) { if (destinations == null) throw new ArgumentNullException("destinations"); if (getChildren == null) throw new ArgumentNullException("getChildren"); return BreadthFirstSearchPathManyDestinations(root, destinations, getChildren, EqualityComparer.Default); } /// /// Returns the shortest path from root to each destination via breadth-first search. /// /// /// /// /// /// /// public static IEnumerable>> BreadthFirstSearchPathManyDestinations(T root, IEnumerable destinations, Func> getChildren, IEqualityComparer comparer) { if (destinations == null) throw new ArgumentNullException("destinations"); if (getChildren == null) throw new ArgumentNullException("getChildren"); if (comparer == null) throw new ArgumentNullException("comparer"); IcdHashSet destinationsToBeProcessed = new IcdHashSet(destinations); // Edge case, root is the destination foreach (T destination in destinationsToBeProcessed.Where(destination => comparer.Equals(root, destination)).ToArray()) { destinationsToBeProcessed.Remove(destination); yield return new KeyValuePair>(destination, new[] {root}); } if (destinationsToBeProcessed.Count == 0) yield break; Queue queue = new Queue(); queue.Enqueue(root); Dictionary nodeParents = new Dictionary(comparer); while (queue.Count > 0) { T current = queue.Dequeue(); foreach (T node in getChildren(current).Where(node => !nodeParents.ContainsKey(node))) { queue.Enqueue(node); nodeParents.Add(node, current); T closureNode = node; foreach (T destination in destinationsToBeProcessed.Where(destination => comparer.Equals(closureNode, destination)).ToArray()) { destinationsToBeProcessed.Remove(destination); yield return new KeyValuePair>(destination, GetPath(destination, root, nodeParents, comparer).Reverse()); } if (destinationsToBeProcessed.Count == 0) yield break; } } } /// /// Walks through a map of nodes from the starting point. /// /// /// /// /// /// /// private static IEnumerable GetPath(T start, T end, IDictionary nodeParents, IEqualityComparer comparer) { IcdHashSet visited = new IcdHashSet(); while (true) { yield return start; if (comparer.Equals(start, end)) break; visited.Add(start); T next; if (!nodeParents.TryGetValue(start, out next)) break; if (visited.Contains(next)) break; start = next; } } } }