Tại sao viết hoa chữ cái đầu tiên của một chuỗi quá phức tạp trong Rust?


81

Tôi muốn viết hoa chữ cái đầu tiên của a &str. Đó là một vấn đề đơn giản và tôi hy vọng có một giải pháp đơn giản. Trực giác mách bảo tôi làm điều gì đó như sau:

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

Nhưng &strs không thể được lập chỉ mục như thế này. Cách duy nhất tôi có thể làm có vẻ quá phức tạp. Tôi chuyển đổi &strthành một trình vòng lặp, chuyển đổi trình vòng lặp thành một vectơ, viết hoa cho mục đầu tiên trong vectơ, mục này tạo ra một trình vòng lặp, mà tôi lập chỉ mục, tạo một ký tự Optionmà tôi đã mở để cung cấp cho tôi chữ cái đầu tiên viết hoa. Sau đó, tôi chuyển đổi vectơ thành một trình vòng lặp, mà tôi chuyển đổi thành a String, tôi chuyển đổi thành a &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;

Có cách nào dễ dàng hơn cách này không, và nếu có thì sao? Nếu không, tại sao Rust lại được thiết kế theo cách này?

Câu hỏi tương tự


44
Đó là một vấn đề đơn giản - không, không phải. Vui lòng viết hoa ßkhi được hiểu là tiếng Đức. Gợi ý: nó không phải là một ký tự đơn lẻ. Ngay cả câu lệnh vấn đề cũng có thể phức tạp. Ví dụ, sẽ không đúng nếu viết hoa ký tự đầu tiên của họ von Hagen. Đây là tất cả khía cạnh của cuộc sống trong một thế giới toàn cầu đã có hàng nghìn năm văn hóa khác nhau với các tập tục khác nhau và chúng tôi đang cố gắng thu gọn tất cả những thứ đó thành 8 bit và 2 dòng mã.
Shepmaster

3
Những gì bạn đặt ra dường như là vấn đề mã hóa ký tự, không phải vấn đề về kiểu dữ liệu. Tôi cho rằng char :: to_uppercase đã xử lý đúng Unicode. Câu hỏi của tôi là, tại sao lại cần tất cả các chuyển đổi kiểu dữ liệu? Có vẻ như việc lập chỉ mục có thể trả về một ký tự Unicode, nhiều byte (không phải một ký tự byte đơn lẻ, sẽ chỉ giả sử là ascii) và to_uppercase có thể trả về một ký tự viết hoa bằng bất kỳ ngôn ngữ nào, nếu một ký tự có sẵn trong ngôn ngữ đã nói.
marshallm,

3
@marshallm char::to_uppercasethực sự xử lý vấn đề này, nhưng bạn vứt bỏ những nỗ lực của mình bằng cách chỉ lấy điểm mã đầu tiên ( nth(0)) thay vì tất cả các điểm mã mà tạo nên giá trị vốn hóa

Mã hóa ký tự không phải là một quá trình đơn giản như đã chỉ ra bởi Joel trên Phần mềm: Unicode .
Nathan

@Shepmaster, nói chung là bạn nói đúng. Đó là một vấn đề đơn giản bằng tiếng Anh (cơ sở tiêu chuẩn thực tế của ngôn ngữ lập trình và định dạng dữ liệu). Có, có những tập lệnh mà "viết hoa" thậm chí không phải là một khái niệm, và những tập lệnh khác lại rất phức tạp.
Paul Draper

Câu trả lời:


98

Tại sao nó lại phức tạp như vậy?

Hãy chia nhỏ nó, từng dòng một

let s1 = "foobar";

Chúng tôi đã tạo một chuỗi ký tự được mã hóa bằng UTF-8 . UTF-8 cho phép chúng tôi mã hóa 1.114.112 điểm mã của Unicode theo cách khá nhỏ gọn nếu bạn đến từ một khu vực trên thế giới nhập hầu hết các ký tự được tìm thấy trong ASCII , một tiêu chuẩn được tạo ra vào năm 1963. UTF-8 là một độ dài thay đổi mã hóa, có nghĩa là một điểm mã có thể chiếm từ 1 đến 4 byte . Các mã hóa ngắn hơn được dành riêng cho ASCII, nhưng nhiều chữ Kanji chiếm 3 byte trong UTF-8 .

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

Điều này tạo ra một vector của bộ truyền động char. Một ký tự là một số 32 bit ánh xạ trực tiếp đến một điểm mã. Nếu chúng tôi bắt đầu với văn bản chỉ ASCII, chúng tôi đã tăng gấp bốn lần yêu cầu bộ nhớ của mình. Nếu chúng ta có một loạt các nhân vật từ cõi không gian , thì có lẽ chúng ta đã không sử dụng nhiều hơn thế nữa.

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

