915-iterator-min-by-max-by — Custom Comparison min_by / max_by
Tutorial
The Problem
Standard min() and max() require Ord — total ordering — which excludes f64 (NaN breaks total order) and types requiring custom comparison semantics. min_by(cmp) and max_by(cmp) accept an explicit Fn(&A, &A) -> Ordering comparator, enabling: comparing floats with partial_cmp().unwrap_or(Equal), multi-key comparison with .then_with(|| a.secondary.cmp(&b.secondary)), reverse ordering by swapping arguments, and domain-specific orderings like "closest to zero." This is the Rust equivalent of OCaml's List.min_elt ~compare and Python's min(key=...).
🎯 Learning Outcomes
.min_by(cmp) and .max_by(cmp) with a custom Ordering comparatorf64 comparison using partial_cmp().unwrap_or(Ordering::Equal).then_with()List.min_elt ~compare and sort comparatorsCode Example
#![allow(clippy::all)]
//! 276. Custom comparison min_by() and max_by()
//!
//! `min_by(cmp)` and `max_by(cmp)` take a `Fn(&A, &A) -> Ordering` comparator.
use std::cmp::Ordering;
#[cfg(test)]
mod tests {
use std::cmp::Ordering;
#[test]
fn test_min_by_float() {
let floats = [3.0f64, 1.0, 2.0];
let min = floats
.iter()
.copied()
.min_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
assert_eq!(min, Some(1.0));
}
#[test]
fn test_max_by_reversed() {
let nums = [1i32, 5, 3, 2, 4];
let max = nums.iter().min_by(|a, b| b.cmp(a));
assert_eq!(max, Some(&5));
}
#[test]
fn test_min_by_multi_key() {
let words = ["bb", "aa", "c"];
let min = words
.iter()
.min_by(|a, b| a.len().cmp(&b.len()).then_with(|| a.cmp(b)));
assert_eq!(min, Some(&"c"));
}
}Key Differences
Ordering is an enum (Less, Equal, Greater); OCaml's comparator returns int (negative, zero, positive).partial_cmp().unwrap_or(); OCaml's Float.compare defines NaN ordering (NaN < everything)..then_with() on Ordering makes multi-key comparison ergonomic; OCaml chains if c <> 0 then c else ....min_by and max_by are standard in Rust; OCaml's standard library requires Base or manual implementation.OCaml Approach
List.min_elt ~compare: ('a -> 'a -> int) -> 'a list -> 'a option (Base library). Standard OCaml requires manual fold_left: List.fold_left (fun acc x -> if compare x acc <= 0 then x else acc). Multi-key: let cmp a b = let c = String.length a - String.length b in if c <> 0 then c else String.compare a b. Float comparison: Float.compare handles NaN consistently. OCaml's compare returns int, Rust's Ordering is a typed enum — both provide equivalent expressiveness.
Full Source
#![allow(clippy::all)]
//! 276. Custom comparison min_by() and max_by()
//!
//! `min_by(cmp)` and `max_by(cmp)` take a `Fn(&A, &A) -> Ordering` comparator.
use std::cmp::Ordering;
#[cfg(test)]
mod tests {
use std::cmp::Ordering;
#[test]
fn test_min_by_float() {
let floats = [3.0f64, 1.0, 2.0];
let min = floats
.iter()
.copied()
.min_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
assert_eq!(min, Some(1.0));
}
#[test]
fn test_max_by_reversed() {
let nums = [1i32, 5, 3, 2, 4];
let max = nums.iter().min_by(|a, b| b.cmp(a));
assert_eq!(max, Some(&5));
}
#[test]
fn test_min_by_multi_key() {
let words = ["bb", "aa", "c"];
let min = words
.iter()
.min_by(|a, b| a.len().cmp(&b.len()).then_with(|| a.cmp(b)));
assert_eq!(min, Some(&"c"));
}
}#[cfg(test)]
mod tests {
use std::cmp::Ordering;
#[test]
fn test_min_by_float() {
let floats = [3.0f64, 1.0, 2.0];
let min = floats
.iter()
.copied()
.min_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
assert_eq!(min, Some(1.0));
}
#[test]
fn test_max_by_reversed() {
let nums = [1i32, 5, 3, 2, 4];
let max = nums.iter().min_by(|a, b| b.cmp(a));
assert_eq!(max, Some(&5));
}
#[test]
fn test_min_by_multi_key() {
let words = ["bb", "aa", "c"];
let min = words
.iter()
.min_by(|a, b| a.len().cmp(&b.len()).then_with(|| a.cmp(b)));
assert_eq!(min, Some(&"c"));
}
}
Exercises
max_by(|a, b| b.cmp(a)).closest_to_zero(data: &[f64]) -> Option<f64> using min_by(|a, b| a.abs().partial_cmp(&b.abs()).unwrap()).struct Event { timestamp: u64, priority: u8 } that sorts by priority descending, then timestamp ascending, using .then_with().