Graphs and graph algorithms for MoonBit: four storage types that share one interface, and the algorithm families that run on all of them.
let g : @moongraph.Graph[String, Double] = @moongraph.Graph::new(directed=false)
let a = g.add_node("a")
let b = g.add_node("b")
let c = g.add_node("c")
g.add_edge(a, b, 1.0) |> ignore
g.add_edge(b, c, 2.0) |> ignore
g.add_edge(a, c, 5.0) |> ignore
let w = g.weights(fn(cost) { cost })
let paths = @path.dijkstra(g, a, weight=w)
assert_eq(paths.path_to(c), Some([a, b, c]))
assert_eq(paths.distance[c], Some(3.0))| Type | Shape | Parallel edges | Numbers after a deletion | Use it when |
|---|---|---|---|---|
| Graph[N, E] | Adjacency list | Yes | Renumbered | The default: build it, change it, run algorithms on it |
| StableGraph[N, E] | Adjacency list with holes | Yes | Kept | You hold node numbers across deletions |
| Csr[N, E] | Compressed adjacency, both directions | Yes | — | Read-only, built once, walked many times |
| Matrix[E] | V x V cells | No | Renumbered | Dense graphs and the passes that look at every pair |
pub(open) trait Topology {
node_count(Self) -> Int
edge_count(Self) -> Int
directed(Self) -> Bool
neighbors(Self, Int) -> ArrayView[Int] // one entry per edge
incident(Self, Int) -> ArrayView[Int] // the edge numbers, in the same order
endpoints(Self, Int) -> (Int, Int)
}
pub(open) trait Inbound {
in_neighbors(Self, Int) -> ArrayView[Int]
in_incident(Self, Int) -> ArrayView[Int]
}let by_cost = @path.dijkstra(g, a, weight=w) // the payload is the cost
let by_hop = @path.dijkstra(g, a) // every edge costs one
assert_eq(by_cost.distance[c], Some(3.0))
assert_eq(by_hop.distance[c], Some(1.0))let r : @moongraph.Graph[Unit, Double] = @moongraph.Graph::new()
r.add_node(()) |> ignore
r.add_node(()) |> ignore
r.add_edge(0, 1, -1.0) |> ignore
let outcome = try {
let _ = @path.dijkstra(r, 0, weight=r.weights(fn(cost) { cost }))
"answered"
} catch {
@moongraph.Refused::NegativeWeight(edge~) => "edge " + edge.to_string() + " is negative"
_ => "answered"
}
assert_eq(outcome, "edge 0 is negative")for cycle in @cycle.simple_cycles(g).take(10) {
inspect(cycle)
}| Package | What it answers |
|---|---|
| moongraph | Graph, StableGraph, Csr, Matrix, Keyed, the traits, the result records |
| moongraph/traverse | breadth-first and depth-first walks, their trees, layers and events |
| moongraph/path | Dijkstra, Bellman-Ford, Johnson, Floyd-Warshall, A*, k shortest, widest, simple routes, contraction hierarchies |
| moongraph/grid | board pathfinding: breadth first, Dijkstra, A*, jump-point search, any-angle Theta*, and D* Lite for a board that changes |
| moongraph/component | components, strong components in topological order, condensation, biconnected pieces, bridges, cuts |
| moongraph/dsu | the disjoint-set forest: union, find, and the pieces a graph falls into |
| moongraph/sat | two-variable clauses, and the assignment when one exists |
| moongraph/dag | topological orders, generations, closures, longest path, dominance |
| moongraph/cycle | cycles and cycle bases, girth, Euler walks, the postman's route |
| moongraph/tree | spanning trees, branchings, Steiner tree, centres, Prüfer codes, common ancestors |
| moongraph/flow | maximum flow, minimum cut, cost flow, connectivity, disjoint routes |
| moongraph/matching | matchings, assignments, covers, the blossom walk |
| moongraph/color | greedy and saturation colourings, edge colouring, chromatic number |
| moongraph/clique | maximal cliques, independent sets, chordality |
| moongraph/centrality | degree, closeness, betweenness, eigenvector, PageRank, hubs and authorities |
| moongraph/community | Louvain, Leiden, label propagation, edge-betweenness splitting |
| moongraph/metric | density, distance, clustering, similarity, assortativity, accessibility |
| moongraph/struct | what kind of graph this is — tree, bipartite, planar, regular — and the cores and trusses that say how tightly it is knit |
| moongraph/iso | isomorphism, and the mapping when there is one |
| moongraph/op | unions, products, complements, subgraphs, line graphs, contraction, and the algebraic operators: nmap, emap, gmap, remove_vertex, remove_edge, connect, closure |
| moongraph/approx | tours, covers, dominating sets, spanners, treewidth bounds |
| moongraph/gen | the named graphs, and the random families worth having by seed |
| moongraph/io | edge lists, adjacency lists, GML, graph6, sparse6, Pajek |
| moongraph/io/graphml | GraphML, on top of an XML parser |
assert_eq(@struct.is_digraphical([(0, 1), (0, 1)]), false)
assert_eq(@struct.is_digraphical([(1, 1), (1, 1), (1, 1)]), true)
assert_eq(@struct.gale_ryser([3, 0], [3, 0]), false)let text = @io.edge_list(g)
let back = @io.parse_edges(text) // Graph[Unit, Double]; the third column is the weight
assert_eq(back.node_count(), 3)
assert_eq(back.edge_count(), 3)| Format | Writes | Reads | Notes |
|---|---|---|---|
| Edge list | yes | yes | two or three columns, # for comments |
| Adjacency list | yes | yes | one line per node |
| GML | yes | yes | nested key value lists |
| graph6 | yes | yes | one bit per possible edge |
| sparse6 | yes | yes | only the edges that are there, with a repeat flag |
| Pajek | yes | yes | counts nodes from one |
| GraphML | yes | yes | node labels and edge weights as data |
moon add moonbitstack/moongraphtest {
let refused = @moongraph.Refused::Cycle(nodes=[0, 1, 0])
let line = match refused {
Cycle(nodes~) => "a cycle through " + nodes.length().to_string() + " nodes"
_ => "something else"
}
assert_eq(line, "a cycle through 3 nodes")
}test {
let g : @moongraph.Graph[Unit, Int32] = @moongraph.Graph::from_edges(
[(0, 1), (1, 2)],
node=(),
edge=0,
directed=false,
)
let csr = @moongraph.Csr::from(g)
assert_eq(csr.node_count(), 3)
assert_eq(csr.neighbors(1), [0, 2][:])
assert_eq(csr.in_neighbors(2), [1][:])
}test {
let g : @moongraph.Graph[String, Double] = @moongraph.Graph::new()
let a = g.add_node("a")
let b = g.add_node("b")
g.add_edge(a, b, 2.5) |> ignore
assert_eq(g.node_count(), 2)
assert_eq(g.neighbors(a), [b][:])
assert_eq(g.edge_data(g.find_edge(a, b).unwrap()), 2.5)
}test {
let g : @moongraph.Graph[Int32, Unit] = @moongraph.Graph::from_edges(
[(0, 1)],
node=0,
edge=(),
directed=false,
)
g.set_node_data(0, 7)
let flipped = g.relabel([1, 0])
assert_eq(flipped.node_data(1), 7)
assert_eq(flipped.endpoints(0), (1, 0))
}test {
let g : @moongraph.Graph[Unit, Unit] = @moongraph.Graph::from_edges(
[(0, 1), (1, 2)],
node=(),
edge=(),
directed=false,
)
let d = g.to_directed()
assert_eq(d.directed(), true)
assert_eq(d.edge_count(), 4)
assert_eq(d.endpoints(2), (1, 2))
assert_eq(d.endpoints(3), (2, 1))
}test {
let g : @moongraph.Graph[Unit, Double] = @moongraph.Graph::new()
let k = @moongraph.Keyed::new(graph=g)
let a = k.add_node("a", ())
let b = k.add_node("b", ())
k.add_edge("a", "b", 1.0) |> ignore
assert_eq(k.node("b"), Some(b))
assert_eq(k.key(a), Some("a"))
assert_eq(k.graph().edge_count(), 1)
}test {
let m : @moongraph.Matrix[Double] = @moongraph.Matrix::new(3)
m.set(0, 2, 1.5)
assert_eq(m.get(0, 2), Some(1.5))
assert_eq(m.get(2, 0), None)
assert_eq(m.edge_count(), 1)
assert_eq(m.neighbors(0), [2][:])
}pub struct StableGraph[N, E] {
directed : Bool
nodes : Array[N]
node_alive : Array[Bool]
vacant : Int
payload : Array[E]
froms : Array[Int]
tos : Array[Int]
alive : Array[Bool]
live : Int
out_nbrs : Array[Array[Int]]
out_eids : Array[Array[Int]]
in_nbrs : Array[Array[Int]]
in_eids : Array[Array[Int]]
empty : Array[Int]
}test {
let g : @moongraph.StableGraph[String, Unit] = @moongraph.StableGraph::new()
for name in ["a", "b", "c"] {
g.add_node(name) |> ignore
}
g.remove_node(1)
assert_eq(g.node_count(), 2)
assert_eq(g.is_vacant(1), true)
assert_eq(g.highest_node(), Some(2))
}impl Inbound for StableGraph[N, E]impl Topology for StableGraph[N, E]fn[N, E] StableGraph::from_edges(edges : Array[(Int, Int)], node~ : N, edge~ : E, directed? : Bool) -> StableGraph[N, E]Install
Download zipGraphs and graph algorithms for MoonBit: four storage types that share one interface, and the algorithm families that run on all of them.