directed weighted graph
parent
d6f2e0b793
commit
df320194f1
|
@ -0,0 +1,169 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MoreLinq;
|
||||
|
||||
namespace MoonTools.Core.Graph
|
||||
{
|
||||
public class DirectedWeightedGraph<TNode, TEdgeData> : IGraph<TNode, TEdgeData> where TNode : System.IEquatable<TNode>
|
||||
{
|
||||
protected HashSet<TNode> nodes = new HashSet<TNode>();
|
||||
protected Dictionary<TNode, HashSet<TNode>> neighbors = new Dictionary<TNode, HashSet<TNode>>();
|
||||
protected Dictionary<(TNode, TNode), TEdgeData> edgeToEdgeData = new Dictionary<(TNode, TNode), TEdgeData>();
|
||||
protected Dictionary<(TNode, TNode), int> weights = new Dictionary<(TNode, TNode), int>();
|
||||
|
||||
// store search sets to prevent GC
|
||||
protected HashSet<TNode> openSet = new HashSet<TNode>();
|
||||
protected HashSet<TNode> closedSet = new HashSet<TNode>();
|
||||
protected Dictionary<TNode, int> gScore = new Dictionary<TNode, int>();
|
||||
protected Dictionary<TNode, int> fScore = new Dictionary<TNode, int>();
|
||||
protected Dictionary<TNode, TNode> cameFrom = new Dictionary<TNode, TNode>();
|
||||
|
||||
public IEnumerable<TNode> Nodes => nodes;
|
||||
|
||||
public void AddNode(TNode node)
|
||||
{
|
||||
if (Exists(node)) { return; }
|
||||
|
||||
nodes.Add(node);
|
||||
neighbors[node] = new HashSet<TNode>();
|
||||
}
|
||||
|
||||
public void AddNodes(params TNode[] nodes)
|
||||
{
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
AddNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
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 void AddEdge(TNode v, TNode u, int weight, TEdgeData data)
|
||||
{
|
||||
CheckNodes(v, u);
|
||||
|
||||
if (Exists(v, u)) { throw new ArgumentException($"Edge with vertex {v} and {u} already exists in the graph"); }
|
||||
|
||||
neighbors[v].Add(u);
|
||||
weights.Add((v, u), weight);
|
||||
edgeToEdgeData.Add((v, u), data);
|
||||
}
|
||||
|
||||
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();
|
||||
edgeToEdgeData.Clear();
|
||||
weights.Clear();
|
||||
}
|
||||
|
||||
public bool Exists(TNode node) => nodes.Contains(node);
|
||||
|
||||
public bool Exists(TNode v, TNode u)
|
||||
{
|
||||
CheckNodes(v, u);
|
||||
return neighbors[v].Contains(u);
|
||||
}
|
||||
|
||||
public IEnumerable<TNode> Neighbors(TNode node)
|
||||
{
|
||||
CheckNodes(node);
|
||||
return neighbors[node];
|
||||
}
|
||||
|
||||
private void CheckEdge(TNode v, TNode u)
|
||||
{
|
||||
CheckNodes(v, u);
|
||||
if (!Exists(v, u)) { throw new ArgumentException($"Edge between vertex {v} and vertex {u} does not exist in the graph"); }
|
||||
}
|
||||
|
||||
public int Weight(TNode v, TNode u)
|
||||
{
|
||||
CheckEdge(v, u);
|
||||
return weights[(v, u)];
|
||||
}
|
||||
|
||||
public TEdgeData EdgeData(TNode v, TNode u)
|
||||
{
|
||||
CheckEdge(v, u);
|
||||
return edgeToEdgeData[(v, u)];
|
||||
}
|
||||
|
||||
private IEnumerable<(TNode, TNode)> ReconstructPath(Dictionary<TNode, TNode> cameFrom, TNode currentNode)
|
||||
{
|
||||
while (cameFrom.ContainsKey(currentNode))
|
||||
{
|
||||
var edge = (cameFrom[currentNode], currentNode);
|
||||
currentNode = edge.Item1;
|
||||
yield return edge;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<(TNode, TNode)> AStarShortestPath(TNode start, TNode end, Func<TNode, TNode, int> heuristic)
|
||||
{
|
||||
CheckNodes(start, end);
|
||||
|
||||
openSet.Clear();
|
||||
closedSet.Clear();
|
||||
gScore.Clear();
|
||||
fScore.Clear();
|
||||
cameFrom.Clear();
|
||||
|
||||
openSet.Add(start);
|
||||
|
||||
gScore[start] = 0;
|
||||
fScore[start] = heuristic(start, end);
|
||||
|
||||
while (openSet.Count > 0)
|
||||
{
|
||||
var currentNode = openSet.MinBy(node => fScore[node]).First();
|
||||
|
||||
if (currentNode.Equals(end))
|
||||
{
|
||||
return ReconstructPath(cameFrom, currentNode).Reverse();
|
||||
}
|
||||
|
||||
openSet.Remove(currentNode);
|
||||
closedSet.Add(currentNode);
|
||||
|
||||
foreach (var neighbor in Neighbors(currentNode))
|
||||
{
|
||||
if (!closedSet.Contains(neighbor))
|
||||
{
|
||||
var weight = weights[(currentNode, neighbor)];
|
||||
|
||||
var tentativeGScore = gScore.ContainsKey(currentNode) ? gScore[currentNode] + weight : int.MaxValue;
|
||||
|
||||
if (!openSet.Contains(neighbor) || tentativeGScore < gScore[neighbor])
|
||||
{
|
||||
cameFrom[neighbor] = currentNode;
|
||||
gScore[neighbor] = tentativeGScore;
|
||||
fScore[neighbor] = tentativeGScore + heuristic(neighbor, end);
|
||||
openSet.Add(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Enumerable.Empty<(TNode, TNode)>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,10 @@ namespace MoonTools.Core.Graph
|
|||
|
||||
public void AddNode(TNode node)
|
||||
{
|
||||
if (Exists(node)) { return; }
|
||||
|
||||
nodes.Add(node);
|
||||
neighbors[node] = new HashSet<TNode>();
|
||||
}
|
||||
|
||||
public void AddNodes(params TNode[] nodes)
|
||||
|
@ -38,31 +41,18 @@ namespace MoonTools.Core.Graph
|
|||
|
||||
public void AddEdge(TNode v, TNode u, int weight, TEdgeData data)
|
||||
{
|
||||
if (Exists(v) && Exists(u))
|
||||
CheckNodes(v, u);
|
||||
|
||||
var id = Guid.NewGuid();
|
||||
neighbors[v].Add(u);
|
||||
weights.Add(id, weight);
|
||||
if (!edges.ContainsKey((v, u)))
|
||||
{
|
||||
var id = Guid.NewGuid();
|
||||
if (!neighbors.ContainsKey(v))
|
||||
{
|
||||
neighbors[v] = new HashSet<TNode>();
|
||||
}
|
||||
neighbors[v].Add(u);
|
||||
weights.Add(id, weight);
|
||||
if (!edges.ContainsKey((v, u)))
|
||||
{
|
||||
edges[(v, u)] = new HashSet<Guid>();
|
||||
}
|
||||
edges[(v, u)].Add(id);
|
||||
edgeToEdgeData.Add(id, data);
|
||||
IDToEdge.Add(id, (v, u));
|
||||
}
|
||||
else if (!Exists(v))
|
||||
{
|
||||
throw new InvalidVertexException("Vertex {0} does not exist in the graph", v);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidVertexException("Vertex {0} does not exist in the graph", u);
|
||||
edges[(v, u)] = new HashSet<Guid>();
|
||||
}
|
||||
edges[(v, u)].Add(id);
|
||||
edgeToEdgeData.Add(id, data);
|
||||
IDToEdge.Add(id, (v, u));
|
||||
}
|
||||
|
||||
public void AddEdges(params (TNode, TNode, int, TEdgeData)[] edges)
|
||||
|
@ -78,6 +68,8 @@ namespace MoonTools.Core.Graph
|
|||
nodes.Clear();
|
||||
neighbors.Clear();
|
||||
weights.Clear();
|
||||
edges.Clear();
|
||||
IDToEdge.Clear();
|
||||
edgeToEdgeData.Clear();
|
||||
}
|
||||
|
||||
|
@ -98,10 +90,7 @@ namespace MoonTools.Core.Graph
|
|||
return edges.ContainsKey((v, u)) ? edges[(v, u)] : Enumerable.Empty<Guid>();
|
||||
}
|
||||
|
||||
public bool Exists(TNode node)
|
||||
{
|
||||
return nodes.Contains(node);
|
||||
}
|
||||
public bool Exists(TNode node) => nodes.Contains(node);
|
||||
|
||||
public bool Exists(TNode v, TNode u)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
using NUnit.Framework;
|
||||
using FluentAssertions;
|
||||
|
||||
using MoonTools.Core.Graph;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Tests
|
||||
{
|
||||
public class DirectedWeightedGraphTests
|
||||
{
|
||||
EdgeData dummyEdgeData;
|
||||
|
||||
[Test]
|
||||
public void AddNode()
|
||||
{
|
||||
var myGraph = new DirectedWeightedGraph<int, EdgeData>();
|
||||
myGraph.AddNode(4);
|
||||
|
||||
myGraph.Exists(4).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddNodes()
|
||||
{
|
||||
var myGraph = new DirectedWeightedGraph<int, EdgeData>();
|
||||
myGraph.AddNodes(4, 20, 69);
|
||||
|
||||
myGraph.Exists(4).Should().BeTrue();
|
||||
myGraph.Exists(20).Should().BeTrue();
|
||||
myGraph.Exists(69).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddEdge()
|
||||
{
|
||||
var myGraph = new DirectedWeightedGraph<int, EdgeData>();
|
||||
myGraph.AddNodes(5, 6);
|
||||
myGraph.AddEdge(5, 6, 10, dummyEdgeData);
|
||||
|
||||
myGraph.Neighbors(5).Should().Contain(6);
|
||||
myGraph.Weight(5, 6).Should().Be(10);
|
||||
myGraph.EdgeData(5, 6).Should().Be(dummyEdgeData);
|
||||
|
||||
myGraph.Invoking(x => x.AddEdge(5, 6, 3, dummyEdgeData)).Should().Throw<ArgumentException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddEdges()
|
||||
{
|
||||
var a = new NumEdgeData { testNum = 1 };
|
||||
var b = new NumEdgeData { testNum = 2 };
|
||||
var c = new NumEdgeData { testNum = 3 };
|
||||
var d = new NumEdgeData { testNum = 4 };
|
||||
|
||||
var myGraph = new DirectedWeightedGraph<int, NumEdgeData>();
|
||||
myGraph.AddNodes(1, 2, 3, 4);
|
||||
myGraph.AddEdges(
|
||||
(1, 2, 5, a),
|
||||
(2, 3, 6, b),
|
||||
(2, 4, 7, c),
|
||||
(3, 4, 8, d)
|
||||
);
|
||||
|
||||
myGraph.Neighbors(1).Should().Contain(2);
|
||||
myGraph.Neighbors(2).Should().Contain(3);
|
||||
myGraph.Neighbors(2).Should().Contain(4);
|
||||
myGraph.Neighbors(3).Should().Contain(4);
|
||||
myGraph.Neighbors(1).Should().NotContain(4);
|
||||
|
||||
myGraph.Weight(1, 2).Should().Be(5);
|
||||
myGraph.Weight(2, 3).Should().Be(6);
|
||||
myGraph.Weight(2, 4).Should().Be(7);
|
||||
myGraph.Weight(3, 4).Should().Be(8);
|
||||
|
||||
myGraph.EdgeData(1, 2).Should().Be(a);
|
||||
myGraph.EdgeData(2, 3).Should().Be(b);
|
||||
myGraph.EdgeData(2, 4).Should().Be(c);
|
||||
myGraph.EdgeData(3, 4).Should().Be(d);
|
||||
|
||||
myGraph.Invoking(x => x.AddEdge(2, 4, 9, d)).Should().Throw<ArgumentException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Clear()
|
||||
{
|
||||
var myGraph = new DirectedWeightedGraph<int, EdgeData>();
|
||||
myGraph.AddNodes(1, 2, 3, 4);
|
||||
myGraph.AddEdges(
|
||||
(1, 2, 5, dummyEdgeData),
|
||||
(2, 3, 6, dummyEdgeData),
|
||||
(2, 4, 7, dummyEdgeData)
|
||||
);
|
||||
|
||||
myGraph.Clear();
|
||||
|
||||
myGraph.Nodes.Should().BeEmpty();
|
||||
myGraph.Invoking(x => x.Neighbors(1)).Should().Throw<ArgumentException>();
|
||||
myGraph.Invoking(x => x.Weight(1, 2)).Should().Throw<ArgumentException>();
|
||||
myGraph.Invoking(x => x.EdgeData(1, 2)).Should().Throw<ArgumentException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NodeExists()
|
||||
{
|
||||
var myGraph = new DirectedWeightedGraph<int, EdgeData>();
|
||||
myGraph.AddNodes(1, 2, 3);
|
||||
myGraph.AddEdges(
|
||||
(1, 2, 4, dummyEdgeData),
|
||||
(2, 3, 5, dummyEdgeData)
|
||||
);
|
||||
|
||||
myGraph.Exists(1).Should().BeTrue();
|
||||
myGraph.Exists(2).Should().BeTrue();
|
||||
myGraph.Exists(3).Should().BeTrue();
|
||||
myGraph.Exists(4).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EdgeExists()
|
||||
{
|
||||
var myGraph = new DirectedWeightedGraph<int, EdgeData>();
|
||||
myGraph.AddNodes(1, 2, 3);
|
||||
myGraph.AddEdges(
|
||||
(1, 2, 4, dummyEdgeData),
|
||||
(2, 3, 5, dummyEdgeData)
|
||||
);
|
||||
|
||||
myGraph.Exists(1, 2).Should().BeTrue();
|
||||
myGraph.Exists(2, 3).Should().BeTrue();
|
||||
myGraph.Exists(1, 3).Should().BeFalse();
|
||||
myGraph.Invoking(x => x.Exists(3, 4)).Should().Throw<ArgumentException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Neighbors()
|
||||
{
|
||||
var myGraph = new DirectedWeightedGraph<int, EdgeData>();
|
||||
myGraph.AddNodes(1, 2, 3);
|
||||
myGraph.AddEdges(
|
||||
(1, 2, 4, dummyEdgeData),
|
||||
(2, 3, 5, dummyEdgeData)
|
||||
);
|
||||
|
||||
myGraph.Neighbors(1).Should().Contain(2);
|
||||
myGraph.Neighbors(2).Should().Contain(3);
|
||||
myGraph.Neighbors(1).Should().NotContain(3);
|
||||
myGraph.Invoking(x => x.Neighbors(4)).Should().Throw<System.ArgumentException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Weight()
|
||||
{
|
||||
var myGraph = new DirectedWeightedGraph<int, EdgeData>();
|
||||
myGraph.AddNodes(1, 2, 3);
|
||||
myGraph.AddEdges(
|
||||
(1, 2, 4, dummyEdgeData),
|
||||
(2, 3, 5, dummyEdgeData)
|
||||
);
|
||||
|
||||
myGraph.Weight(1, 2).Should().Be(4);
|
||||
myGraph.Weight(2, 3).Should().Be(5);
|
||||
myGraph.Invoking(x => x.Weight(3, 4)).Should().Throw<System.ArgumentException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void EdgeData()
|
||||
{
|
||||
var a = new NumEdgeData { testNum = 3 };
|
||||
var b = new NumEdgeData { testNum = 5 };
|
||||
|
||||
var myGraph = new DirectedWeightedGraph<int, NumEdgeData>();
|
||||
myGraph.AddNodes(1, 2, 3);
|
||||
myGraph.AddEdges(
|
||||
(1, 2, 4, a),
|
||||
(2, 3, 5, b)
|
||||
);
|
||||
|
||||
myGraph.EdgeData(1, 2).Should().Be(a);
|
||||
myGraph.EdgeData(2, 3).Should().Be(b);
|
||||
myGraph.Invoking(x => x.EdgeData(2, 4)).Should().Throw<ArgumentException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AStarShortestPath()
|
||||
{
|
||||
var run = new MoveTypeEdgeData { moveType = MoveType.Run };
|
||||
var jump = new MoveTypeEdgeData { moveType = MoveType.Jump };
|
||||
var wallJump = new MoveTypeEdgeData { moveType = MoveType.WallJump };
|
||||
|
||||
var myGraph = new DirectedWeightedGraph<char, MoveTypeEdgeData>();
|
||||
myGraph.AddNodes('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
|
||||
myGraph.AddEdges(
|
||||
('a', 'b', 2, run),
|
||||
('a', 'c', 3, run),
|
||||
('a', 'e', 4, wallJump),
|
||||
('b', 'd', 2, jump),
|
||||
('b', 'e', 1, run),
|
||||
('c', 'g', 4, jump),
|
||||
('c', 'h', 11, run),
|
||||
('d', 'c', 3, jump),
|
||||
('d', 'f', 2, run),
|
||||
('d', 'h', 3, wallJump),
|
||||
('e', 'f', 5, run),
|
||||
('f', 'd', 2, run),
|
||||
('f', 'h', 6, wallJump),
|
||||
('g', 'h', 7, run),
|
||||
('h', 'f', 1, jump)
|
||||
);
|
||||
|
||||
myGraph
|
||||
.AStarShortestPath('a', 'h', (x, y) => 1)
|
||||
.Select(edge => myGraph.EdgeData(edge.Item1, edge.Item2))
|
||||
.Should()
|
||||
.ContainInOrder(
|
||||
run, jump, wallJump
|
||||
)
|
||||
.And
|
||||
.HaveCount(3);
|
||||
|
||||
myGraph.Invoking(x => x.AStarShortestPath('a', 'z', (x, y) => 15)).Should().Throw<System.ArgumentException>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -239,7 +239,7 @@ namespace Tests
|
|||
);
|
||||
|
||||
myGraph
|
||||
.AStarShortestPath('a', 'h', (x, y) => 15)
|
||||
.AStarShortestPath('a', 'h', (x, y) => 1)
|
||||
.Select(id => myGraph.EdgeData(id))
|
||||
.Should()
|
||||
.ContainInOrder(
|
||||
|
|
Loading…
Reference in New Issue