Подтвердить что ты не робот

Почему заглавные буквы первой буквы строки, столь запутанной в 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 спроектирован таким образом?

Подобный вопрос

4b9b3361

Ответ 1

Пусть сломается, по очереди

let s1 = "foobar";

Мы создали литеральную строку, которая закодирована в UTF-8. UTF-8 позволяет нам кодировать 1,114,112 кодовых точек Unicode таким образом, что это довольно компактно, если вы попали из региона мира, который в основном использует символы, найденные в ASCII, стандарте, созданном в 1963 году. UTF-8 - это переменная длина кодирование, что означает, что одна точка кода может занимать от 1 до 4 байтов. Более короткие кодировки зарезервированы для ASCII, но многие кандзи занимают 3 байта в UTF-8.

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

Это создает вектор char. Символ представляет собой 32-битное число, которое непосредственно сопоставляется с кодовой точкой. Если бы мы начали с текста, содержащего только ASCII, мы увеличили в четыре раза наши требования к памяти. Если бы у нас была куча персонажей из астрального плана, то, возможно, мы не использовали это намного больше.

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

Это захватывает первую кодовую точку и запрашивает ее преобразование в верхний регистр. К сожалению, для тех из нас, кто вырос по-английски, не всегда есть однократное сопоставление "маленькой буквы" с "большой буквой". Боковое замечание: мы называем их upper- и нижним регистром, потому что одна буква букв была над другим полем писем в тот же день.

Этот код будет паниковать, если точка кода не имеет соответствующего варианта в верхнем регистре. Я не уверен, существуют ли на самом деле. Он также может семантически терпеть неудачу, если точка кода имеет вариант с верхним регистром, который имеет несколько символов, например, немецкий ß. Обратите внимание, что ß никогда не может быть капитализирована в реальном мире, это единственный пример, который я всегда могу запомнить и найти. По состоянию на 2017-06-29, на самом деле, официальные правила немецкой орфографии были обновлены так, что как "ẞ" и "СС" являются действительной капитализацией !

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

Здесь мы преобразуем символы обратно в UTF-8 и требуем нового размещения для их хранения, поскольку исходная переменная хранилась в постоянной памяти, чтобы не занимать память во время выполнения.

let s3 = &s2;

И теперь мы ссылаемся на эту String.

Это простая проблема

К сожалению, это не так. Может быть, нам стоит попытаться превратить мир в эсперанто?

Я предполагаю, что char::to_uppercase уже правильно обрабатывает Unicode.

Да, я, конечно, надеюсь. К сожалению, Unicode недостаточно во всех случаях. Благодаря huon для указания турецкого I, где и верхняя (İ), и нижняя (i) версии имеют точку. То есть, нет никакой надлежащей капитализации буквы i; это зависит от языка исходного текста.

зачем нужна конвертация всех типов данных?

Поскольку типы данных, с которыми вы работаете, важны, когда вас беспокоят правильность и производительность. char 32-бит, а строка кодируется UTF-8. Это разные вещи.

индексирование может возвращать многобайтовый символ Unicode

Здесь может быть некоторая несовпадающая терминология. char является многобайтовым символом Юникода.

Нарезка строки возможна, если вы побиваете байты, но стандартная библиотека будет паниковать, если вы не находитесь на границе символа.

Одна из причин, по которой индексирование строки для получения символа никогда не выполнялась, заключается в том, что так много людей злоупотребляют строками как массивы символов ASCII. Индексирование строки для установки символа никогда не могло быть эффективным - вы должны были бы иметь возможность заменить 1-4 байта со значением, равным 1-4 байтам, в результате чего остальная часть строки будет сильно отскакивать.

to_uppercase может возвращать символ верхнего регистра

Как упоминалось выше, ß является единственным символом, который при заглавной записи становится двумя символами.


Если бы мне пришлось написать код, это выглядело бы так:

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

Но я бы, вероятно, искал прописные или unicode на crates.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(),
    }
}

Ответ 2

Есть ли более простой способ, чем это, и если да, то что? Если нет, почему Rust спроектирован таким образом?

Хорошо, да и нет. Ваш код, как указывал другой ответ, неверен и будет паниковать, если вы дадите ему что-то вроде བོད་ སྐད་ ལ་. Поэтому делать это с стандартной библиотекой Rust еще сложнее, чем вы изначально думали.

Однако, Rust предназначен для поощрения повторного использования кода и упрощения приведения библиотек. Таким образом, идиоматический способ заглавной строки на самом деле вполне приемлем:

extern crate inflector;
use inflector::Inflector;

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

Ответ 3

Это не особенно сложно, если вы можете ограничить ввод только строками 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". (детская площадка)

Используйте с осторожностью.