engine: undirected simple graph with deterministic neighbour order
Adjacency lives in slices, not maps, on purpose: Go randomises map iteration order between runs, and the engine's determinism guarantee needs every graph walk to visit neighbours in the same order. AddEdge mirrors networkx semantics (duplicates and self loops are no-ops) so the Holme-Kim port can lean on the same behaviour. Receiver names stay short per Go convention (g *Graph); everything else uses descriptive names.
This commit is contained in:
parent
33d40a18ea
commit
33a85cb720
3 changed files with 133 additions and 6 deletions
76
internal/engine/graph_test.go
Normal file
76
internal/engine/graph_test.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Table-driven tests are the standard Go shape: a slice of cases, one
|
||||
// t.Run per case so failures name the case that broke.
|
||||
|
||||
func TestGraphAddEdge(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
edges [][2]int // applied in order
|
||||
wantAdded []bool // expected AddEdge result per edge
|
||||
wantEdges int // expected NumEdges afterwards
|
||||
}{
|
||||
{
|
||||
name: "simple edges",
|
||||
edges: [][2]int{{0, 1}, {1, 2}},
|
||||
wantAdded: []bool{true, true},
|
||||
wantEdges: 2,
|
||||
},
|
||||
{
|
||||
name: "duplicate ignored both directions",
|
||||
edges: [][2]int{{0, 1}, {0, 1}, {1, 0}},
|
||||
wantAdded: []bool{true, false, false},
|
||||
wantEdges: 1,
|
||||
},
|
||||
{
|
||||
name: "self loop ignored",
|
||||
edges: [][2]int{{2, 2}},
|
||||
wantAdded: []bool{false},
|
||||
wantEdges: 0,
|
||||
},
|
||||
}
|
||||
for _, testCase := range tests {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
graph := NewGraph(4)
|
||||
for edgeIndex, edge := range testCase.edges {
|
||||
added := graph.AddEdge(edge[0], edge[1])
|
||||
if added != testCase.wantAdded[edgeIndex] {
|
||||
t.Errorf("AddEdge(%d, %d) = %v, want %v",
|
||||
edge[0], edge[1], added, testCase.wantAdded[edgeIndex])
|
||||
}
|
||||
}
|
||||
if got := graph.NumEdges(); got != testCase.wantEdges {
|
||||
t.Errorf("NumEdges() = %d, want %d", got, testCase.wantEdges)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGraphNeighborsAndDegree(t *testing.T) {
|
||||
graph := NewGraph(4)
|
||||
graph.AddEdge(0, 1)
|
||||
graph.AddEdge(0, 2)
|
||||
graph.AddEdge(0, 3)
|
||||
|
||||
if got := graph.Degree(0); got != 3 {
|
||||
t.Errorf("Degree(0) = %d, want 3", got)
|
||||
}
|
||||
if got := graph.Degree(3); got != 1 {
|
||||
t.Errorf("Degree(3) = %d, want 1", got)
|
||||
}
|
||||
// Insertion order is part of the contract (determinism).
|
||||
if got, want := graph.Neighbors(0), []int{1, 2, 3}; !slices.Equal(got, want) {
|
||||
t.Errorf("Neighbors(0) = %v, want %v", got, want)
|
||||
}
|
||||
if !graph.HasEdge(2, 0) {
|
||||
t.Error("HasEdge(2, 0) = false, want true (undirected)")
|
||||
}
|
||||
if graph.HasEdge(1, 2) {
|
||||
t.Error("HasEdge(1, 2) = true, want false")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue