libgraph v0.4.0 Graph

This module defines a directed graph data structure, which supports both acyclic and cyclic forms. It also defines the API for creating, manipulating, and querying that structure.

This is intended as a replacement for :digraph, which requires the use of 3 ETS tables at a minimum, but up to 6 at a time during certain operations (such as get_short_path/3). In environments where many graphs are in memory at a time, this can be dangerous, as it is easy to hit the system limit for max ETS tables, which will bring your node down. This graph implementation does not use ETS, so it can be used freely without concern for hitting this limit.

The following properties should be kept in mind when planning space requirements:

  • The graph structure stores vertices twice, as a map of vertex -> id (integer) and it’s inverse index; I have not yet been able to determine if shared references are used, or if the runtime forces copies, but you should be aware that the graph may require up to 2((sizeof(V)*N)+(sizeof(integer)*N)), where N is the number of vertices
  • The graph also contains a map of all out edges and it’s inverse, so each edge is 2((3*sizeof(integer))+2) bytes, which represents the 6 integers used, and the bytes needed for the tuples used
  • Additionally, each edge with metadata (weight/label) will incur the cost for a new list, a tuple (per option), and the size of the term stored

You can obtain a “true” size in bytes, by calling Graph.info/1, which gets the size in bytes of the graph when encoded using Erlang External Term Format.

The reason for the different internal structures, particularly the inverse indexes, is performance. In order to efficiently perform queries on the graph, we need quick key-based lookup for both in-edges and out-edges for a vertex. Additionally, we need to work with the smallest possible keys when storing edges, which means we need a map of vertices to their ids, and the inverse of that lookup so that we can reify a collection of ids to their associated vertices/edges. Internally, we work strictly with ids and only convert back to the actual vertex (or create an Edge struct) when we have the result set. This balances performance, space requirements, and ease of maintenance.

There are benchmarks provided with this library which compare it directly to :digraph for some common operations, and thus far, libgraph outperforms :digraph in all of them.

The only bit of data I have not yet evaluated is how much garbage is generated when querying/manipulating the graph between libgraph and digraph, but I suspect the use of ETS means that digraph is able to keep that to a minimum. Until I verify if that’s the case, I would assume that libgraph has higher memory requirements, but better performance, and is able to side-step the ETS limit. If your requirements, like mine, mean that you are dynamically constructing and querying graphs concurrently, I think libgraph is the better choice - however if you either need the APIs of :digraph that I have not yet implemented, or do not have the same use case, I would stick to :digraph for now.

Link to this section Summary

Functions

Gets the shortest path between a and b

Adds an edge connecting a to b. If either a or b do not exist in the graph, they are automatically added. Adding the same edge more than once does not create multiple edges, each edge is only ever stored once

Like add_edge/3, but takes a list of Graph.Edge structs, and adds an edge to the graph for each pair

Adds a new vertex to the graph. If the vertex is already present in the graph, the add is a no-op

Like add_vertex/2, but takes a list of vertices to add to the graph

Returns the root vertex of the arborescence, if one exists, otherwise nil

Returns a list of connected components, where each component is a list of vertices

Removes an edge connecting a to b. If no such vertex exits, or the edge does not exist, it is effectively a no-op

Like delete_edge/3, but takes a list of vertex pairs, and deletes the corresponding edge from the graph, if it exists

Removes a vertex from the graph, as well as any edges which refer to that vertex. If the vertex does not exist in the graph, it is a no-op

Like delete_vertex/2, but takes a list of vertices to delete from the graph

Gets the shortest path between a and b

Return a list of all the edges, where each edge is expressed as a tuple of {A, B}, where the elements are the vertices involved, and implying the direction of the edge to be from A to B

Builds a list of paths between vertex a and vertex b

Returns the in-degree of vertex v of graph g

Returns a list of Graph.Edge structs representing the in edges to vertex v

Returns a list of vertices which all have edges coming in to the given vertex v

Returns a map of summary information about this graph

Returns true if and only if the graph g is acyclic

Returns true if the graph is an aborescence, a directed acyclic graph, where the root, a vertex, of the arborescence has a unique path from itself to every other vertex in the graph

Returns true if the graph g is not acyclic

Returns true if graph g1 is a subgraph of g2

Returns true if and only if the graph g is a tree

Returns a list of vertices from graph g which are included in a loop, where a loop is a cycle of length 1

