如何在忽略大小写的情况下高效比较字符串?

7

要比较两个字符串(不区分大小写),似乎需要先将字符串转换为小写版本:

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

有没有一种方法可以比较字符串而忽略大小写,且不需要创建临时的小写字符串呢?即通过迭代字符,在那里执行转换为小写并比较结果的方法?


3个回答

6

如果您只使用ASCII字符,可以使用eq_ignore_ascii_case

assert!("Ferris".eq_ignore_ascii_case("FERRIS"));

如果涉及到字符串,例如来自用户输入或文件名,这并没有什么帮助。字符串有多少次只存在于应用程序中,并完全在程序员的控制之下? - Thomas S.
@ThomasS。没错,但这仍然是一个有效的答案,对某些人可能有用。 - Ibraheem Ahmed
2
这正是我所需要的。我正在将一组常量ASCII字符串与用户输入进行比较,因此如果不是ASCII,则无论如何都不会匹配。 - Steven Turner

4

虽然没有内置的方法,但你可以编写一个方法来实现你所描述的功能,假设你只关心ASCII输入。

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)
}

正如下面的一些评论所指出的,对于UTF-8编码,忽略大小写的比较在未对整个字符串进行操作时无法正常工作,即使对整个字符串进行操作,某些情况下也存在多个大小写转换的表示形式,可能会产生意外的结果。

考虑到这些警告,以下代码可处理许多额外情况(例如大多数带重音符号的拉丁字符),并根据您的需求可能是令人满意的。

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)
}

3
дҪҝз”Ёstr::charsзҡ„д»»дҪ•ж–№жі•йғҪж— жі•жӯЈзЎ®жҜ”иҫғUnicodeеӯ—з¬ҰдёІгҖӮ - mcarton
为了补充我相信mcarton所说的:str::chars 迭代码点,但是因为预组合,有可能存在规范等效但在技术层面有不同内容的字符串。chars不会考虑到这些信息。另一个大问题是大小写转换是与语言环境相关的,例如I的小写形式是i... 除非你使用土耳其语,那么它的小写形式就是 ı。我相信在那里还有其他陷阱。 - Masklinn
我不是在尝试比较相等性,而是比较 <、== 或 >。顺便问一下,string.to_lowercase() 是否考虑了语言环境,以便一个字符可以变成多个字符? - Thomas S.
2
假设你只关心ASCII输入,这在今天是非常糟糕的做法。 - Jack Aidley

1

UNICODE

支持UNICODE的最佳方式是使用to_lowercase()to_uppercase()

这是因为UNICODE有许多注意事项,而这些函数可以处理大多数情况。但也有一些特定于语言环境的字符串无法正确处理。

let left = "first".to_string();
let right = "FiRsT".to_string();

assert!(left.to_lowercase() == right.to_lowercase());

效率

可以迭代并返回第一个不相等的字符,因此实际上您只分配了一个字符。但是,使用chars函数进行迭代不能处理UNICODE可能会遇到的所有情况。

有关详细信息,请参见Peter Hallanswer

ASCII

如果仅使用ASCII,则最有效的方法是使用eq_ignore_ascii_case(根据Ibraheem Ahmedanswer)。这不会分配/复制临时文件。

只有在您的代码控制至少一侧比较并且您确定它仅包含ASCII时,才能使用此方法。

assert!("Ferris".eq_ignore_ascii_case("FERRIS"));

本地化

Rust的大小写转换函数在处理区域设置方面尽力而为,但并不支持所有区域设置。为了实现适当的国际化支持,您需要寻找其他能够完成此任务的crate。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接