DG (dg v0.4.1)

View Source

Elixir wrapper of :digraph with a pinch of protocols and sigils

Installation

The package can be installed by adding dg to your list of dependencies in mix.exs:

def deps do
  [
    {:dg, "~> 0.4"},
    ...
  ]
end

Highlights

Inspect

dg = DG.new()
DG.add_vertex(dg, "v1")
DG.add_vertex(dg, "v2", "label 2")
DG.add_vertex(dg, "v3")
DG.add_edge(dg, "v1", "v2")
DG.add_edge(dg, "v1", "v3", "1 to 3")

IO.inspect(dg)

Outputs mermaid.js format flowchart

graph LR
    v1-->v2[label 2]
    v1--1 to 3-->v3

which can be put into livebook mermaid block and will display as

graph LR
    v1-->v2[label 2]
    v1--1 to 3-->v3

Collectable

Easily add

  • vertices [{:vertex, 1}, {:vertex, 2, "label 2"}, ...]
  • or edges [{:edge, 1, 2}, {:edge, "a", "b", "label ab"}, ...]

to the graph via Enum.into/2

dg = DG.new()

~w(a b c d e) |> Enum.map(&{:vertex, &1}) |> Enum.into(dg)

~w(a b c d e)
|> Enum.chunk_every(2, 1, :discard)
|> Enum.map(fn [f, t] -> {:edge, f, t} end)
|> Enum.into(dg)

IO.inspect(dg)

outputs

graph LR
    b-->c
    c-->d
    a-->b
    d-->e

Functions

  • Most functions from :digraph and :digraph_utils are mirrored under DG.
  • Some functions have their parameters re-arranged so that the graph is always the first parameter. (namely reachable/2, reachable_neighbours/2, reaching/2 and reaching_neighbours/2)
  • new/{0,1,2,3} returns %DG{} instead of an Erlang digraph
  • new/2 and new/3 are shortcuts that can add vertices and edges on creation, which don't exist on :digraph

Sigils

~G and ~g are provided so you can copy mermaid.js flowchart and paste into Elixir to get a %DG{}

import DG.Sigil

dg = ~G"""
graph LR
  a[some label]
  b[other label]
  1-->2
  3[three] -- three to four --> 4[four]
  a --> b
"""
# With interpolation

label = "1 2 3"
dg = ~g"""
graph LR
  a --#{label}--> b
"""

caveat: :digraph is stateful (using ets), don't use the sigil at compile time, e.g. as a module attribute, it won't carry over to runtime. Only use it in runtime code, e.g. function body, and remember to clean up properly when it's no longer used, with delete/1.

Load from libgraph

dg = DG.new()
DG.from({:libgraph, graph})

Summary

Functions

add_edge(dg, v1, v2)

add_edge(dg, v1, v2, label)

add_edge(dg, e, v1, v2, label)

add_vertex(dg)

add_vertex(dg, v)

add_vertex(dg, v, label)

arborescence_root(dg)

components(dg)

condensation(dg)

cyclic_strong_components(dg)

del_edge(dg, e)

del_edges(dg, edges)

del_path(dg, v1, v2)

del_vertex(dg, v)

del_vertices(dg, vertices)

delete(dg)

edge(dg, e)

edges(dg)

edges(dg, v)

from(arg)

get_cycle(dg, v)

get_path(dg, v1, v2)

get_short_cycle(dg, v)

get_short_path(dg, v1, v2)

in_degree(dg, v)

in_edges(dg, v)

in_neighbours(dg, v)

info(dg)

is_acyclic(dg)

is_arborescence(dg)

is_tree(dg)

loop_vertices(dg)

new(opts \\ [])

new(vertices, opts)

new(vertices, edges, opts)

no_edges(dg)

no_vertices(dg)

options(dg)

options(dg, new_opts)

out_degree(dg, v)

out_edges(dg, v)

out_neighbours(dg, v)

postorder(dg)

preorder(dg)

reachable(dg, vertices)

reachable_neighbours(dg, vertices)

reaching(dg, vertices)

reaching_neighbours(dg, vertices)

strong_components(dg)

subgraph(dg, vertices, options \\ [])

topsort(dg)

vertex(dg, v)

vertices(dg)