Maps a function over all the vertices in a graph using a depth-first traversal

Creates a new graph

Returns the number of edges in the graph

Returns the number of vertices in the graph

Returns the out-degree of vertex v of graph g

Returns a list of Graph.Edge structs representing the out edges from vertex v

Returns a list of vertices which the given vertex v has edges going to

Returns all vertices of graph g. The order is given by a depth-first traversal of the graph, collecting visited vertices in postorder. More precisely, the vertices visited while searching from an arbitrarily chosen vertex are collected in postorder, and all those collected vertices are placed before the subsequently visited vertices

Returns all vertices of graph g. The order is given by a depth-first traversal of the graph, collecting visited vertices in preorder

Returns an unsorted list of vertices from the graph, such that for each vertex in the list (call it v), there is a path in the graph from some vertex of vs to v

Returns an unsorted list of vertices from the graph, such that for each vertex in the list (call it v), there is a path in the graph of length one or more from some vertex of vs to v

Returns an unsorted list of vertices from the graph, such that for each vertex in the list (call it v), there is a path from v to some vertex of vs

Returns an unsorted list of vertices from the graph, such that for each vertex in the list (call it v), there is a path of length one or more from v to some vertex of vs

Applies a reducer over all the vertices in a graph using a depth-first traversal. The reducer function receives the current vertex, and the accumulator, and must return a new accumulator

Replaces vertex with new_vertex in the graph

Splits the edge between v1 and v2 by inserting a new vertex, v3, deleting the edge between v1 and v2, and inserting an edge from v1 to v3 and from v3 to v2

Returns a list of strongly connected components, where each component is a list of vertices

Builds a maximal subgraph of g which includes all of the vertices in vs and the edges which connect them

Returns a topological ordering of the vertices of graph g, if such an ordering exists, otherwise it returns false. For each vertex in the returned list, no out-neighbors occur earlier in the list

The transposition of a graph is another graph with the direction of all the edges reversed

Updates the metadata (weight/label) for an edge using the provided options

Returns a list of all the vertices in the graph

Walks the graph by starting with a depth-first traversal, the walk function receives the current vertex, it’s out-neighbors and in-neighbors (as lists of Edge structs), and the accumulator. You can return one of the following to control the walk

See Graph.Reducer.walk/4

Link to this section Types

Link to this type mapper_fun()
mapper_fun() :: (vertex -> term)
Link to this type reducer_fun()
reducer_fun() :: (vertex, term -> term)
Link to this type vertex()
vertex() :: term
Link to this type walk_opt()
walk_opt() :: {:algorithm, :breadth_first}
Link to this type walker_fun()
walker_fun ::
  (vertex, [vertex], [vertex], term -> {:next, term}) |
  {:next, vertex, term} |
  {:skip, term} |
  {:halt, term}

Link to this section Functions

Link to this function a_star(g, a, b, hfun)
a_star(t, vertex, vertex, (vertex, vertex -> integer)) :: [vertex]

Gets the shortest path between a and b.

The A algorithm is very much like Dijkstra’s algorithm, except in addition to edge weights, A also considers a heuristic function for determining the lower bound of the cost to go from vertex v to b. The lower bound must be less than the cost of the shortest path from v to b, otherwise it will do more harm than good. Dijkstra’s algorithm can be reframed as A* where lower_bound(v) is always 0.

This function puts the heuristics in your hands, so you must provide the heuristic function, which should take a single parameter, v, which is the vertex being currently examined. Your heuristic should then determine what the lower bound for the cost to reach b from v is, and return that value.

Example

iex> g = Graph.new |> Graph.add_edges([{:a, :b}, {:b, :c}, {:c, :d}, {:b, :d}])
...> Graph.a_star(g, :a, :d, fn _ -> 0 end)
[:a, :b, :d]

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
...> g = Graph.add_edges(g, [{:a, :c}, {:b, :c}, {:b, :d}])
...> Graph.a_star(g, :a, :d, fn _ -> 0 end)
nil
Link to this function add_edge(g, edge)
add_edge(t, Graph.Edge.t) :: t

Like add_edge/3 or add_edge/4, but takes a Graph.Edge struct created with Graph.Edge.new/2 or Graph.Edge.new/3.

Example

