32_euler

command
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 14 Imported by: 0

README

Example 32 — Eulerian circuits (route inspection)

What it demonstrates

Finding an Eulerian circuit — a tour that traverses every edge exactly once and returns to its start — with Hierholzer's algorithm, over both an undirected graph (search.HierholzerUndirected) and a directed one (search.Hierholzer). It verifies the returned tour uses every street exactly once and is a closed circuit, and shows the module correctly reporting search.ErrNoEulerian when the preconditions fail. The scenario is route inspection (the "Chinese postman" setting): a fleet must cover every street of a network once and come back to the depot.

A subtle, faithful lesson is built in: closing a single street does not destroy the route. Removing one undirected edge leaves exactly two odd-degree vertices, so an Eulerian path still exists — a valid inspection run that simply no longer returns to the depot (the directed analogue leaves one surplus source and one surplus sink). Hierholzer finds that path. Only when two vertex-disjoint streets are closed (four odd-degree vertices) does every Eulerian trail disappear and the module report ErrNoEulerian. The -broken flag closes two disjoint streets for exactly this reason.

Domain / scenario

A seeded route network assembled from edge-disjoint cycles ("patrol loops"). A base ring through a random permutation of all -nodes intersections guarantees the network is connected and every node starts at even degree. Each of -loops extra loops is a simple cycle over a random subset of intersections, added only if all of its streets are new (edge-disjoint). Because every added cycle raises each of its nodes' degree by two (undirected) — or its in- and out-degree by one each (directed) — the Eulerian precondition is preserved by construction no matter how many loops are layered on. The streets far outnumber the intersections, so Hierholzer genuinely stitches many loops together rather than walking a single cycle.

How to run

go run ./examples/32_euler                              # small deterministic default
go run ./examples/32_euler -nodes 200000 -loops 40000   # observable-scale run
go run ./examples/32_euler -broken                      # close two streets → no Eulerian tour

Scale and flags

Flag Meaning Default Large
-nodes number of intersections (≥ 4) 200 200000
-loops extra edge-disjoint patrol loops 40 40000
-loop-min minimum loop length (≥ 3) 3 3
-loop-max maximum loop length (≤ nodes) 8 8
-broken close two disjoint streets to force ErrNoEulerian false
-seed RNG seed (fixes the network shape) 1 any

Expected output

At the default config the deterministic fact lines are:

config.nodes=200
config.loops=40
config.broken=false
config.seed=1
undirected.streets=404
undirected.trail_len=405
undirected.trail_len_is_streets_plus_1=true
undirected.each_street_once=true
undirected.is_circuit=true
directed.streets=404
directed.trail_len=405
directed.trail_len_is_streets_plus_1=true
directed.each_street_once=true
directed.is_circuit=true

With -broken the two street closures leave four odd-degree vertices, so:

undirected.streets=402
undirected.no_eulerian=true
directed.streets=402
directed.no_eulerian=true

Interleaved with the facts are volatile telemetry lines, prefixed with # , that vary per run and per machine:

# undirected.elapsed=53µs
# directed.elapsed=16µs
# mem.heap_alloc=208.70 KiB

The regression test pins the fact lines and ignores every # line.

Evidence it collects

For the traversal subject (per docs/examples-standard.md): the per-tour wall-clock (# undirected.elapsed, # directed.elapsed) for the O(E) Hierholzer pass, and live heap (# mem.heap_alloc, # mem.heap_growth). The correctness evidence is the tour verification itself — every street used exactly once, trail length E + 1, and the circuit closing back to its start — asserted as facts rather than telemetry. Scale it up with -nodes / -loops and watch the elapsed grow linearly with the street count.

Key APIs

  • graph/adjlist.New (Directed: false / true) / AdjList.AddEdge — build the mutable route network; the undirected form mirrors each street automatically.
  • graph/csr.BuildFromAdjList — freeze the builder into the immutable CSR snapshot Hierholzer reads.
  • search.HierholzerUndirectedCtx — Eulerian circuit/path over an undirected (symmetric) CSR; returns the trail as []graph.NodeID of length E + 1, or search.ErrNoEulerian.
  • search.HierholzerCtx — the directed counterpart, requiring equal in- and out-degree for a circuit.
  • search.ErrNoEulerian — the sentinel returned when no Eulerian trail exists.

Further reading

Documentation

Overview

Example 32_euler — Eulerian circuits over a route-inspection network, using Hierholzer's algorithm on both an undirected and a directed graph.

The scenario is route inspection (the "Chinese postman" setting): a fleet must traverse every street of a network exactly once and return to the depot. Such a tour exists precisely when the network has an Eulerian circuit — every intersection has even degree (undirected), or equal in- and out-degree (directed, for one-way streets). The example builds a network that satisfies those conditions BY CONSTRUCTION, finds the circuit with search.Hierholzer / search.HierholzerUndirected, and verifies the tour uses every street exactly once. A -broken flag deletes one street to show the module correctly reports search.ErrNoEulerian when no such tour exists.

Topology — why it is always Eulerian

The network is assembled from edge-disjoint CYCLES (patrol loops). A base ring through a seeded random permutation of all N intersections guarantees the network is connected and every node starts at even degree. Each extra loop is a simple cycle over a random subset of intersections, added only if all its streets are new (edge-disjoint). Adding a cycle raises the degree of each node it touches by exactly two (undirected) — or its in- and out-degree by one each (directed) — so the Eulerian precondition is preserved no matter how many loops are layered on. The result is a realistic, seeded, non-trivial network (streets far outnumber intersections) whose Euler tour genuinely exercises Hierholzer's loop-stitching rather than walking a single cycle.

Scale

The small deterministic default (200 intersections, 40 extra loops) runs in milliseconds and is pinned by the regression test. Scale it up to make the O(E) tour construction observable:

go run ./examples/32_euler -nodes 200000 -loops 40000 -seed 7

Only the telemetry (lines prefixed with "# ") varies between runs; the deterministic facts are reproducible for a fixed -seed.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL