016 — Drop Every Nth Element
Tutorial Video
Text description (accessibility)
This video demonstrates the "016 — Drop Every Nth Element" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Dropping every nth element from a list (OCaml 99 Problems #16) — keeping all elements except those at positions 0, n-1, 2n-1, ... Key difference from OCaml: 1. **`enumerate` vs counter**: Rust's `.enumerate()` is the standard way to add position information. OCaml threads a counter through recursive calls as a function argument.
Tutorial
The Problem
Dropping every nth element from a list (OCaml 99 Problems #16) — keeping all elements except those at positions 0, n-1, 2n-1, ... (1-indexed: drop positions n, 2n, 3n, ...) — is a simple but instructive filtering problem. It requires maintaining a counter alongside the data, which is the job of enumerate.
This pattern appears in subsampling (audio downsampling, video frame dropping), RAID stripe removal, round-robin scheduling (skip every nth slot), and data thinning. The enumerate().filter().map() chain is idiomatic Rust for position-based filtering.
Dropping every nth element is a subsampling operation: keep 2 out of every 3 elements, or keep all but every 4th. This appears in audio downsampling (drop every 2nd sample to halve the sample rate), video frame dropping (drop every 3rd frame to reduce file size), and round-robin load balancing (skip every nth server for maintenance). The enumerate() + filter() idiom is the general pattern for position-based filtering.
🎯 Learning Outcomes
.enumerate() to pair each element with its 1-based index(index + 1) % n != 0(i + 1) % n != 0 keeps elements at positions that are not multiples of n in 1-based countingn=1 (drops all elements) and n=0 (undefined — check for this)Code Example
#![allow(clippy::all)]
// Placeholder — pending conversionKey Differences
enumerate vs counter**: Rust's .enumerate() is the standard way to add position information. OCaml threads a counter through recursive calls as a function argument.filter is declarative — express the condition for keeping elements. OCaml's recursive approach is imperative in spirit (check counter, decide, recurse).List.rev at the end.enumerate() idiom:** Rust's .enumerate() adds a 0-based index to each element. Adjusting to 1-based: (i + 1) % n != 0. OCaml passes the counter as a recursive argument..filter() as a method on iterators. OCaml uses a when guard inside pattern matching. Semantically identical; syntactically different.OCaml Approach
OCaml's version: let drop lst n = let rec aux acc count = function | [] -> List.rev acc | x :: t -> if count = n then aux acc 1 t else aux (x :: acc) (count + 1) t in aux [] 1 lst. The counter starts at 1; when it reaches n, the element is skipped and the counter resets to 1. This is the idiomatic recursive accumulator pattern.
OCaml's version uses a counter accumulator: let rec drop_rec list n current = match list with | [] -> [] | _ :: t when current mod n = 0 -> drop_rec t n (current + 1) | h :: t -> h :: drop_rec t n (current + 1). The when guard filters at multiples of n. The counter starts at 1 for 1-based indexing matching OCaml 99 conventions.
Full Source
#![allow(clippy::all)]
// Placeholder — pending conversionDeep Comparison
OCaml vs Rust: Drop Every Nth
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
keep_every_nth(list: &[i32], n: usize) -> Vec<i32> that keeps only elements at positions n, 2n, 3n, ... This is useful for downsampling.drop_range(list: &[i32], start: usize, end: usize) -> Vec<i32> that removes elements from index start to end inclusive.Stride<I> iterator adapter that yields every nth element of the underlying iterator without collecting to a Vec.keep_every_nth(list: &[T], n: usize) -> Vec<T> that keeps ONLY the elements at positions n, 2n, 3n, ... — the complement of drop-every-nth.stride(list: &[T], start: usize, step: usize) -> Vec<T> that keeps elements at indices start, start+step, start+2*step, etc. — generalizing both keep-every-nth and drop-every-nth.