iex> g = Graph.new |> Graph.add_edge(Graph.Edge.new(:a, :b))
...> [:a, :b] = Graph.vertices(g)
...> Graph.edges(g)
[%Graph.Edge{v1: :a, v2: :b}]
Link to this function add_edge(g, a, b, opts \\ [])
add_edge(t, vertex, vertex, Graph.Edge.edge_opts) ::
  t |
  {:error, {:invalid_edge_option, term}}

Adds an edge connecting a to b. If either a or b do not exist in the graph, they are automatically added. Adding the same edge more than once does not create multiple edges, each edge is only ever stored once.

Edges have a default weight of 1, and an empty (nil) label. You can change this by passing options to this function, as shown below.

Example

iex> g = Graph.new |> Graph.add_edge(:a, :b)
...> [:a, :b] = Graph.vertices(g)
...> Graph.edges(g)
[%Graph.Edge{v1: :a, v2: :b, label: nil, weight: 1}]

iex> g = Graph.new |> Graph.add_edge(:a, :b, label: :foo, weight: 2)
...> [:a, :b] = Graph.vertices(g)
...> Graph.edges(g)
[%Graph.Edge{v1: :a, v2: :b, label: :foo, weight: 2}]
Link to this function add_edges(g, es)
add_edges(t, [Graph.Edge.t]) ::
  t |
  {:error, {:invalid_edge, term}}

Like add_edge/3, but takes a list of Graph.Edge structs, and adds an edge to the graph for each pair.

See the docs for Graph.Edge.new/2 or Graph.Edge.new/3 for more info.

Examples

iex> alias Graph.Edge
...> edges = [Edge.new(:a, :b), Edge.new(:b, :c, weight: 2)]
...> g = Graph.new |> Graph.add_vertices([:a, :b, :c]) |> Graph.add_edges(edges)
...> Graph.edges(g)
[%Graph.Edge{v1: :a, v2: :b}, %Graph.Edge{v1: :b, v2: :c, weight: 2}]

iex> Graph.new |> Graph.add_vertices([:a, :b, :c]) |> Graph.add_edges([:a, :b])
{:error, {:invalid_edge, :a}}
Link to this function add_vertex(g, vertex)
add_vertex(t, vertex) :: t

Adds a new vertex to the graph. If the vertex is already present in the graph, the add is a no-op.

Example

iex> g = Graph.new |> Graph.add_vertex(:a) |> Graph.add_vertex(:a)
...> Graph.vertices(g)
[:a]
Link to this function add_vertices(g, vs)
add_vertices(t, [vertex]) :: t

Like add_vertex/2, but takes a list of vertices to add to the graph.

Example

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :a])
...> Graph.vertices(g)
[:a, :b]
Link to this function arborescence_root(g)
arborescence_root(t) :: vertex | nil

Returns the root vertex of the arborescence, if one exists, otherwise nil.

Link to this function components(g)
components(t) :: [[vertex]]

Returns a list of connected components, where each component is a list of vertices.

A connected component is a maximal subgraph such that there is a path between each pair of vertices, considering all edges undirected.

A subgraph is a graph whose vertices and edges are a subset of the vertices and edges of the source graph.

A maximal subgraph is a subgraph with property P where all other subgraphs which contain the same vertices do not have that same property P.

Example

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
...> g = Graph.add_edges(g, [{:a, :b}, {:a, :c}, {:b, :c}, {:c, :d}, {:c, :a}])
...> Graph.components(g)
[[:d, :b, :c, :a]]
Link to this function delete_edge(g, a, b)

Removes an edge connecting a to b. If no such vertex exits, or the edge does not exist, it is effectively a no-op.

Example

iex> g = Graph.new |> Graph.add_edge(:a, :b) |> Graph.delete_edge(:a, :b)
...> [:a, :b] = Graph.vertices(g)
...> Graph.edges(g)
[]
Link to this function delete_edges(g, es)
delete_edges(t, [{vertex, vertex}]) ::
  t |
  {:error, {:invalid_edge, term}}

Like delete_edge/3, but takes a list of vertex pairs, and deletes the corresponding edge from the graph, if it exists.

Examples

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c]) |> Graph.add_edge(:a, :b)
...> g = Graph.delete_edges(g, [{:a, :b}])
...> Graph.edges(g)
[]

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c]) |> Graph.add_edge(:a, :b)
...> Graph.delete_edges(g, [:a])
{:error, {:invalid_edge, :a}}
Link to this function delete_vertex(g, v)
delete_vertex(t, vertex) :: t

