037 — Preorder Traversal Sequence
Tutorial Video
Text description (accessibility)
This video demonstrates the "037 — Preorder Traversal Sequence" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Preorder traversal visits nodes in root-left-right order, producing a sequence where the root always comes before its descendants. Key difference from OCaml: 1. **String construction**: Rust uses `format!` or `String::push`. OCaml uses `^` (string concatenation) which is O(n) per call — avoid in inner loops; use `Buffer` instead.
Tutorial
The Problem
Preorder traversal visits nodes in root-left-right order, producing a sequence where the root always comes before its descendants. This ordering has a critical property: in a full binary tree (where leaves are uniquely identifiable), the preorder sequence uniquely determines the tree. Combined with the inorder sequence (from example 038), any binary tree can be reconstructed.
Preorder traversal underlies expression tree serialization (prefix notation: "+ 3 4" rather than "3 + 4"), directory tree listing (find command), syntax tree serialization in compilers, and tree copying algorithms. Depth-first search (DFS) on graphs is a generalization of preorder traversal.
🎯 Learning Outcomes
. represents a leaf (self-delimiting format)Vec stack instead of recursion to avoid stack overflowCode Example
#![allow(clippy::all)]
// Preorder dot-string encoding of a tree: "." for Leaf, self-delimiting (OCaml 99 Problems, ext. of #29-40).
#[derive(Debug, Clone, PartialEq)]
pub enum Tree<T> {
Leaf,
Node(T, Box<Tree<T>>, Box<Tree<T>>),
}
impl<T> Tree<T> {
pub fn leaf() -> Self {
Tree::Leaf
}
pub fn node(val: T, left: Tree<T>, right: Tree<T>) -> Self {
Tree::Node(val, Box::new(left), Box::new(right))
}
}
pub fn preorder(tree: &Tree<char>) -> String {
match tree {
Tree::Leaf => ".".to_string(),
Tree::Node(c, l, r) => format!("{}{}{}", c, preorder(l), preorder(r)),
}
}
pub fn from_preorder(s: &str) -> Tree<char> {
let chars: Vec<char> = s.chars().collect();
let mut pos = 0;
parse(&chars, &mut pos)
}
fn parse(chars: &[char], pos: &mut usize) -> Tree<char> {
let c = chars[*pos];
*pos += 1;
if c == '.' {
Tree::Leaf
} else {
let left = parse(chars, pos);
let right = parse(chars, pos);
Tree::node(c, left, right)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> Tree<char> {
Tree::node('a', Tree::node('b', Tree::leaf(), Tree::leaf()), Tree::node('c', Tree::leaf(), Tree::leaf()))
}
#[test]
fn test_preorder_encoding() {
assert_eq!(preorder(&sample()), "ab..c..");
}
#[test]
fn test_from_preorder() {
assert_eq!(from_preorder("ab..c.."), sample());
}
#[test]
fn test_round_trip() {
assert_eq!(from_preorder(&preorder(&sample())), sample());
}
#[test]
fn test_leaf_tree() {
assert_eq!(preorder(&Tree::Leaf), ".");
assert_eq!(from_preorder("."), Tree::Leaf);
}
}Key Differences
format! or String::push. OCaml uses ^ (string concatenation) which is O(n) per call — avoid in inner loops; use Buffer instead.. consumes exactly one position, and a node character consumes one position followed by two self-delimiting subtrees. No length prefix needed.&mut usize cursor or slices. OCaml's functional version returns (result, remaining_string) pairs — the monadic parser style.Vec<&Tree<T>> and processes nodes iteratively — safe for deep trees in Rust (no TCO guarantee).extend for subtrees:** result.extend(preorder(left)); result.extend(preorder(right)) copies values. An accumulator approach (preorder_aux(tree, &mut acc)) is more efficient for large trees.Vec<&Tree<T>> stack avoids deep recursion. Push right child first, then left, so left is processed first (LIFO stack reverses order).OCaml Approach
OCaml's version: let rec preorder = function | Leaf -> "." | Node (c, l, r) -> String.make 1 c ^ preorder l ^ preorder r. Reconstruction: let rec of_preorder s = match s.[0] with | '.' -> (Leaf, String.sub s 1 ...) | c -> let (l, s') = of_preorder (String.sub s 1 ...) in let (r, s'') = of_preorder s' in (Node (c, l, r), s''). The function returns (tree, remaining_string).
Full Source
#![allow(clippy::all)]
// Preorder dot-string encoding of a tree: "." for Leaf, self-delimiting (OCaml 99 Problems, ext. of #29-40).
#[derive(Debug, Clone, PartialEq)]
pub enum Tree<T> {
Leaf,
Node(T, Box<Tree<T>>, Box<Tree<T>>),
}
impl<T> Tree<T> {
pub fn leaf() -> Self {
Tree::Leaf
}
pub fn node(val: T, left: Tree<T>, right: Tree<T>) -> Self {
Tree::Node(val, Box::new(left), Box::new(right))
}
}
pub fn preorder(tree: &Tree<char>) -> String {
match tree {
Tree::Leaf => ".".to_string(),
Tree::Node(c, l, r) => format!("{}{}{}", c, preorder(l), preorder(r)),
}
}
pub fn from_preorder(s: &str) -> Tree<char> {
let chars: Vec<char> = s.chars().collect();
let mut pos = 0;
parse(&chars, &mut pos)
}
fn parse(chars: &[char], pos: &mut usize) -> Tree<char> {
let c = chars[*pos];
*pos += 1;
if c == '.' {
Tree::Leaf
} else {
let left = parse(chars, pos);
let right = parse(chars, pos);
Tree::node(c, left, right)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> Tree<char> {
Tree::node('a', Tree::node('b', Tree::leaf(), Tree::leaf()), Tree::node('c', Tree::leaf(), Tree::leaf()))
}
#[test]
fn test_preorder_encoding() {
assert_eq!(preorder(&sample()), "ab..c..");
}
#[test]
fn test_from_preorder() {
assert_eq!(from_preorder("ab..c.."), sample());
}
#[test]
fn test_round_trip() {
assert_eq!(from_preorder(&preorder(&sample())), sample());
}
#[test]
fn test_leaf_tree() {
assert_eq!(preorder(&Tree::Leaf), ".");
assert_eq!(from_preorder("."), Tree::Leaf);
}
}#[cfg(test)]
mod tests {
use super::*;
fn sample() -> Tree<char> {
Tree::node('a', Tree::node('b', Tree::leaf(), Tree::leaf()), Tree::node('c', Tree::leaf(), Tree::leaf()))
}
#[test]
fn test_preorder_encoding() {
assert_eq!(preorder(&sample()), "ab..c..");
}
#[test]
fn test_from_preorder() {
assert_eq!(from_preorder("ab..c.."), sample());
}
#[test]
fn test_round_trip() {
assert_eq!(from_preorder(&preorder(&sample())), sample());
}
#[test]
fn test_leaf_tree() {
assert_eq!(preorder(&Tree::Leaf), ".");
assert_eq!(from_preorder("."), Tree::Leaf);
}
}
Deep Comparison
OCaml vs Rust: Tree Preorder
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
postorder(tree: &Tree<char>) -> String with left-right-root order using a similar dot-string encoding. The dot for leaves must appear after the two empty subtrees.preorder_iterative<T: Clone>(tree: &Tree<T>) -> Vec<T> using an explicit Vec stack to avoid recursion — safe for arbitrarily deep trees.None/Some distinction uniquely serializes a binary tree. Implement both serialize (preorder with None for leaves) and deserialize (consume from iterator).