Search code examples
stringrustcase-insensitive

What is an efficient way to compare strings while ignoring case?


To compare two Strings, ignoring case, it looks like I first need to convert to a lower case version of the string:

let a_lower = a.to_lowercase();
let b_lower = b.to_lowercase();
a_lower.cmp(&b_lower)

Is there a method that compares strings, ignoring case, without creating the temporary lower case strings, i.e. that iterates over the characters, performs the to-lowercase conversion there and compares the result?


Solution

  • There is no built-in method, but you can write one to do exactly as you described, assuming you only care about ASCII input.

    use itertools::{EitherOrBoth::*, Itertools as _}; // 0.9.0
    use std::cmp::Ordering;
    
    fn cmp_ignore_case_ascii(a: &str, b: &str) -> Ordering {
        a.bytes()
            .zip_longest(b.bytes())
            .map(|ab| match ab {
                Left(_) => Ordering::Greater,
                Right(_) => Ordering::Less,
                Both(a, b) => a.to_ascii_lowercase().cmp(&b.to_ascii_lowercase()),
            })
            .find(|&ordering| ordering != Ordering::Equal)
            .unwrap_or(Ordering::Equal)
    }
    

    As some comments below have pointed out, case-insensitive comparison is not going to work properly for UTF-8, without operating on the whole string, and even then there are multiple representations of some case conversions, which could give unexpected results.

    With those caveats, the following will work for a lot of extra cases compared with the ASCII version above (e.g. most accented Latin characters) and may be satisfactory, depending on your requirements:

    fn cmp_ignore_case_utf8(a: &str, b: &str) -> Ordering {
        a.chars()
            .flat_map(char::to_lowercase)
            .zip_longest(b.chars().flat_map(char::to_lowercase))
            .map(|ab| match ab {
                Left(_) => Ordering::Greater,
                Right(_) => Ordering::Less,
                Both(a, b) => a.cmp(&b),
            })
            .find(|&ordering| ordering != Ordering::Equal)
            .unwrap_or(Ordering::Equal)
    }