Removes a vertex from the graph, as well as any edges which refer to that vertex. If the vertex does not exist in the graph, it is a no-op.

Example

iex> g = Graph.new |> Graph.add_vertex(:a) |> Graph.add_vertex(:b) |> Graph.add_edge(:a, :b)
...> [:a, :b] = Graph.vertices(g)
...> [%Graph.Edge{v1: :a, v2: :b}] = Graph.edges(g)
...> g = Graph.delete_vertex(g, :b)
...> [:a] = Graph.vertices(g)
...> Graph.edges(g)
[]
Link to this function delete_vertices(g, vs)
delete_vertices(t, [vertex]) :: t

Like delete_vertex/2, but takes a list of vertices to delete from the graph.

Example

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c]) |> Graph.delete_vertices([:a, :b])
...> Graph.vertices(g)
[:c]
Link to this function dijkstra(g, a, b)
dijkstra(t, vertex, vertex) :: [vertex]

Gets the shortest path between a and b.

As indicated by the name, this uses Dijkstra’s algorithm for locating the shortest path, which means that edge weights are taken into account when determining which vertices to search next. By default, all edges have a weight of 1, so vertices are inspected at random; which causes this algorithm to perform a naive depth-first search of the graph until a path is found. If your edges are weighted however, this will allow the algorithm to more intelligently navigate the graph.

Example

iex> g = Graph.new |> Graph.add_edges([{:a, :b}, {:b, :c}, {:c, :d}, {:b, :d}])
...> Graph.dijkstra(g, :a, :d)
[:a, :b, :d]

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
...> g = Graph.add_edges(g, [{:a, :c}, {:b, :c}, {:b, :d}])
...> Graph.dijkstra(g, :a, :d)
nil
Link to this function edges(graph)
edges(t) :: [Graph.Edge.t]

Return a list of all the edges, where each edge is expressed as a tuple of {A, B}, where the elements are the vertices involved, and implying the direction of the edge to be from A to B.

NOTE: You should be careful when using this on dense graphs, as it produces lists with whatever you’ve provided as vertices, with likely many copies of each. I’m not sure if those copies are shared in-memory as they are unchanged, so it should be fairly compact in memory, but I have not verified that to be sure.

Example

iex> g = Graph.new |> Graph.add_vertex(:a) |> Graph.add_vertex(:b) |> Graph.add_vertex(:c)
...> g = g |> Graph.add_edge(:a, :c) |> Graph.add_edge(:b, :c)
...> Graph.edges(g)
[%Graph.Edge{v1: :a, v2: :c}, %Graph.Edge{v1: :b, v2: :c}]
Link to this function get_paths(g, a, b)
get_paths(t, vertex, vertex) :: [[vertex]]

Builds a list of paths between vertex a and vertex b.

The algorithm used here is a depth-first search, which evaluates the whole graph until all paths are found. Order is guaranteed to be deterministic, but not guaranteed to be in any meaningful order (i.e. shortest to longest).

Example

iex> g = Graph.new |> Graph.add_edges([{:a, :b}, {:b, :c}, {:c, :d}, {:b, :d}, {:c, :a}])
...> Graph.get_paths(g, :a, :d)
[[:a, :b, :c, :d], [:a, :b, :d]]

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
...> g = Graph.add_edges(g, [{:a, :c}, {:b, :c}, {:b, :d}])
...> Graph.get_paths(g, :a, :d)
[]
Link to this function get_shortest_path(g, a, b)
get_shortest_path(t, vertex, vertex) :: [vertex]

See dijkstra/1.

Link to this function in_degree(g, v)

Returns the in-degree of vertex v of graph g.

The in-degree of a vertex is the number of edges directed inbound towards that vertex.

Example

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c]) |> Graph.add_edge(:a, :b)
...> Graph.in_degree(g, :b)
1
Link to this function in_edges(g, v)
in_edges(t, vertex) :: Graph.Edge.t

Returns a list of Graph.Edge structs representing the in edges to vertex v.

Link to this function in_neighbors(g, v)
in_neighbors(t, vertex) :: [vertex]

Returns a list of vertices which all have edges coming in to the given vertex v.

Link to this function info(g)
info(t) :: %{num_edges: non_neg_integer, num_vertices: non_neg_integer}

Returns a map of summary information about this graph.

