Both DirectedGraph and DirectedEdgedGraph have small changes in .Net 2.0
- Optional EqualityComparer in the constructor.
- Collapse and Union removed for simplicity.
- Basic Graphviz suport in DirctedGraph
DirectedGraph
DirectedGraph<T> is a data structure that defines a
Graph over your objects. Some of the basic characteristics:
It's a
directed graph: Having a link A -> B
Does not mean there's a link B -> A.
It's not weighed: In fact, there's no information attached to the edges.
It's implemented with a
Adjacency List: In fact, the only state it has is a Dictionary<T,HashSet<T>>
It's a wrapper: This means that you can use DirectedGraph over any set of objects with a graph structure, without having to implement INode or something like that. Just like List or Dictionary, but this is not a common practice in graph libraries.
public class DirectedGraph<T>: IEnumerable<T>
{
Dictionary<T, HashSet<T>> adjacency = new Dictionary<T, HashSet<T>>();
public IEqualityComparer<T> Comparer { get; private set; }
public DirectedGraph()
public DirectedGraph(IEqualityComparer<T> comparer)
public IEnumerable<T> Nodes;
public IEnumerable<Edge<T>> Edges
public int Count
public int EdgesCount
public bool Contains(T node)
public bool Connected(T from, T to)
public bool TryConnected(T from, T to)
public void Add(T from)
public void Add(T from, T to)
public void Add(T from, params T[] elements)
public void Add(T from, IEnumerable<T> elements)
public bool Remove(T from, T to)
public bool Remove(Edge<T> edge)
public void RemoveAll(IEnumerable<Edge<T>> edges)
public bool RemoveFullNode(T node)
public bool RemoveFullNode(T node, IEnumerable<T> inverseRelated)
public HashSet<T> RelatedTo(T node)
public HashSet<T> TryRelatedTo(T node)
public IEnumerable<T> InverseRelatedTo(T node)
public HashSet<T> IndirectlyRelatedTo(T node)
public HashSet<T> IndirectlyRelatedTo(T node, Func<T, bool> condition)
public void DepthExplore(T node, Func<T, bool> condition, Action<T> preAction, Action<T> postAction)
public void BreadthExplore(T root, Func<T, bool> condition, Action<T> action)
public DirectedGraph<T> Inverse()
public DirectedGraph<T> UndirectedGraph()
public void UnionWith(DirectedGraph<T> other)
public DirectedGraph<T> Clone()
public void Expand(T node, Func<T, IEnumerable<T>> expandFunction)
public override string ToString()
public IEnumerator<T> GetEnumerator()
public IEnumerable<T> CompilationOrder()
public IEnumerable<HashSet<T>> CompilationOrderGroups()
public DirectedGraph<T> FeedbackEdgeSet()
public DirectedGraph<T> WhereEdges(Func<Edge<T>, bool> condition)
public List<T> ShortestPath(T from, T to)
public string Graphviz()
public string Graphviz(string name, Func<T, string> getName)
public static void RemoveFullNodeSymetric(DirectedGraph<T> original, DirectedGraph<T> inverse, T node)
public static DirectedGraph<T> Generate(T root, Func<T, IEnumerable<T>> expandFunction)
public static DirectedGraph<T> Generate(T root, Func<T, IEnumerable<T>> expandFunction, IEqualityComparer<T> comparer)
public static DirectedGraph<T> Generate(IEnumerable<T> roots, Func<T, IEnumerable<T>> expandFunction)
public static DirectedGraph<T> Generate(IEnumerable<T> roots, Func<T, IEnumerable<T>> expandFunction, IEqualityComparer<T> comparer)
}
public struct Edge<T>
{
public readonly T From;
public readonly T To;
(...)
};
Example
Example of using a DirectedGraph to model an acrylic graph:

DirectedGraph<int> dg = new DirectedGraph<int>
{
{7,11,8},
{5,11},
{3,8,10},
{11,2,9,10},
{8,9}
};
dg.ToString();
dg.ToString(", ");
dg.Nodes.ToString(", ");
dg.Edges.ToString(", ");
dg.Count;
dg.EdgesCount;
dg.Connected(7,11);
dg.Connected(7, 9);
dg.Connected(20, 9);
dg.TryConnected(7, 9);
dg.TryConnected(20, 9);
dg.RelatedTo(7).ToString(", ");
dg.RelatedTo(20).ToString(", ");
dg.TryRelatedTo(7).ToString(", ");
dg.TryRelatedTo(20).ToString(", ");
dg.InverseRelatedTo(11).ToString(", ");
dg.InverseRelatedTo(20).ToString(", ");
dg.IndirectlyRelatedTo(5).ToString(", ");
dg.IndirectlyRelatedTo(20).ToString(", ");
dg.DepthExplore(7, null, n => Console.Write(n+ ", "), null);
dg.DepthExplore(7, n => n == 9, n => Console.Write(n + ", "), null);
dg.DepthExplore(7, null, null, n => Console.Write(n + ", "));
dg.BreadthExplore(7, n => true, n => Console.Write(n + ", "));
dg.Inverse().ToString();
dg.UndirectedGraph().ToString();
dg.CompilationOrder().ToString(", ");
dg.CompilationOrderGroups().ToString(g => "[" + g.ToString(",") + "]", ", ");
dg.Colapse(n => n % 2 == 1).ToString();
dg.WhereEdges(e => e.From % 2 == 1 && e.To % 2 == 1).ToString();
DirectedEdgedGraph
DirectedEdgedGraph<T,E>, on the other hand, is the DirectedGraph's sibling with the ability to store information in the edges.
Internally, the data structure still being an adjacency list, but instead of dictionaries of hashset its implemented with a dictionary of dictionaries: Dictionary<T, Dictionary<T, E>>.
public class DirectedEdgedGraph<T,E>: IEnumerable<T>
{
Dictionary<T, Dictionary<T, E>> adjacency = new Dictionary<T, Dictionary<T, E>>();
public IEqualityComparer<T> Comparer { get; private set; }
public DirectedEdgedGraph()
public DirectedEdgedGraph(IEqualityComparer<T> comparer)
public IEnumerable<T> Nodes;
public IEnumerable<Edge<T>> Edges
public IEnumerable<Edge<T, E>> LabeledEdges
public int Count
public int EdgesCount
public bool Contains(T node)
public bool Connected(T from, T to)
public bool TryConnected(T from, T to)
public void Add(T from)
public void Add(T from, T to, E value)
public void Add(T from, params KeyValuePair<T,E>[] elements)
public void Add(T from, IEnumerable<KeyValuePair<T, E>> elements)
public bool Remove(T from, T to)
public bool Remove(Edge<T> edge)
public void RemoveAll(IEnumerable<Edge<T>> edges)
public bool RemoveFullNode(T node)
public bool RemoveFullNode(T node, IEnumerable<T> inverseRelated)
public Dictionary<T,E> RelatedTo(T node)
public Dictionary<T> TryRelatedTo(T node)
public IEnumerable<T> InverseRelatedTo(T node)
public HashSet<T> IndirectlyRelatedTo(T node)
public HashSet<T> IndirectlyRelatedTo(T node, Func<KeyValuePair<T,E>, bool> condition)
public void DepthExplore(T node, Func<T, bool> condition, Action<T> preAction, Action<T> postAction)
public void BreadthExplore(T root, Func<T, bool> condition, Action<T> action)
public DirectedEdgedGraph<T> Inverse()
public DirectedEdgedGraph<T> UndirectedGraph()
public void UnionWith(DirectedEdgedGraph<T> other)
public DirectedEdgedGraph<T> Clone()
public void Expand(T node, Func<T, IEnumerable<KeyValuePair<T, E>>> expandFunction)
public override string ToString()
public IEnumerator<T> GetEnumerator()
public IEnumerable<T> CompilationOrder()
public IEnumerable<HashSet<T>> CompilationOrderGroups()
public DirectedEdgedGraph<T> FeedbackEdgeSet()
public DirectedEdgedGraph<T> WhereEdges(Func<Edge<T>, bool> condition)
public List<T> ShortestPath(T from, T to, Func<E, int> getWeight)
public string Graphviz()
public string Graphviz(string name)
public static void RemoveFullNodeSymetric(DirectedEdgedGraph<T> original, DirectedEdgedGraph<T> inverse, T node)
public static DirectedEdgedGraph<T> Generate(T root, Func<T,IEnumerable<KeyValuePair<T, E>>> expandFunction)
public static DirectedEdgedGraph<T, E> Generate(T root, Func<T, IEnumerable<KeyValuePair<T, E>>> expandFunction, IEqualityComparer<T> comparer)
public static DirectedEdgedGraph<T, E> Generate(IEnumerable<T> roots, Func<T, IEnumerable<KeyValuePair<T, E>>> expandFunction)
public static DirectedEdgedGraph<T, E> Generate(IEnumerable<T> roots, Func<T, IEnumerable<KeyValuePair<T, E>>> expandFunction, IEqualityComparer<T> comparer)
}
public struct Edge<T,E>
{
public readonly T From;
public readonly T To;
public readonly E Value;
(...)
}
Example
Now, we are going to make an example of using DirectedEdgedGraph to model a graph with values attached to each edge.
Moreover, we are going to model a cyclic graph here, so we will need special care to help some operations to finish, or they will be get stuck in an infinite loop.
This is the graph to model:

DirectedEdgedGraph<char, int> dg = new DirectedEdgedGraph<char, int>
{
{'A','B', 2},
{'B','C', 5},
{'B','D', 2},
{'C','D', 2},
{'C','A', 4},
{'D','A', 1},
{'D','C', 1},
};
dg.ToString();
dg.ToString(", ");
dg.Edges.ToString(", ");
dg.EdgesWithValue.ToString(", ");
dg.RelatedTo('C').ToString(", ");
dg.InverseRelatedTo('C').ToString(", ");
HashSet<char> visited = new HashSet<char>();
dg.DepthExplore('B', c => !visited.Contains(c), c => { visited.Add(c); Console.Write(c + ", "); }, null);
HashSet<char> visited = new HashSet<char>();
dg.DepthExplore('B', c => !visited.Contains(c), c => visited.Add(c), c => Console.Write(c + ", "));
HashSet<char> visited = new HashSet<char>();
dg.BreadthExplore('B', c => !visited.Contains(c), c => { visited.Add(c); Console.Write(c + ", "); });
var back = dg.FeedbackEdgeSet();
var dg2 = dg.Clone();
dg2.RemoveAll(back.Edges);
dg2.CompilationOrder().ToString(", ");
dg2.CompilationOrderGroups().ToString(g => "[" + g.ToString(",") + "]", ", ");
dg.Colapse(c => c == 'A').ToString();
dg.WhereEdges(e => e.Value > 2).ToString();