017 — Split a List at Position
Tutorial Video
Text description (accessibility)
This video demonstrates the "017 — Split a List at Position" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Splitting a list at position n (OCaml 99 Problems #17) — producing `(list[..n], list[n..])` — is the fundamental decomposition operation. Key difference from OCaml: 1. **O(1) vs O(n)**: Rust's `slice.split_at(n)` is O(1) — it just computes two pointer/length pairs without copying. OCaml's list split is always O(n) because linked lists require traversal.
Tutorial
The Problem
Splitting a list at position n (OCaml 99 Problems #17) — producing (list[..n], list[n..]) — is the fundamental decomposition operation. It is the inverse of concatenation and the basis for divide-and-conquer algorithms like merge sort, binary search, and quickselect. Every partitioning problem reduces to some variant of split.
Rust's slice operations make this trivial via v.split_at(n), but understanding the recursive construction — peel elements from the front until the counter reaches zero — builds intuition for structural recursion on sequences. This pattern appears in parser combinators (split remaining input after consuming n tokens) and stream processing.
🎯 Learning Outcomes
v.split_at(n) for efficient O(1) splitting of slices(Vec<T>, Vec<T>) as the two halvesv.split_at(n.min(v.len())) to avoid panics when n exceeds the slice lengthsplit_at (zero allocation, O(1)) and value-returning take/skip (O(n))Code Example
#![allow(clippy::all)]
// Placeholder — pending conversionKey Differences
slice.split_at(n) is O(1) — it just computes two pointer/length pairs without copying. OCaml's list split is always O(n) because linked lists require traversal.&[T], &[T]). Splitting an owned Vec into two owned Vecs requires cloning or draining.n.min(v.len()) for safe splitting. OCaml returns (List.rev acc, []) when the list runs out — silently handling the case.Vec::split_off(n) mutates the original Vec (left half stays, right half is returned). OCaml's split always produces new lists.slice.split_at(n) is O(1) — it just creates two references into the same memory. OCaml's recursive version is O(n) because it must traverse the linked list.split_at panics if n > len. Use n.min(v.len()) to clamp. OCaml's version returns an empty right side when n exceeds the list length.split_at returns two &[T] slices — zero allocation, zero copying. Both point into the original data. OCaml's split builds two new lists.OCaml Approach
OCaml's version: let split lst n = let rec aux acc n = function | [], _ -> (List.rev acc, []) | rest, 0 -> (List.rev acc, rest) | x :: t, n -> aux (x :: acc) (n - 1) (t, n - 1) in aux [] n (lst, n). The counter decrements with each element consumed. When it reaches zero or the list is empty, the accumulator becomes the left half and the remainder is the right half.
OCaml's split_at n list uses a recursive helper with a counter: let rec split_at_aux acc n = function | [] -> (List.rev acc, []) | h :: t when n = 0 -> (List.rev acc, h :: t) | h :: t -> split_at_aux (h :: acc) (n-1) t. The accumulator must be reversed at the end. OCaml has no built-in equivalent to Rust's split_at.
Full Source
#![allow(clippy::all)]
// Placeholder — pending conversionDeep Comparison
OCaml vs Rust: Split List
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
split_while(list: &[i32], pred: impl Fn(&i32) -> bool) -> (&[i32], &[i32]) that splits at the first element that does not satisfy pred. This is OCaml's List.partition / Haskell's span.chunks(list: &[i32], n: usize) -> Vec<Vec<i32>> that splits the list into chunks of size n (with the last chunk potentially smaller). Use v.chunks(n).map(|c| c.to_vec()).collect().Vec<i32> from a split+sort, write merge(left: &[i32], right: &[i32]) -> Vec<i32> that produces a single sorted result — completing the merge sort algorithm.split_when<T, F: Fn(&T) -> bool>(list: &[T], pred: F) -> (&[T], &[T]) that splits at the first element satisfying the predicate — like OCaml's List.split_while.chunks(list: &[T], size: usize) -> Vec<&[T]> that splits a list into chunks of size (last chunk may be smaller). Use slice.chunks(n) as the standard library equivalent.