929-palindrome-check — Palindrome Check
Tutorial
The Problem
A palindrome reads the same forwards and backwards: "racecar", [1,2,1], "madam". Palindrome checking exercises iterator-based bidirectional comparison, a fundamental algorithm pattern. The naive approach reverses the sequence and compares — O(n) time and O(n) space. The efficient approach compares from both ends using a double-ended iterator — O(n) time and O(1) space. This algorithm appears in DNA sequence analysis (palindromic restriction enzyme sites), string processing, and as a classic interview problem. Rust's .eq(iter.rev()) idiom makes it a clean one-liner.
🎯 Learning Outcomes
.iter().eq(slice.iter().rev()) for a clean one-liner palindrome check&[i32] and &str character sliceslist = List.rev list approachCode Example
pub fn is_palindrome<T: PartialEq>(list: &[T]) -> bool {
let n = list.len();
(0..n / 2).all(|i| list[i] == list[n - 1 - i])
}Key Differences
list.iter().eq(list.iter().rev()) is elegant and generic; OCaml xs = List.rev xs is equally elegant but allocates a reversed copy.<T: PartialEq> works for any type; OCaml's structural = also works for any type but may be slower for complex types..eq() short-circuits on first mismatch; OCaml's list equality also short-circuits.OCaml Approach
OCaml: let is_palindrome xs = xs = List.rev xs — reverse and compare using structural equality. This creates a new reversed list (O(n) allocation) then compares. More efficient: pattern matching from both ends is awkward with singly-linked lists — it requires converting to an array. let is_palindrome_arr xs = let arr = Array.of_list xs in let n = Array.length arr in let rec go i j = i >= j || arr.(i) = arr.(j) && go (i+1) (j-1) in go 0 (n-1).
Full Source
#![allow(clippy::all)]
// Palindrome Check
// Rust translation from OCaml 99 Problems #6
// Idiomatic Rust
fn is_palindrome<T: PartialEq>(list: &[T]) -> bool {
list.iter().eq(list.iter().rev())
}
// Alternative: manual comparison
fn is_palindrome_manual<T: PartialEq + Clone>(list: &[T]) -> bool {
let reversed: Vec<_> = list.iter().rev().cloned().collect();
list == reversed.as_slice()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_palindrome() {
assert_eq!(is_palindrome(&[1, 2, 3, 2, 1]), true);
assert_eq!(is_palindrome(&[1, 2, 3, 4]), false);
assert_eq!(is_palindrome::<i32>(&[]), true);
assert_eq!(is_palindrome(&[1]), true);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_palindrome() {
assert_eq!(is_palindrome(&[1, 2, 3, 2, 1]), true);
assert_eq!(is_palindrome(&[1, 2, 3, 4]), false);
assert_eq!(is_palindrome::<i32>(&[]), true);
assert_eq!(is_palindrome(&[1]), true);
}
}
Deep Comparison
Comparison: Palindrome Check
OCaml — Using List.rev
let is_palindrome lst =
lst = List.rev lst
Rust — Idiomatic (index-based, zero allocation)
pub fn is_palindrome<T: PartialEq>(list: &[T]) -> bool {
let n = list.len();
(0..n / 2).all(|i| list[i] == list[n - 1 - i])
}
Rust — Functional (iterator-based)
pub fn is_palindrome_iter<T: PartialEq>(list: &[T]) -> bool {
list.iter().eq(list.iter().rev())
}
Comparison Table
| Aspect | OCaml | Rust |
|---|---|---|
| Input type | 'a list (linked list) | &[T] (slice reference) |
| Equality | Structural = (polymorphic) | PartialEq trait bound |
| Reversal | List.rev → new list (O(n) alloc) | iter().rev() → zero alloc |
| Access pattern | Sequential only | Random access O(1) |
| Memory | GC-managed | Borrowed reference |
Type Signatures
val is_palindrome : 'a list -> boolfn is_palindrome<T: PartialEq>(list: &[T]) -> boolTakeaways
DoubleEndedIterator eliminates the need for explicit list reversalPartialEqiter().eq(iter().rev())) is the closest Rust analog to OCaml's styleExercises
is_palindrome_str(s: &str) -> bool that handles Unicode correctly (comparing grapheme clusters, not bytes).longest_palindromic_substring(s: &str) -> &str that finds the longest palindromic substring.make_palindrome<T: Clone>(prefix: &[T]) -> Vec<T> that appends the reverse of the prefix to make a palindrome.