NOTE: The size_in_bytes value is calculated via :erlang.external_size/1, which determines the size in bytes when the term is serialized to External Term Format. Since the size in bytes is for the serialized representation, it is always going to be a higher value than the actual size in memory, since Erlang is able to share references to values rather than copy them. However, the value is still a handy “worst-case” estimate, so I still consider it somewhat useful information to have handy.

Example

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
...> g = g |> Graph.add_edges([{:a, :b}, {:b, :c}])
...> Graph.info(g)
%{num_vertices: 4, num_edges: 2, size_in_bytes: 420}
Link to this function is_acyclic?(g)
is_acyclic?(t) :: boolean

Returns true if and only if the graph g is acyclic.

Link to this function is_arborescence?(g)
is_arborescence?(t) :: boolean

Returns true if the graph is an aborescence, a directed acyclic graph, where the root, a vertex, of the arborescence has a unique path from itself to every other vertex in the graph.

Link to this function is_cyclic?(g)
is_cyclic?(t) :: boolean

Returns true if the graph g is not acyclic.

Link to this function is_subgraph?(g1, g2)
is_subgraph?(t, t) :: boolean

Returns true if graph g1 is a subgraph of g2.

A graph is a subgraph of another graph if it’s vertices and edges are a subset of that graph’s vertices and edges.

Example

iex> g1 = Graph.new |> Graph.add_vertices([:a, :b, :c, :d]) |> Graph.add_edge(:a, :b) |> Graph.add_edge(:b, :c)
...> g2 = Graph.new |> Graph.add_vertices([:b, :c]) |> Graph.add_edge(:b, :c)
...> Graph.is_subgraph?(g2, g1)
true
Link to this function is_tree?(g)
is_tree?(t) :: boolean

Returns true if and only if the graph g is a tree.

Link to this function loop_vertices(g)
loop_vertices(t) :: [vertex]

Returns a list of vertices from graph g which are included in a loop, where a loop is a cycle of length 1.

Example

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c]) |> Graph.add_edge(:a, :a)
...> Graph.loop_vertices(g)
[:a]
Link to this function map(g, fun, opts \\ [])
map(t, mapper_fun, opts :: [walk_opt]) :: [term]

Maps a function over all the vertices in a graph using a depth-first traversal

You can perform a breadth-first traversal instead by passing the option algorithm: :breadth_first.

Example

iex> g = Graph.new |> Graph.add_vertices([1, 2, 4]) |> Graph.add_edge(1, 2) |> Graph.add_edge(2, 4)
...> Graph.map(g, fn v -> v * 2 end)
[2, 4, 8]
Link to this function new()
new() :: t

Creates a new graph.

Link to this function num_edges(graph)
num_edges(t) :: non_neg_integer

Returns the number of edges in the graph

Example

iex> g = Graph.add_edges(Graph.new, [{:a, :b}, {:b, :c}, {:a, :a}])
...> Graph.num_edges(g)
3
Link to this function num_vertices(graph)
num_vertices(t) :: non_neg_integer

Returns the number of vertices in the graph

Example

iex> g = Graph.add_vertices(Graph.new, [:a, :b, :c])
...> Graph.num_vertices(g)
3
Link to this function out_degree(g, v)
out_degree(t, vertex) :: non_neg_integer

Returns the out-degree of vertex v of graph g.

The out-degree of a vertex is the number of edges directed outbound from that vertex.

Example

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c]) |> Graph.add_edge(:a, :b)
...> Graph.out_degree(g, :a)
1
Link to this function out_edges(g, v)
out_edges(t, vertex) :: Graph.Edge.t

Returns a list of Graph.Edge structs representing the out edges from vertex v.

Link to this function out_neighbors(g, v)
out_neighbors(t, vertex) :: [vertex]

Returns a list of vertices which the given vertex v has edges going to.

Link to this function postorder(g)
postorder(t) :: [vertex]

Returns all vertices of graph g. The order is given by a depth-first traversal of the graph, collecting visited vertices in postorder. More precisely, the vertices visited while searching from an arbitrarily chosen vertex are collected in postorder, and all those collected vertices are placed before the subsequently visited vertices.

Example

Our example code constructs a graph which looks like so:

    :a
                 :b
      /           :c   :d
    /
   :e

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d, :e])
...> g = Graph.add_edges(g, [{:a, :b}, {:b, :c}, {:b, :d}, {:c, :e}])
...> Graph.postorder(g)
[:e, :c, :d, :b, :a]
Link to this function preorder(g)
preorder(t) :: [vertex]

Returns all vertices of graph g. The order is given by a depth-first traversal of the graph, collecting visited vertices in preorder.

Example

Our example code constructs a graph which looks like so:

     :a
                   :b
       /           :c   :d
     /
   :e

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d, :e])
...> g = Graph.add_edges(g, [{:a, :b}, {:b, :c}, {:b, :d}, {:c, :e}])
...> Graph.preorder(g)
[:a, :b, :c, :e, :d]
Link to this function reachable(g, vs)
reachable(t, [vertex]) :: [[vertex]]

Returns an unsorted list of vertices from the graph, such that for each vertex in the list (call it v), there is a path in the graph from some vertex of vs to v.

As paths of length zero are allowed, the vertices of vs are also included in the returned list.

Example

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
...> g = Graph.add_edges(g, [{:a, :b}, {:a, :c}, {:b, :c}, {:c, :d}])
...> Graph.reachable(g, [:a])
[:d, :c, :b, :a]
Link to this function reachable_neighbors(g, vs)
reachable_neighbors(t, [vertex]) :: [[vertex]]

Returns an unsorted list of vertices from the graph, such that for each vertex in the list (call it v), there is a path in the graph of length one or more from some vertex of vs to v.

As a consequence, only those vertices of vs that are included in some cycle are returned.

Example

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
...> g = Graph.add_edges(g, [{:a, :b}, {:a, :c}, {:b, :c}, {:c, :d}])
...> Graph.reachable_neighbors(g, [:a])
[:d, :c, :b]
Link to this function reaching(g, vs)
reaching(t, [vertex]) :: [[vertex]]

Returns an unsorted list of vertices from the graph, such that for each vertex in the list (call it v), there is a path from v to some vertex of vs.

As paths of length zero are allowed, the vertices of vs are also included in the returned list.

Example

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
...> g = Graph.add_edges(g, [{:a, :b}, {:a, :c}, {:b, :c}, {:c, :d}])
...> Graph.reaching(g, [:d])
[:b, :a, :c, :d]
Link to this function reaching_neighbors(g, vs)
reaching_neighbors(t, [vertex]) :: [[vertex]]

Returns an unsorted list of vertices from the graph, such that for each vertex in the list (call it v), there is a path of length one or more from v to some vertex of vs.

As a consequence, only those vertices of vs that are included in some cycle are returned.

Example

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d]) …> g = Graph.add_edges(g, [{:a, :b}, {:a, :c}, {:b, :c}, {:c, :a}, {:b, :d}]) …> Graph.reaching_neighbors(g, [:b]) [:b, :c, :a]

Link to this function reduce(g, acc, fun, opts \\ [])
reduce(t, term, reducer_fun, opts :: [walk_opt]) :: term

Applies a reducer over all the vertices in a graph using a depth-first traversal. The reducer function receives the current vertex, and the accumulator, and must return a new accumulator.

You can perform a breadth-first traversal instead by passing the option algorithm: :breadth_first.

Example

iex> g = Graph.new |> Graph.add_vertices([1, 2, 4]) |> Graph.add_edge(1, 2) |> Graph.add_edge(2, 4)
...> Graph.reduce(g, 0, fn v, acc -> acc + v end)
7
Link to this function replace_vertex(g, v, rv)
replace_vertex(t, vertex, vertex) ::
  t |
  {:error, :no_such_vertex}

Replaces vertex with new_vertex in the graph.

Example

iex> g = Graph.new |> Graph.add_vertices([:a, :b]) |> Graph.add_edge(:a, :b)
...> [:a, :b] = Graph.vertices(g)
...> g = Graph.replace_vertex(g, :a, :c)
...> [:b, :c] = Graph.vertices(g)
...> Graph.edges(g)
[%Graph.Edge{v1: :c, v2: :b}]
Link to this function split_edge(g, v1, v2, v3)
split_edge(t, vertex, vertex, vertex) ::
  t |
  {:error, :no_such_edge}

Splits the edge between v1 and v2 by inserting a new vertex, v3, deleting the edge between v1 and v2, and inserting an edge from v1 to v3 and from v3 to v2.

The two resulting edges from the split will share the same weight and label.

Example

iex> g = Graph.new |> Graph.add_vertices([:a, :c]) |> Graph.add_edge(:a, :c, weight: 2)
...> g = Graph.split_edge(g, :a, :c, :b)
...> Graph.edges(g)
[%Graph.Edge{v1: :a, v2: :b, weight: 2}, %Graph.Edge{v1: :b, v2: :c, weight: 2}]
Link to this function strong_components(g)
strong_components(t) :: [[vertex]]

Returns a list of strongly connected components, where each component is a list of vertices.

A strongly connected component is a maximal subgraph such that there is a path between each pair of vertices.

See components/1 for the definitions of subgraph and maximal subgraph if you are unfamiliar with the terminology.

Example

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
...> g = Graph.add_edges(g, [{:a, :b}, {:a, :c}, {:b, :c}, {:c, :d}, {:c, :a}])
...> Graph.strong_components(g)
[[:d], [:b, :c, :a]]
Link to this function subgraph(graph, vs)
subgraph(t, [vertex]) :: t

Builds a maximal subgraph of g which includes all of the vertices in vs and the edges which connect them.

See the test suite for example usage.

Link to this function topsort(g)
topsort(t) :: [vertex]

Returns a topological ordering of the vertices of graph g, if such an ordering exists, otherwise it returns false. For each vertex in the returned list, no out-neighbors occur earlier in the list.

Example

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
...> g = Graph.add_edges(g, [{:a, :b}, {:a, :c}, {:b, :c}, {:c, :d}])
...> Graph.topsort(g)
[:a, :b, :c, :d]

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d])
...> g = Graph.add_edges(g, [{:a, :b}, {:a, :c}, {:b, :c}, {:c, :d}, {:c, :a}])
...> Graph.topsort(g)
false
Link to this function transpose(g)
transpose(t) :: t

The transposition of a graph is another graph with the direction of all the edges reversed.

Example

iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c]) |> Graph.add_edge(:a, :b) |> Graph.add_edge(:b, :c)
...> g |> Graph.transpose |> Graph.edges
[%Graph.Edge{v1: :b, v2: :a}, %Graph.Edge{v1: :c, v2: :b}]
Link to this function update_edge(g, v1, v2, opts)
update_edge(t, vertex, vertex, Graph.Edge.edge_opts) ::
  t |
  {:error, :no_such_edge}

Updates the metadata (weight/label) for an edge using the provided options.

Example

iex> g = Graph.new |> Graph.add_edge(:a, :b)
...> [%Graph.Edge{v1: :a, v2: :b, label: nil, weight: 1}] = Graph.edges(g)
...> %Graph{} = g = Graph.update_edge(g, :a, :b, weight: 2, label: :foo)
...> Graph.edges(g)
[%Graph.Edge{v1: :a, v2: :b, label: :foo, weight: 2}]
Link to this function vertices(graph)
vertices(t) :: vertex

Returns a list of all the vertices in the graph.

NOTE: You should be careful when using this on large graphs, as the list it produces contains every vertex on the graph. I have not yet verified whether Erlang ensures that they are a shared reference with the original, or copies, but if the latter it could result in running out of memory if the graph is too large.

Example

iex> g = Graph.new |> Graph.add_vertex(:a) |> Graph.add_vertex(:b)
...> Graph.vertices(g)
[:a, :b]
Link to this function walk(g, acc, fun)
walk(t, term, walker_fun) :: term

Walks the graph by starting with a depth-first traversal, the walk function receives the current vertex, it’s out-neighbors and in-neighbors (as lists of Edge structs), and the accumulator. You can return one of the following to control the walk:

  • {:next, acc}, continues the depth-first traversal, passing along the accumulator
  • {:next, v, acc}, continues the traversal from v, passing along the accumulator
  • {:skip, acc}, skips traversal of the current vertex’s out-neighbors, passing along the accumulator
  • {:halt, acc}, stops the traversal, returning the accumulator

You can use this function to implement your own traversals of the graph, and as a foundation for reducers of your own design.

NOTE: If you take control over the direction of the traversal, take care that you handle cycles correctly, or you may end up following cycles indefinitely.

Options

walk/3 can also be provided with an options list, the current options available are:

  • algorithm: :breadth_first | :depth_first, performs the traversal using the selected algorithm. The default is :depth_first.
Link to this function walk(g, acc, fun, opts)
walk(t, term, walker_fun, opts :: [walk_opt]) :: term

See Graph.Reducer.walk/4.