using System; using System.Collections.Generic; using System.Linq; using Collections.Pooled; using MoreLinq; namespace MoonTools.Core.Graph { public class DirectedWeightedMultiGraph : IGraph where TNode : IEquatable { protected HashSet nodes = new HashSet(); protected Dictionary> neighbors = new Dictionary>(); protected Dictionary<(TNode, TNode), HashSet> edges = new Dictionary<(TNode, TNode), HashSet>(); protected Dictionary IDToEdge = new Dictionary(); protected Dictionary weights = new Dictionary(); protected Dictionary edgeToEdgeData = new Dictionary(); // store search sets to prevent GC protected HashSet openSet = new HashSet(); protected HashSet closedSet = new HashSet(); protected Dictionary gScore = new Dictionary(); protected Dictionary fScore = new Dictionary(); protected Dictionary cameFrom = new Dictionary(); public IEnumerable Nodes => nodes; public void AddNode(TNode node) { if (Exists(node)) { return; } nodes.Add(node); neighbors[node] = new HashSet(); } public void AddNodes(params TNode[] nodes) { foreach (var node in nodes) { AddNode(node); } } public void AddEdge(TNode v, TNode u, int weight, TEdgeData data) { CheckNodes(v, u); var id = Guid.NewGuid(); neighbors[v].Add(u); weights.Add(id, weight); if (!edges.ContainsKey((v, u))) { edges[(v, u)] = new HashSet(); } edges[(v, u)].Add(id); edgeToEdgeData.Add(id, data); IDToEdge.Add(id, (v, u)); } public void AddEdges(params (TNode, TNode, int, TEdgeData)[] edges) { foreach (var edge in edges) { AddEdge(edge.Item1, edge.Item2, edge.Item3, edge.Item4); } } public void Clear() { nodes.Clear(); neighbors.Clear(); weights.Clear(); edges.Clear(); IDToEdge.Clear(); edgeToEdgeData.Clear(); } private void CheckNodes(params TNode[] givenNodes) { foreach (var node in givenNodes) { if (!Exists(node)) { throw new ArgumentException($"Vertex {node} does not exist in the graph"); } } } public IEnumerable EdgeIDs(TNode v, TNode u) { CheckNodes(v, u); return edges.ContainsKey((v, u)) ? edges[(v, u)] : Enumerable.Empty(); } public bool Exists(TNode node) => nodes.Contains(node); public bool Exists(TNode v, TNode u) { CheckNodes(v, u); return edges.ContainsKey((v, u)); } public IEnumerable Neighbors(TNode node) { CheckNodes(node); return neighbors.ContainsKey(node) ? neighbors[node] : Enumerable.Empty(); } public IEnumerable Weights(TNode v, TNode u) { CheckNodes(v, u); return edges[(v, u)].Select(id => weights[id]); } public TEdgeData EdgeData(Guid id) { if (!edgeToEdgeData.ContainsKey(id)) { throw new ArgumentException($"Edge {id} does not exist in the graph."); } return edgeToEdgeData[id]; } private IEnumerable ReconstructPath(PooledDictionary cameFrom, TNode currentNode) { while (cameFrom.ContainsKey(currentNode)) { var edgeID = cameFrom[currentNode]; var edge = IDToEdge[edgeID]; currentNode = edge.Item1; yield return edgeID; } } public IEnumerable AStarShortestPath(TNode start, TNode end, Func heuristic) { CheckNodes(start, end); var openSet = new PooledSet(ClearMode.Always); var closedSet = new PooledSet(ClearMode.Always); var gScore = new PooledDictionary(ClearMode.Always); var fScore = new PooledDictionary(ClearMode.Always); var cameFrom = new PooledDictionary(ClearMode.Always); openSet.Add(start); gScore[start] = 0; fScore[start] = heuristic(start, end); while (openSet.Any()) { var currentNode = openSet.MinBy(node => fScore[node]).First(); if (currentNode.Equals(end)) { openSet.Dispose(); closedSet.Dispose(); gScore.Dispose(); fScore.Dispose(); foreach (var edgeID in ReconstructPath(cameFrom, currentNode).Reverse()) { yield return edgeID; } cameFrom.Dispose(); yield break; } openSet.Remove(currentNode); closedSet.Add(currentNode); foreach (var neighbor in Neighbors(currentNode)) { if (!closedSet.Contains(neighbor)) { var lowestEdgeID = EdgeIDs(currentNode, neighbor).MinBy(id => weights[id]).First(); var weight = weights[lowestEdgeID]; var tentativeGScore = gScore.ContainsKey(currentNode) ? gScore[currentNode] + weight : int.MaxValue; if (!openSet.Contains(neighbor) || tentativeGScore < gScore[neighbor]) { cameFrom[neighbor] = lowestEdgeID; gScore[neighbor] = tentativeGScore; fScore[neighbor] = tentativeGScore + heuristic(neighbor, end); openSet.Add(neighbor); } } } } openSet.Dispose(); closedSet.Dispose(); gScore.Dispose(); fScore.Dispose(); cameFrom.Dispose(); yield break; } } }