簡體   English   中英

為什么在 Rust 中將字符串的第一個字母大寫如此復雜?

[英]Why is capitalizing the first letter of a string so convoluted in Rust?

我想將&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 是這樣設計的?

類似的問題

為什么這么糾結?

讓我們逐行分解

let s1 = "foobar";

我們創建了一個以UTF-8編碼的文字字符串。 UTF-8 允許我們以一種非常緊湊的方式對Unicode的 1,114,112 個代碼點進行編碼,如果您來自世界上主要輸入ASCII中的字符的地區,該標准創建於 1963 年。UTF-8 是一個可變長度編碼,這意味着單個代碼點可能需要 1 到 4 個字節 較短的編碼是為 ASCII 保留的,但許多漢字在 UTF-8 中占用 3 個字節

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

這將創建一個矢量char acters。 字符是直接映射到代碼點的 32 位數字。 如果我們從純 ASCII 文本開始,我們的內存需求就翻了兩番。 如果我們有一堆來自星界的角色,那么也許我們還沒有使用更多。

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

這會獲取第一個代碼點並請求將其轉換為大寫變體。 不幸的是,對於我們這些說英語長大的人來說, “小寫字母”到“大寫字母”的映射並不總是簡單的一對一 旁注:我們稱它們為大寫和小寫,因為在當時,一盒字母位於另一盒字母上方

當代碼點沒有相應的大寫變體時,此代碼將發生恐慌。 我不確定這些是否存在,實際上。 當代碼點具有包含多個字符的大寫變體(例如德語ß時,它也可能在語義上失敗。 請注意,ß 可能永遠不會在現實世界中真正大寫,這是我永遠記得和搜索的唯一示例。 截至2017年6月29日,事實上,德語拼寫的官方規則已經這樣更新了兩個“ẞ”和“SS”是有效的市值

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

在這里,我們將字符轉換回 UTF-8 並需要一個新的分配來存儲它們,因為原始變量存儲在常量內存中,以便在運行時不占用內存。

let s3 = &s2;

現在我們引用那個String

這是一個簡單的問題

不幸的是,事實並非如此。 也許我們應該努力將世界轉換為世界語

我認為char::to_uppercase已經正確處理了 Unicode。

是的,我當然希望如此。 不幸的是,Unicode 在所有情況下都不夠。 由於胡恩您指出土耳其我,其中兩個上(I)和小寫字母(I)的版本有一個點。 也就是說,有信無一倍正確的資本i ; 它也取決於源文本的語言環境

為什么需要所有數據類型轉換?

因為當您擔心正確性和性能時,您正在使用的數據類型很重要。 一個char是 32 位的,一個字符串是 UTF-8 編碼的。 它們是不同的東西。

索引可以返回一個多字節的 Unicode 字符

這里可能有一些不匹配的術語。 char多字節 Unicode 字符。

如果您逐字節進行切片,則可以對字符串進行切片,但如果您不在字符邊界上,則標准庫會發生混亂。

從未實現對字符串進行索引以獲取字符的原因之一是因為很多人將字符串誤用為 ASCII 字符數組。 索引字符串以設置字符永遠不會有效 - 您必須能夠將 1-4 個字節替換為也是 1-4 個字節的值,從而導致字符串的其余部分反彈很多。

to_uppercase可以返回一個大寫字符

如上所述, ß是單個字符,大寫時會變成兩個字符

解決方案

另請參閱trentcl 的答案,該答案僅使用大寫 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("ß"));
}

但我可能會在 crates.io 上搜索大寫unicode ,然后讓比我更聰明的人來處理它。

改進

談到“比我更聰明的人”, 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(),
    }
}

有沒有比這更簡單的方法,如果有,那又怎樣? 如果不是,為什么 Rust 是這樣設計的?

嗯,是和不是。 正如另一個答案所指出的那樣,您的代碼是不正確的,如果您給它類似 བོད་སྐད་ལ་ 之類的東西,它會感到恐慌。 所以用 Rust 的標准庫做這件事比你最初想象的要難。

然而,Rust 旨在鼓勵代碼重用並使引入庫變得容易。 因此,將字符串大寫的慣用方式實際上非常可口:

extern crate inflector;
use inflector::Inflector;

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

如果您能夠將輸入限制為僅限 ASCII 的字符串,這並不是特別復雜。

從 Rust 1.23 開始, str有一個make_ascii_uppercase方法(在舊的 Rust 版本中,它可以通過AsciiExt特性獲得)。 這意味着您可以相對輕松地將純 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" 操場

謹慎使用。

我是這樣做的:

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

我同意這個問題的家伙。 所以,我用自己的方式做到了:

fn capitalize(word: &str) -> String {
    let mut output = String::with_capacity(word.len());
    let (first, last) = word.split_at(1);
    let first_letter = format!("{}", first.to_uppercase());
    output.push_str(first_letter.as_str());
    output.push_str(last);
    output
}

fn main() {
    let input = "end";
    let ret = capitalize(input);
    println!("{} -> {}", input, ret);
}

這是一個比@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()
}

這就是我解決這個問題的方法,注意在轉換為大寫之前我必須檢查 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
🦀
ß

བོད་སྐད་ལ 

OP的方法進一步采取:
用大寫表示替換第一個字符

let mut s = "foobar".to_string();
for i in 1..4 {
    if s.is_char_boundary(i) {
        let u = &s[0..i].to_uppercase();
        s.replace_range(..i, u);
        break;
    }
}
println!("{}", s);

不需要檢查字符串s是否為空,因為如果索引i大於s.len()is_char_boundary不會恐慌。

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
}

由於to_uppercase()方法返回一個新字符串,您應該能夠像這樣添加字符串的其余部分。

這在 rust 版本 1.57+ 中進行了測試,但很可能在任何支持 slice 的版本中工作。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM