๐Ÿฆ€ Functional Rust

006: Palindrome Check

Difficulty: Beginner Category: Foundation Concept: Check if a string/list reads the same forwards and backwards Key Insight: Rust's `chars().rev()` iterator makes palindrome checking elegant; both languages compare with reversed version.
// 006: Palindrome Check

// Approach 1: Reverse and compare (allocating)
fn is_palindrome_rev(s: &str) -> bool {
    let reversed: String = s.chars().rev().collect();
    s == reversed
}

// Approach 2: Iterator comparison (zero allocation)
fn is_palindrome_iter(s: &str) -> bool {
    s.chars().eq(s.chars().rev())
}

// Approach 3: Case-insensitive, alphanumeric only
fn is_palindrome_clean(s: &str) -> bool {
    let clean: Vec<char> = s
        .chars()
        .filter(|c| c.is_alphanumeric())
        .map(|c| c.to_lowercase().next().unwrap())
        .collect();
    clean.iter().eq(clean.iter().rev())
}

fn main() {
    let words = ["racecar", "hello", "abba", "A man, a plan, a canal: Panama"];
    for w in &words {
        println!("'{}' palindrome? {}", w, is_palindrome_rev(w));
    }
    println!("Clean palindrome: {}", is_palindrome_clean("Race Car"));
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_palindrome_rev() {
        assert!(is_palindrome_rev("racecar"));
        assert!(is_palindrome_rev("abba"));
        assert!(!is_palindrome_rev("hello"));
        assert!(is_palindrome_rev(""));
        assert!(is_palindrome_rev("a"));
    }

    #[test]
    fn test_palindrome_iter() {
        assert!(is_palindrome_iter("racecar"));
        assert!(!is_palindrome_iter("abc"));
    }

    #[test]
    fn test_palindrome_clean() {
        assert!(is_palindrome_clean("A man, a plan, a canal: Panama"));
        assert!(is_palindrome_clean("Race Car"));
        assert!(!is_palindrome_clean("hello world"));
    }
}
(* 006: Palindrome Check *)

(* Approach 1: Reverse and compare *)
let is_palindrome_rev s =
  let chars = List.of_seq (String.to_seq s) in
  chars = List.rev chars

(* Approach 2: Index-based comparison *)
let is_palindrome_idx s =
  let n = String.length s in
  let rec check i =
    if i >= n / 2 then true
    else if s.[i] <> s.[n - 1 - i] then false
    else check (i + 1)
  in
  check 0

(* Approach 3: Case-insensitive, alphanumeric only *)
let is_palindrome_clean s =
  let clean =
    s
    |> String.lowercase_ascii
    |> String.to_seq
    |> Seq.filter (fun c ->
      (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
    |> List.of_seq
  in
  clean = List.rev clean

(* Tests *)
let () =
  assert (is_palindrome_rev "racecar");
  assert (is_palindrome_rev "abba");
  assert (not (is_palindrome_rev "hello"));
  assert (is_palindrome_rev "");
  assert (is_palindrome_rev "a");
  assert (is_palindrome_idx "racecar");
  assert (not (is_palindrome_idx "abc"));
  assert (is_palindrome_clean "A man, a plan, a canal: Panama");
  assert (is_palindrome_clean "Race Car");
  assert (not (is_palindrome_clean "hello world"));
  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Core Insight

A palindrome reads the same forwards and backwards. The simplest check: reverse and compare. Both languages make this a one-liner, but the underlying data structures differ (OCaml string vs Rust UTF-8 String).

OCaml Approach

  • Convert string to char list, reverse, compare
  • Or use index-based comparison from both ends
  • `String.to_seq` + `List.of_seq` for char extraction

Rust Approach

  • `s.chars().rev().collect::<String>() == s`
  • Or `s.chars().eq(s.chars().rev())` โ€” no allocation
  • `.chars()` handles UTF-8 correctly

Comparison Table

FeatureOCamlRust
Reverse string`String.to_seq \> List.of_seq \> List.rev``s.chars().rev()`
Compare`=` structural equality`==` or `.eq()`
UTF-8Byte-based strings`.chars()` for Unicode
Zero-alloc checkIndex loop`.chars().eq(s.chars().rev())`