Thao tác này lấy điểm mã đầu tiên và yêu cầu nó được chuyển đổi thành một biến thể viết hoa. Thật không may cho những người trong chúng ta, những người lớn lên nói tiếng Anh, không phải lúc nào cũng có một ánh xạ đơn giản giữa "chữ cái nhỏ" với "chữ cái lớn" . Lưu ý bên lề: chúng ta gọi chúng là chữ hoa và chữ thường bởi vì một hộp chữ cái nằm trên hộp chữ cái kia vào ngày xưa .

Mã này sẽ hoảng loạn khi một điểm mã không có biến thể viết hoa tương ứng. Tôi không chắc liệu chúng có tồn tại hay không. Nó cũng có thể không thành công về mặt ngữ nghĩa khi một điểm mã có biến thể viết hoa có nhiều ký tự, chẳng hạn như tiếng Đức ß. Lưu ý rằng ß có thể không bao giờ thực sự được viết hoa trong Thế giới thực, đây là ví dụ duy nhất mà tôi luôn có thể nhớ và tìm kiếm. Kể từ ngày 26 tháng 6 năm 2017, trên thực tế, các quy tắc chính thức của chính tả tiếng Đức đã được cập nhật để cả "ẞ" và "SS" đều là cách viết hoa hợp lệ !

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

Ở đây chúng tôi chuyển đổi các ký tự trở lại UTF-8 và yêu cầu một cấp phát mới để lưu trữ chúng, vì biến ban đầu được lưu trữ trong bộ nhớ không đổi để không chiếm bộ nhớ trong thời gian chạy.

let s3 = &s2;

Và bây giờ chúng ta tham khảo điều đó String.

Đó là một vấn đề đơn giản

Thật không may, điều này là không đúng sự thật. Có lẽ chúng ta nên cố gắng chuyển đổi thế giới sang Esperanto ?

Tôi cho rằng char::to_uppercaseđã xử lý đúng Unicode.

Vâng, tôi chắc chắn hy vọng như vậy. Thật không may, Unicode không đủ trong mọi trường hợp. Cảm ơn huon đã chỉ ra chữ I của Thổ Nhĩ Kỳ , trong đó cả phiên bản viết hoa ( İ ) và viết thường ( i ) đều có dấu chấm. Đó là, không có một cách viết hoa thích hợp của chữ cái i; nó cũng phụ thuộc vào ngôn ngữ của văn bản nguồn.

tại sao lại cần tất cả các chuyển đổi kiểu dữ liệu?

Bởi vì các loại dữ liệu bạn đang làm việc rất quan trọng khi bạn lo lắng về tính đúng đắn và hiệu suất. A charlà 32 bit và một chuỗi được mã hóa UTF-8. Chúng là những thứ khác nhau.

lập chỉ mục có thể trả về một ký tự Unicode, nhiều byte

Có thể có một số thuật ngữ không khớp ở đây. A char một ký tự Unicode nhiều byte.

Có thể cắt một chuỗi nếu bạn chuyển từng byte, nhưng thư viện tiêu chuẩn sẽ hoảng sợ nếu bạn không ở trên ranh giới ký tự.

Một trong những lý do khiến việc lập chỉ mục một chuỗi để lấy một ký tự không bao giờ được thực hiện là vì có quá nhiều người sử dụng sai chuỗi làm mảng ký tự ASCII. Lập chỉ mục một chuỗi để đặt một ký tự không bao giờ có thể hiệu quả - bạn phải có thể thay thế 1-4 byte bằng một giá trị cũng là 1-4 byte, khiến phần còn lại của chuỗi bị trả lại khá nhiều.

to_uppercase có thể trả về một ký tự chữ hoa

Như đã nói ở trên, ßlà một ký tự đơn lẻ mà khi viết hoa sẽ trở thành hai ký tự .

Các giải pháp

Xem thêm câu trả lời của trentcl chỉ viết hoa các ký tự ASCII.

Nguyên

Nếu tôi phải viết mã, nó sẽ giống như sau:

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("ß"));
}

Nhưng có lẽ tôi sẽ tìm kiếm chữ hoa hoặc mã unicode trên crates.io và để ai đó thông minh hơn tôi xử lý.

Cải tiến

Nói về "ai đó thông minh hơn tôi", Veedrac chỉ ra rằng có thể hiệu quả hơn khi chuyển đổi trình lặp lại thành một lát sau khi các điểm mã vốn đầu tiên được truy cập. Điều này cho phép memcpyphần còn lại của các byte.

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

34
Sau khi suy nghĩ về nó rất nhiều, tôi hiểu rõ hơn những lựa chọn thiết kế này. Thư viện tiêu chuẩn nên chọn sự cân bằng linh hoạt, hiệu quả và an toàn nhất có thể. Nếu không, nó buộc các nhà phát triển phải đánh đổi những thứ có thể không phù hợp với ứng dụng, kiến ​​trúc hoặc ngôn ngữ của họ. Hoặc nó có thể dẫn đến sự mơ hồ và hiểu lầm. Nếu tôi thích những sự đánh đổi khác, tôi có thể chọn thư viện của bên thứ 3 hoặc tự viết.
marshallm,

