A MoonBit port of petgraph: graph data structures and algorithms.
moon add I3eg1nner/petgraphThe mooncakes.io package namespace is I3eg1nner/, while this repository is hosted at github.com/V1GreenSummer/. Both accounts belong to the same author; the namespace tracks the publishing account, the repository URL tracks where the source lives.
{
"import": [
"I3eg1nner/petgraph/graph",
"I3eg1nner/petgraph/algo",
"I3eg1nner/petgraph/dot"
]
}git clone https://github.com/V1GreenSummer/moonbit-petgraph.git && cd moonbit-petgraph
moon check # type-check
moon test # run the test suite
moon run src/cmd/main # run the demo///|
test "shortest path and spanning tree" {
// An undirected graph with `Int` node and edge weights.
//
// 0 -- 1
// | |
// 3 -- 2
let g : @graph.Graph[Int, Int] = @graph.Graph::new_undirected()
let n0 = g.add_node(0)
let n1 = g.add_node(1)
let n2 = g.add_node(2)
let n3 = g.add_node(3)
let _ = g.add_edge(n0, n1, 1)
let _ = g.add_edge(n1, n2, 1)
let _ = g.add_edge(n2, n3, 1)
let _ = g.add_edge(n0, n3, 1)
// Shortest-path distances from node 0 (each edge costs its weight).
let dist = @algo.dijkstra(g, start=n0, edge_cost=fn(e) {
g.edge_weight(e).unwrap()
})
// Distance to node 2 is 2 (via 0-1-2 or 0-3-2).
debug_inspect(dist.get(n2), content="Some(2)")
// Minimum spanning tree (Kruskal): the 4-cycle drops exactly one edge.
let mst = @algo.min_spanning_tree(g, edge_cost=fn(e) {
g.edge_weight(e).unwrap()
})
debug_inspect(mst.length(), content="3")
}///|
test "dot export" {
let g : @graph.Graph[Int, Unit] = @graph.from_edges([(0, 1), (1, 2)])
let dot = @dot.to_dot(g, config=[@dot.DotConfig::EdgeNoLabel])
assert_eq(
dot,
(
#|digraph {
#| 0 [ label = "0" ]
#| 1 [ label = "1" ]
#| 2 [ label = "2" ]
#| 0 -> 1 [ ]
#| 1 -> 2 [ ]
#|}
#|
),
)
}///|
test "traversal and cycles" {
// A directed acyclic graph: 0 -> 1 -> 3, 0 -> 2 -> 3.
let g : @graph.Graph[Int, Unit] = @graph.from_edges([
(0, 1),
(0, 2),
(1, 3),
(2, 3),
])
// Topological sort returns the order directly on a DAG, and raises
// `@algo.Cycle` on a cyclic graph (wrap in `try … catch` to handle that).
let order = @algo.toposort(g)
debug_inspect(order.length(), content="4")
// No directed cycle.
debug_inspect(@algo.is_cyclic_directed(g), content="false")
}///|
test "maximum flow" {
// A classic two-path network from source 0 to sink 3.
//
// 1
// 3/ \2
// 0 3
// 2\ /3
// 2
let g : @graph.Graph[Int, Int] = @graph.Graph::new()
for i in 0..<4 {
let _ = g.add_node(i)
}
let n = i => @graph.NodeId::new(i)
let _ = g.add_edge(n(0), n(1), 3)
let _ = g.add_edge(n(0), n(2), 2)
let _ = g.add_edge(n(1), n(3), 2)
let _ = g.add_edge(n(2), n(3), 3)
let cost = e => g.edge_weight(e).unwrap()
// Both algorithms agree on the value; the bottleneck is 2 + 2 = 4.
let (dinics_flow, _) = @algo.dinics(g, source=n(0), destination=n(3), edge_cost=cost)
let (ff_flow, _) = @algo.ford_fulkerson(
g, source=n(0), destination=n(3), edge_cost=cost,
)
debug_inspect(dinics_flow, content="4")
debug_inspect(ff_flow, content="4")
}///|
test "reversed view" {
// A directed path 0 -> 1 -> 2.
let g : @graph.Graph[Int, Unit] = @graph.from_edges([(0, 1), (1, 2)])
let start = @graph.NodeId::new(2)
// Forwards from node 2 there is nowhere to go.
let forward = @visit.Dfs::new(g, start).iter(g).collect()
debug_inspect(forward.map(x => x.index()), content="[2]")
// Reversed, node 2 reaches the whole path.
let rev = @visit.Reversed(g)
let backward = @visit.Dfs::new(rev, start).iter(rev).collect()
debug_inspect(backward.map(x => x.index()), content="[2, 1, 0]")
}# Print just the DOT block from the demo and render it.
moon run src/cmd/main | sed -n '/^\(di\)\?graph {/,/^}/p' | dot -Tsvg -o graph.svgOr paste the string into an online viewer such as
GraphvizOnline.| Rust petgraph | This port | why |
|---|---|---|
| NodeIndex / EdgeIndex | NodeId / EdgeId (.index() kept) | shorter; not a Rust index newtype |
| node_indices() / edge_indices() | node_ids() / edge_ids() | follows the NodeId rename |
| named iterators (Neighbors, NodeIndices, …) | lazy Iter[T] | MoonBit's standard iterator; for x in … is identical |
| toposort -> Result<_, Cycle> | toposort(…) raise Cycle | MoonBit error idiom — handle with try … catch |
| bellman_ford -> Result<_, NegativeCycle> | … raise NegativeCycle | same |
| Ty type parameter (Directed / Undirected) | runtime new vs new_undirected | no const-generic directedness |
| Measure / FloatMeasure / PositiveMeasure / BoundedMeasure | Measure and BoundedMeasure (Int, Double) | no numeric-tower traits to build on; four upstream bounds collapse into two |
| GraphBase / IntoNeighbors / Visitable / … (18 traits) | one pub(open) trait NeighborSource | MoonBit has no associated types or GATs; one trait covers what the traversals actually need |
| EdgeReference (borrowed) | EdgeRef[E] (owning struct, derive(Debug)) | no lifetimes |
| min_spanning_tree -> Iterator<Element> | -> Array[EdgeId] | ids are cheap here; caller reads weights from the graph |
| steiner_tree -> StableGraph | -> Array[EdgeId] | no StableGraph in this port |
| dinics hangs when source == destination | returns zero flow | a non-terminating call is worse than a divergence |
moon add I3eg1nner/petgraphThe mooncakes.io package namespace is I3eg1nner/, while this repository is hosted at github.com/V1GreenSummer/. Both accounts belong to the same author; the namespace tracks the publishing account, the repository URL tracks where the source lives.
{
"import": [
"I3eg1nner/petgraph/graph",
"I3eg1nner/petgraph/algo",
"I3eg1nner/petgraph/dot"
]
}git clone https://github.com/V1GreenSummer/moonbit-petgraph.git && cd moonbit-petgraph
moon check # type-check
moon test # run the test suite
moon run src/cmd/main # run the demo///|
test "shortest path and spanning tree" {
// An undirected graph with `Int` node and edge weights.
//
// 0 -- 1
// | |
// 3 -- 2
let g : @graph.Graph[Int, Int] = @graph.Graph::new_undirected()
let n0 = g.add_node(0)
let n1 = g.add_node(1)
let n2 = g.add_node(2)
let n3 = g.add_node(3)
let _ = g.add_edge(n0, n1, 1)
let _ = g.add_edge(n1, n2, 1)
let _ = g.add_edge(n2, n3, 1)
let _ = g.add_edge(n0, n3, 1)
// Shortest-path distances from node 0 (each edge costs its weight).
let dist = @algo.dijkstra(g, start=n0, edge_cost=fn(e) {
g.edge_weight(e).unwrap()
})
// Distance to node 2 is 2 (via 0-1-2 or 0-3-2).
debug_inspect(dist.get(n2), content="Some(2)")
// Minimum spanning tree (Kruskal): the 4-cycle drops exactly one edge.
let mst = @algo.min_spanning_tree(g, edge_cost=fn(e) {
g.edge_weight(e).unwrap()
})
debug_inspect(mst.length(), content="3")
}///|
test "dot export" {
let g : @graph.Graph[Int, Unit] = @graph.from_edges([(0, 1), (1, 2)])
let dot = @dot.to_dot(g, config=[@dot.DotConfig::EdgeNoLabel])
assert_eq(
dot,
(
#|digraph {
#| 0 [ label = "0" ]
#| 1 [ label = "1" ]
#| 2 [ label = "2" ]
#| 0 -> 1 [ ]
#| 1 -> 2 [ ]
#|}
#|
),
)
}///|
test "traversal and cycles" {
// A directed acyclic graph: 0 -> 1 -> 3, 0 -> 2 -> 3.
let g : @graph.Graph[Int, Unit] = @graph.from_edges([
(0, 1),
(0, 2),
(1, 3),
(2, 3),
])
// Topological sort returns the order directly on a DAG, and raises
// `@algo.Cycle` on a cyclic graph (wrap in `try … catch` to handle that).
let order = @algo.toposort(g)
debug_inspect(order.length(), content="4")
// No directed cycle.
debug_inspect(@algo.is_cyclic_directed(g), content="false")
}///|
test "maximum flow" {
// A classic two-path network from source 0 to sink 3.
//
// 1
// 3/ \2
// 0 3
// 2\ /3
// 2
let g : @graph.Graph[Int, Int] = @graph.Graph::new()
for i in 0..<4 {
let _ = g.add_node(i)
}
let n = i => @graph.NodeId::new(i)
let _ = g.add_edge(n(0), n(1), 3)
let _ = g.add_edge(n(0), n(2), 2)
let _ = g.add_edge(n(1), n(3), 2)
let _ = g.add_edge(n(2), n(3), 3)
let cost = e => g.edge_weight(e).unwrap()
// Both algorithms agree on the value; the bottleneck is 2 + 2 = 4.
let (dinics_flow, _) = @algo.dinics(
g,
source=n(0),
destination=n(3),
edge_cost=cost,
)
let (ff_flow, _) = @algo.ford_fulkerson(
g,
source=n(0),
destination=n(3),
edge_cost=cost,
)
debug_inspect(dinics_flow, content="4")
debug_inspect(ff_flow, content="4")
}///|
test "reversed view" {
// A directed path 0 -> 1 -> 2.
let g : @graph.Graph[Int, Unit] = @graph.from_edges([(0, 1), (1, 2)])
let start = @graph.NodeId::new(2)
// Forwards from node 2 there is nowhere to go.
let forward = @visit.Dfs::new(g, start).iter(g).collect()
debug_inspect(forward.map(x => x.index()), content="[2]")
// Reversed, node 2 reaches the whole path.
let rev = @visit.Reversed(g)
let backward = @visit.Dfs::new(rev, start).iter(rev).collect()
debug_inspect(backward.map(x => x.index()), content="[2, 1, 0]")
}# Print just the DOT block from the demo and render it.
moon run src/cmd/main | sed -n '/^\(di\)\?graph {/,/^}/p' | dot -Tsvg -o graph.svgOr paste the string into an online viewer such as
GraphvizOnline.| Rust petgraph | This port | why |
|---|---|---|
| NodeIndex / EdgeIndex | NodeId / EdgeId (.index() kept) | shorter; not a Rust index newtype |
| node_indices() / edge_indices() | node_ids() / edge_ids() | follows the NodeId rename |
| named iterators (Neighbors, NodeIndices, …) | lazy Iter[T] | MoonBit's standard iterator; for x in … is identical |
| toposort -> Result<_, Cycle> | toposort(…) raise Cycle | MoonBit error idiom — handle with try … catch |
| bellman_ford -> Result<_, NegativeCycle> | … raise NegativeCycle | same |
| Ty type parameter (Directed / Undirected) | runtime new vs new_undirected | no const-generic directedness |
| Measure / FloatMeasure / PositiveMeasure / BoundedMeasure | Measure and BoundedMeasure (Int, Double) | no numeric-tower traits to build on; four upstream bounds collapse into two |
| GraphBase / IntoNeighbors / Visitable / … (18 traits) | one pub(open) trait NeighborSource | MoonBit has no associated types or GATs; one trait covers what the traversals actually need |
| EdgeReference (borrowed) | EdgeRef[E] (owning struct, derive(Debug)) | no lifetimes |
| min_spanning_tree -> Iterator<Element> | -> Array[EdgeId] | ids are cheap here; caller reads weights from the graph |
| steiner_tree -> StableGraph | -> Array[EdgeId] | no StableGraph in this port |
| dinics hangs when source == destination | returns zero flow | a non-terminating call is worse than a divergence |
fn version() -> Stringtest {
inspect(@petgraph.version(), content="0.2.0")
}A MoonBit port of petgraph: graph data structures and algorithms.