为什么在Rust中字符串首字母大写如此复杂?

zfycwa2u  于 2022-11-24  发布在  其他
关注(0)|答案(9)|浏览(208)

我想把&str的第一个字母大写。这是一个简单的问题,我希望得到一个简单的解决方案。直觉告诉我这样做:

let mut s = "foobar";
s[0] = s[0].to_uppercase();

但是&str不能这样索引,我唯一能做的就是把&str转换成一个迭代器,再把迭代器转换成一个向量,向量的第一项是大写的,这就创建了一个迭代器,我把它索引进去,创建了一个Option,然后把向量转换成一个迭代器,再把它转换成String,再把它转换成&str

let s1 = "foobar";
let mut v: Vec<char> = s1.chars().collect();
v[0] = v[0].to_uppercase().nth(0).unwrap();
let s2: String = v.into_iter().collect();
let s3 = &s2;

有没有比这更简单的方法,如果有,是什么?如果没有,为什么铁 rust 要这样设计?
类似问题

f45qwnt8

f45qwnt81#

为何如此复杂?
让我们逐行分析

let s1 = "foobar";

我们已经创建了一个以UTF-8编码的字符串。UTF-8允许我们以一种非常紧凑的方式对Unicode的1,114,112个code points进行编码,如果你来自世界上一个主要使用ASCII(1963年创建的标准)字符的地区。UTF-8是一种 * 可变长度 * 编码,这意味着单个码位可能是take from 1 to 4 bytes。较短的编码是为ASCII保留的,但many Kanji take 3 bytes in UTF-8除外。

let mut v: Vec<char> = s1.chars().collect();

这就创建了一个char字符的向量。字符是一个32位数字,直接Map到一个码位。如果我们从纯ASCII文本开始,我们的内存需求就增加了四倍。如果我们有一堆来自the astral plane的字符,那么我们可能还没有使用那么多。

v[0] = v[0].to_uppercase().nth(0).unwrap();

这将获取第一个码位并请求将其转换为大写变体。不幸的是,对于我们这些从小说英语的人来说,这里有not always a simple one-to-one mapping of a "small letter" to a "big letter"。边注:我们称之为大写字母和小写字母是因为在过去,一个字母盒位于另一个字母盒的上方。
当一个代码点没有对应的大写变体时,这段代码会出现混乱。我不确定是否存在。当一个代码点有一个包含多个字符的大写变体时,它也可能在语义上失败,比如德语的ß。注意,ß在真实的世界中可能永远不会大写。这是我能一直记住和搜索的唯一例子。截至2017-06-29,事实上,德语拼写的官方规则已经更新,这样both "ẞ" and "SS" are valid capitalizations

let s2: String = v.into_iter().collect();

这里我们将字符转换回UTF-8,并需要新的分配来存储它们,因为原始变量存储在常量内存中,以便在运行时不占用内存。

let s3 = &s2;

现在我们引用String
这是个简单的问题
不幸的是,这不是真的。也许我们应该奋进把世界转换成Esperanto
我认为char::to_uppercase已经正确处理了Unicode。
是的,我当然希望如此。不幸的是,Unicode并不适用于所有情况。感谢huon指出Turkish I,其中大写(****)和小写(i)版本都有一个点。也就是说,没有一个正确的大写字母i;它还取决于源文本的locale
为什么需要所有数据类型转换?
因为当你担心正确性和性能时,你所使用的数据类型是很重要的。char是32位的,字符串是UTF-8编码的,它们是不同的东西。
索引可以返回多字节Unicode字符
这里可能有一些不匹配的术语。char * 是 * 多字节Unicode字符。
如果逐字节地对字符串进行 * 切片 * 是可能的,但如果不在字符边界上,标准库将出现混乱。
索引字符串以获取字符的方法从未被实现的原因之一是,很多人将字符串误用为ASCII字符数组。索引字符串以 set 字符永远不会是高效的--你必须能够用同样是1-4字节的值来替换1- 4字节,这会导致字符串的其余部分来回跳动。
to_uppercase可以返回大写字符
如上所述,ß是单个字符,当大写时,它变成两个字符

解决方案

另请参阅trentcl's answer,其中只包含大写ASCII字符。
原始
如果我必须写代码,它看起来像:

fn some_kind_of_uppercase_first_letter(s: &str) -> String {
    let mut c = s.chars();
    match c.next() {
        None => String::new(),
        Some(f) => f.to_uppercase().chain(c).collect(),
    }
}