13
@marshallm thật tuyệt khi nghe! Tôi sợ rằng nhiều người mới làm quen với Rust hiểu nhầm những quyết định mà các nhà thiết kế của Rust đã đưa ra và chỉ đơn giản là viết chúng ra là quá phức tạp không mang lại lợi ích gì. Bằng cách đặt và trả lời các câu hỏi ở đây, tôi đã nhận được sự đánh giá cao đối với sự chăm sóc cần thiết cho các thiết kế như vậy và hy vọng trở thành một lập trình viên giỏi hơn. Giữ một tâm trí cởi mở và sẵn sàng học hỏi thêm là một đặc điểm tuyệt vời cần có với tư cách là một lập trình viên.
Shepmaster

6
Các "i Thổ Nhĩ Kỳ" là một ví dụ về sự phụ thuộc miền địa phương đó là liên quan trực tiếp hơn cho câu hỏi này đặc biệt hơn so với phân loại.
huon

6
Tôi ngạc nhiên khi họ có to_uppercase và to_lowercase nhưng không phải to_titlecase. IIRC, một số ký tự unicode thực sự có một biến thể viết hoa tiêu đề đặc biệt.
Tim,

6
Nhân tiện, ngay cả một điểm mã duy nhất có thể không phải là đơn vị phù hợp để chuyển đổi. Điều gì sẽ xảy ra nếu ký tự đầu tiên là một cụm grapheme sẽ được xử lý đặc biệt khi viết hoa? (Nó như vậy sẽ xảy ra rằng umlauts bị phân hủy làm việc nếu bạn chỉ cần chữ hoa nhân vật cơ bản, nhưng tôi không biết nếu đó là phổ biến thành sự thật.)
Sebastian Redl

21

Có cách nào dễ dàng hơn cách này không, và nếu có thì sao? Nếu không, tại sao Rust lại được thiết kế theo cách này?

Vâng, có và không. Mã của bạn, như câu trả lời khác đã chỉ ra, không đúng và sẽ hoảng sợ nếu bạn đưa cho nó một cái gì đó như བོད་ སྐད་ ལ་. Vì vậy, làm điều này với thư viện chuẩn của Rust thậm chí còn khó hơn bạn nghĩ ban đầu.

Tuy nhiên, Rust được thiết kế để khuyến khích sử dụng lại mã và giúp việc sử dụng thư viện trở nên dễ dàng. Vì vậy, cách thành ngữ để viết hoa một chuỗi thực sự khá thú vị:

extern crate inflector;
use inflector::Inflector;

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

4
Câu hỏi của người dùng nghe giống như anh ta muốn hơn .to_sentence_case().
Christopher Oezbek

1
Đáng buồn thay, nó không giúp ích gì cho việc đặt tên cho mọi thứ ... Đây là thư viện tuyệt vời và tôi chưa bao giờ thấy nó trước đây, nhưng tên của nó rất khó (đối với tôi) để nhớ và có các chức năng hầu như không liên quan đến sự suy nghĩ thực tế, một trong số chúng là ví dụ của bạn.
Sahsahae

11

Nó đặc biệt không phức tạp nếu bạn có thể giới hạn đầu vào của mình ở các chuỗi chỉ ASCII.

Kể từ Rust 1.23, strcó một make_ascii_uppercasephương thức (trong các phiên bản Rust cũ hơn, nó đã có sẵn thông qua AsciiExtđặc điểm). Điều này có nghĩa là bạn có thể viết hoa các lát chuỗi chỉ ASCII một cách tương đối dễ dàng:

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

Điều này sẽ biến "taylor"thành "Taylor", nhưng nó sẽ không biến "édouard"thành "Édouard". ( sân chơi )

Sử dụng cẩn thận.


2
Giúp một người mới chơi Rust, tại sao rcó thể thay đổi được? Tôi thấy đó slà một biến thể str. Ohhhh ok: Tôi có câu trả lời cho câu hỏi của riêng tôi: get_mut(được gọi ở đây là w / a range) trả về rõ ràng Option<&mut>.
Steven Lu

0

Đây là cách tôi giải quyết vấn đề này, lưu ý rằng tôi phải kiểm tra xem bản thân có phải là ascii không trước khi chuyển thành chữ hoa.

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

Đầu ra

Bruno
B
🦀
ß

བོད་སྐད་ལ 

-1

Dưới đây là một phiên bản có nghĩa là chậm hơn một chút so với phiên bản cải tiến @ Shepmaster, nhưng cũng có nhiều thành ngữ :

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

-1

Tôi đã làm theo cách này:

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

Nếu nó không phải là một chuỗi ASCII:

fn str_cap(s: &str) -> String {
  format!("{}{}", s.chars().next().unwrap().to_uppercase(), 
  s.chars().skip(1).collect::<String>())
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.