810-eulerian-path — Eulerian Path
Tutorial Video
Text description (accessibility)
This video demonstrates the "810-eulerian-path — Eulerian Path" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. An Eulerian path visits every edge exactly once. Key difference from OCaml: 1. **Edge consumption**: Rust removes edges from `adj[v]` using `pop`; OCaml uses list tails — both avoid revisiting edges.
Tutorial
The Problem
An Eulerian path visits every edge exactly once. Euler proved in 1736 (the Königsberg bridge problem — the first graph theory result) that an Eulerian path exists if and only if exactly 0 or 2 vertices have odd degree (directed: out-degree − in-degree = ±1 for endpoints, 0 for all others). Eulerian circuits (closed paths) require all vertices to have equal in/out-degree. Applications include printed circuit board routing, DNA sequence assembly (de Bruijn graphs), and postman route optimization.
🎯 Learning Outcomes
Code Example
#![allow(clippy::all)]
//! # Eulerian Path
pub fn eulerian_path(n: usize, edges: &[(usize, usize)]) -> Option<Vec<usize>> {
let mut adj: Vec<Vec<usize>> = vec![vec![]; n];
let mut deg = vec![[0, 0]; n];
for &(u, v) in edges {
adj[u].push(v);
deg[u][0] += 1;
deg[v][1] += 1;
}
let (mut start, mut end) = (None, None);
for v in 0..n {
let diff = deg[v][0] as i32 - deg[v][1] as i32;
match diff {
1 => {
if start.is_some() {
return None;
} else {
start = Some(v);
}
}
-1 => {
if end.is_some() {
return None;
} else {
end = Some(v);
}
}
0 => {}
_ => return None,
}
}
let start = start.unwrap_or(0);
let mut path = vec![];
let mut stack = vec![start];
while let Some(&v) = stack.last() {
if adj[v].is_empty() {
path.push(v);
stack.pop();
} else {
let u = adj[v].pop().unwrap();
stack.push(u);
}
}
path.reverse();
if path.len() != edges.len() + 1 {
None
} else {
Some(path)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_euler() {
let p = eulerian_path(4, &[(0, 1), (1, 2), (2, 0), (0, 3), (3, 0)]);
assert!(p.is_some());
}
}Key Differences
adj[v] using pop; OCaml uses list tails — both avoid revisiting edges.OCaml Approach
OCaml implements with Array.make n [] for adjacency and Array.make n 0 for degree tracking. Hierholzer's stack uses a list ref. OCaml's List.tl advances the adjacency list. The de Bruijn graph construction for DNA assembly is a natural OCaml application given its string processing strengths. Ocamlgraph.Euler provides a clean implementation.
Full Source
#![allow(clippy::all)]
//! # Eulerian Path
pub fn eulerian_path(n: usize, edges: &[(usize, usize)]) -> Option<Vec<usize>> {
let mut adj: Vec<Vec<usize>> = vec![vec![]; n];
let mut deg = vec![[0, 0]; n];
for &(u, v) in edges {
adj[u].push(v);
deg[u][0] += 1;
deg[v][1] += 1;
}
let (mut start, mut end) = (None, None);
for v in 0..n {
let diff = deg[v][0] as i32 - deg[v][1] as i32;
match diff {
1 => {
if start.is_some() {
return None;
} else {
start = Some(v);
}
}
-1 => {
if end.is_some() {
return None;
} else {
end = Some(v);
}
}
0 => {}
_ => return None,
}
}
let start = start.unwrap_or(0);
let mut path = vec![];
let mut stack = vec![start];
while let Some(&v) = stack.last() {
if adj[v].is_empty() {
path.push(v);
stack.pop();
} else {
let u = adj[v].pop().unwrap();
stack.push(u);
}
}
path.reverse();
if path.len() != edges.len() + 1 {
None
} else {
Some(path)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_euler() {
let p = eulerian_path(4, &[(0, 1), (1, 2), (2, 0), (0, 3), (3, 0)]);
assert!(p.is_some());
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_euler() {
let p = eulerian_path(4, &[(0, 1), (1, 2), (2, 0), (0, 3), (3, 0)]);
assert!(p.is_some());
}
}
Deep Comparison
OCaml vs Rust: Eulerian Path
Overview
See the example.rs and example.ml files for detailed implementations.
Key Differences
| Aspect | OCaml | Rust |
|---|---|---|
| Type system | Hindley-Milner | Ownership + traits |
| Memory | GC | Zero-cost abstractions |
| Mutability | Explicit ref | mut keyword |
| Error handling | Option/Result | Result<T, E> |
See README.md for detailed comparison.
Exercises
eulerian_circuit_undirected(n, edges) for undirected graphs: check that all vertices have even degree and find the circuit.