fn main() {
    println!("{}", some_kind_of_uppercase_first_letter("joe"));
    println!("{}", some_kind_of_uppercase_first_letter("jill"));
    println!("{}", some_kind_of_uppercase_first_letter("von Hagen"));
    println!("{}", some_kind_of_uppercase_first_letter("ß"));
}

但我可能会在www.example.com上搜索uppercaseunicodecrates.io,让比我聪明的人来处理。

改善

说到“比我聪明的人”,Veedrac指出,在访问第一个大写代码点之后,将迭代器转换回切片可能更有效,这允许剩余字节的memcpy

fn some_kind_of_uppercase_first_letter(s: &str) -> String {
    let mut c = s.chars();
    match c.next() {
        None => String::new(),
        Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
    }
}
yqyhoc1h

yqyhoc1h2#

有没有比这更简单的方法,如果有,是什么?如果没有,为什么铁 rust 要这样设计?
好吧,是的,也不是。正如另一个答案所指出的,你的代码是不正确的,如果你给予它一些类似于的东西,你会感到恐慌。所以用Rust的标准库做这件事比你最初想象的还要难。
然而,Rust旨在鼓励代码重用,并使引入库变得容易。因此,大写字符串的惯用方法实际上是相当令人满意的:

extern crate inflector;
use inflector::Inflector;

let capitalized = "some string".to_title_case();
gk7wooem

gk7wooem3#

如果您能够将输入限制为仅包含ASCII字符串,那么这并不特别复杂。
从Rust 1.23开始,str有了一个make_ascii_uppercase方法(在旧的Rust版本中,它是通过AsciiExt trait提供的)。这意味着你可以相对容易地将ASCII字符串切片大写:

fn make_ascii_titlecase(s: &mut str) {
    if let Some(r) = s.get_mut(0..1) {
        r.make_ascii_uppercase();
    }
}

这会将"taylor"转换为"Taylor",但不会将"édouard"转换为"Édouard"。(playground
请谨慎使用。

fcwjkofz

fcwjkofz4#

我是这样做的:

fn str_cap(s: &str) -> String {
  format!("{}{}", (&s[..1].to_string()).to_uppercase(), &s[1..])
}

如果不是ASCII字符串:

fn str_cap(s: &str) -> String {
  format!("{}{}", s.chars().next().unwrap().to_uppercase(), 
  s.chars().skip(1).collect::<String>())
}
huus2vyu

huus2vyu5#

观察员办事处进一步采取的办法:

  • 将第一个字符替换为其大写形式 *
let mut s = "foobar".to_string();
let r = s.remove(0).to_uppercase().to_string() + &s;

let r = format!("{}{s}", s.remove(0).to_uppercase());
println!("{r}");

也可以使用Unicode字符,例如"😎foobar"

eqqqjvef

eqqqjvef6#

由于方法to_uppercase()返回一个新字符串,因此您应该能够像这样添加字符串的剩余部分。
这在Rust版本1.57+中进行了测试,但在支持切片的任何版本中都可能工作。

fn uppercase_first_letter(s: &str) -> String {
        s[0..1].to_uppercase() + &s[1..]
    }
ruarlubt

ruarlubt7#

下面这个版本比@Shepmaster的改进版慢一点,但也更符合 * 习惯 *:

fn capitalize_first(s: &str) -> String {
    let mut chars = s.chars();
    chars
        .next()
        .map(|first_letter| first_letter.to_uppercase())
        .into_iter()
        .flatten()
        .chain(chars)
        .collect()
}
pcww981p

pcww981p8#

这就是我解决这个问题的方法,注意我必须在转换为大写之前检查self是否不是ascii。

trait TitleCase {
    fn title(&self) -> String;
}

impl TitleCase for &str {
    fn title(&self) -> String {
        if !self.is_ascii() || self.is_empty() {
            return String::from(*self);
        }
        let (head, tail) = self.split_at(1);
        head.to_uppercase() + tail
    }
}

pub fn main() {
    println!("{}", "bruno".title());
    println!("{}", "b".title());
    println!("{}", "🦀".title());
    println!("{}", "ß".title());
    println!("{}", "".title());
    println!("{}", "བོད་སྐད་ལ".title());
}

输出量

Bruno
B
🦀
ß

བོད་སྐད་ལ
tjjdgumg

tjjdgumg9#

受get_mut示例的启发,我编写了如下代码:

fn make_capital(in_str : &str) -> String {
    let mut v = String::from(in_str);
    v.get_mut(0..1).map(|s| { s.make_ascii_uppercase(); &*s });

    v
}

相关问题