Simple directed acyclic graphs for Ruby.
This ruby gem started out as a fork of kevinrutherford's dag implementation. If you want to migrate from his implementation to this one, have a look at the breaking changes. Have a look at performance improvements to see why you might want to migrate.
Install the gem
gem install simple_dag
Or add it to your Gemfile and run bundle
.
gem 'simple_dag'
require 'simple_dag'
dag = DAG.new
v1 = dag.add_vertex
v2 = dag.add_vertex
v3 = dag.add_vertex
dag.add_edge from: v1, to: v2
dag.add_edge from: v2, to: v3
v1.path_to?(v3) # => true
v3.path_to?(v1) # => false
dag.add_edge from: v3, to: v1 # => ArgumentError: A DAG must not have cycles
dag.add_edge from: v1, to: v2 # => ArgumentError: Edge already exists
dag.add_edge from: v1, to: v3
v1.successors # => [v2, v3]
See the specs for more detailed usage scenarios.
Tested with Ruby 2.2, 2.3, 2.4, 2.5, JRuby, Rubinius. Builds with Ruby 2.5 and JRuby are currently failing. See this issue for details.
Differences to dag
-
The function
DAG::Vertex#has_path_to?
aliased asDAG::Vertex#has_descendant?
andDAG::Vertex#has_descendent?
has been renamed toDAG::Vertex#path_to?
. The aliases have been removed. -
The function
DAG::Vertex#has_ancestor?
aliased asDAG::Vertex#is_reachable_from?
has been renamed toDAG::Vertex#reachable_from?
. The aliases have been removed. -
The array of edges returned by
DAG#edges
is no longer sorted by insertion order of the edges. -
DAG::Vertex#path_to?
andDAG::Vertex#reachable_from?
no longer raise errors if the vertex passed as an argument is not a vertex in the sameDAG
. Instead, they just returnfalse
. -
Parallel edges are no longer allowed in the dag. Instead,
DAG#add_edge
raises anArgumentError
if you try to add an edge between two adjacent vertices. If you want to model a multigraph, you can add a weight payload to the edges that contains a natural number.
-
DAG#topological_sort
returns a topological sort of the vertices in the dag in a theoretically optimal computational time complexity. -
DAG#enumerated_edges
returns anEnumerator
of the edges in the dag.
-
The computational complexity of
DAG::Vertex#outgoing_edges
has improved to a constant because the edges are no longer stored in one array in theDAG
. Instead, the edges are now stored in their respective sourceVertex
. -
The performance of
DAG::Vertex#successors
has improved because firstly, it depends onDAG::Vertex#outgoing_edges
and secondly the call toArray#uniq
is no longer necessary since parallel edges are prohibited. -
The computational complexities of
DAG::Vertex#descendants
,DAG::Vertex#path_to?
andDAG::Vertex#reachable_from?
have improved because the functions depend onDAG::Vertex#successors
-
I optimized
DAG::Vertex#path_to?
further in commit 5d8f8e5 -
I optimized
DAG::Vertex#descendants
further in commit 58f8233 -
The computational complexity of
DAG::Vertex#incoming_edges
is unchanged: Linear in the number of all edges in theDAG
. -
The performance of
DAG::Vertex#predecessors
has improved because the call toArray#uniq
is no longer necessary since parallel edges are prohibited. -
The performance of
DAG::Vertex#ancestors
has improved because the function depends onDAG::Vertex#predecessors
and I optimizedDAG::Vertex#ancestors
further in commit 58f8233 -
The computational complexity of
DAG::add_edge
has improved because the cycle check in the function depends onDAG::Vertex#path_to?
. -
The performance of
DAG#subgraph
has improved because the function depends onDAG::Vertex#descendants
,DAG::Vertex#ancestors
andDAG::add_edge
. And I optimizedDAG#subgraph
further in commits eda52e4 and 6cd84dd -
The computational complexity of
DAG::edges
has worsened from a constant complexity to a linear complexity. This is irrelevant if you want to iterate over all the edges in the graph. You should consider usingDAG#enumerated_edges
for a better space utilization.