Чому велика літера першої лінії рядка так звита в Rust?


81

Я хотів би написати велику літеру з першої літери &str. Це проста проблема, і я сподіваюся на просте рішення. Інтуїція підказує мені зробити щось подібне:

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

Але &strs не можна індексувати так. Єдиний спосіб, яким я зміг це зробити, здається надто заплутаним. Я перетворюю в &strітератор, перетворюю ітератор у вектор, верхній регістр - перший елемент у векторі, який створює ітератор, який я індексую, створюючи Option, який я розгортаю, щоб дати мені велику першу літеру. Потім я перетворюю вектор в ітератор, який я перетворюю в a String, який я перетворюю в 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;

Чи є простіший спосіб, ніж цей, і якщо так, то який? Якщо ні, то чому Rust розроблений саме таким чином?

Подібне запитання


46
Це проста проблема - ні, це не так. Будь ласка, використовуйте великі літери, ßколи їх тлумачать як німецьку. Підказка: це не один персонаж. Навіть постановка проблеми може бути складною. Наприклад, неправильно писати великі літери першого символу прізвища von Hagen. Це все аспект життя у світовому світі, в якому тисячі років існували різні культури з різними практиками, і ми намагаємось розбити все це на 8 бітів і 2 рядки коду.
Шепмастер

3
Те, що ви представляєте, здається проблемою кодування символів, а не проблемою типу даних. Я припускаю, що char :: to_uppercase вже належним чином обробляє Unicode. Моє питання полягає в тому, навіщо потрібні всі перетворення типів даних? Здається, індексація може повернути багатобайтовий символ Unicode (не єдиний байтовий символ, який передбачає лише ascii), а to_uppercase може повернути символ верхнього регістру будь-якою мовою, якою він перебуває, якщо такий є на цій мові.
marshallm

3
@marshallm char::to_uppercaseсправді вирішує цю проблему, але ви відкидаєте її зусилля, беручи лише першу кодову точку ( nth(0)) замість усіх кодових точок, що складають великі літери

Кодування символів не є простим процесом, на що вказував Джоель щодо Software: Unicode .
Натан

@Shepmaster, загалом ти маєш рацію. Це проста проблема англійською мовою (фактична стандартна база мов програмування та форматів даних). Так, є сценарії, де "написання великих літер" - це навіть не поняття, а інші, де це дуже складно.
Пол Дрейпер

Відповіді:


100

Чому це так заплутано?

Давайте розберемо це, рядок за рядком

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

Це захоплює першу точкову точку і просить перетворити її на верхній регістр. На жаль для тих з нас, хто виріс, розмовляючи англійською, не завжди існує просте індивідуальне відображення "малої літери" у "великої букви" . Примітка: ми називаємо їх великими та малими літерами, тому що одна коробка листів була над другою коробкою листів у той день .

Цей код викликає паніку, коли кодова точка не має відповідного варіанта верхнього регістру. Я не впевнений, чи існують вони насправді. Це також може семантично вийти з ладу, коли кодова точка має варіант верхнього регістру, який має кілька символів, наприклад німецький ß. Зауважте, що ß насправді ніколи не може бути написано великими літерами у Реальному світі, це лише той приклад, який я завжди можу пам’ятати та шукати. Починаючи з 29.06.2017 року, фактично офіційні правила написання німецької мови оновлено таким чином, що і "ẞ", і "SS" є дійсними великими літерами !

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

Тут ми перетворюємо символи назад в UTF-8 і вимагаємо нового розподілу для їх зберігання, оскільки вихідна змінна зберігалася в постійній пам'яті, щоб не забирати пам'ять під час виконання.

let s3 = &s2;

І тепер ми беремо посилання на це String.

Це проста проблема

На жаль, це неправда. Можливо, нам слід намагатись навернути світ на есперанто ?

Я припускаю, що char::to_uppercaseвже правильно обробляє Unicode.

Так, я, звичайно, сподіваюся. На жаль, Unicode недостатній у всіх випадках. Завдяки huon за вказівку на турецьку I , де як версії верхнього ( İ ), так і нижнього регістру ( i ) мають крапку. Тобто, немає один капіталізації листи i; це також залежить від мови вихідного тексту.

навіщо потрібні всі перетворення типів даних?

Оскільки типи даних, з якими ви працюєте, важливі, коли вас турбує правильність та продуктивність. A char- це 32 біти, а рядок кодується UTF-8. Це різні речі.

індексація може повернути багатобайтовий символ Unicode

Тут може бути якась невідповідна термінологія. A 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 і дозволяв комусь розумнішим за мене обробляти.

Покращена

Говорячи про "когось розумнішого за мене", Веедрак зазначає, що, ймовірно, ефективніше перетворити ітератор назад у фрагмент після доступу до перших великих кодових точок. Це дозволяє отримати 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(),
    }
}

34
Поміркувавши над цим, я краще розумію ці варіанти дизайну. Стандартна бібліотека повинна вибирати найбільш універсальні, ефективні та безпечні компроміси. В іншому випадку це змушує розробників робити компроміси, які можуть не відповідати їх застосуванню, архітектурі чи локалі. Або це може призвести до двозначності та непорозумінь. Якщо я віддаю перевагу іншим компромісам, я можу вибрати сторонню бібліотеку або написати її самостійно.
marshallm

13
@marshallm це справді чудово чути! Я боюся, що багато новачків у Rust неправильно розуміють рішення, прийняті дизайнерами Rust, і просто списують їх як занадто складні без користі. Задаючи тут відповіді на запитання та відповідаючи на них, я отримав вдячність за турботу, яка потребує використання таких конструкцій і, сподіваюся, стала кращим програмістом. Тримати відкритим розум і бажати дізнатися більше - це чудова риса для програміста.
Шепмастер

6
«Турецький я» є прикладом локалі залежності , яка є більш безпосереднє відношення до цього конкретного питання , ніж сортування.
huon

6
Я здивований, що вони мають to_uppercase і to_lowercase, але не to_titlecase. IIRC, деякі символи Юнікоду насправді мають спеціальний варіант заголовка.
Тім,

6
До речі, навіть одна кодова точка не може бути правильною одиницею для перетворення. Що робити, якщо першим символом є кластер графем, який повинен отримувати особливу обробку у верхньому регістрі? (Так трапляється, що розкладені умлаути спрацьовують, якщо ви просто пишете базовий символ великими літерами, але я не знаю, чи це загально вірно.)
Себастьян Редл,

23

Чи є простіший спосіб, ніж цей, і якщо так, то який? Якщо ні, то чому Rust розроблений саме таким чином?

Ну, так і ні. Як зазначено в іншій відповіді, ваш код не правильний, і ви будете панікувати, якщо ви дасте йому щось на зразок བོད་ སྐད་ ལ་. Отже, зробити це зі стандартною бібліотекою Руста навіть складніше, ніж ви спочатку думали.

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

extern crate inflector;
use inflector::Inflector;

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

4
Питання користувача звучить більше, як би він хотів .to_sentence_case().
Крістофер Озбек,

1
На жаль, це не допомагає називати речі ... Це неймовірна бібліотека, і я її ніколи раніше не бачив, але її ім’я важко (для мене) запам’ятати і має функції, які навряд чи мають щось спільне із фактичним зворотом, одна з них бути вашим прикладом.
Sahsahae

11

Це не особливо заплутано, якщо ви можете обмежити введення лише рядками 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". ( дитячий майданчик )

Використовуйте з обережністю.


2
Допоможіть новачкові Rust, чому він rзмінюється? Я бачу, що sце мінливо str. О-о-о-о: добре, у мене є відповідь на власне запитання: get_mut(називається тут з діапазоном) явно повертається Option<&mut>.
Стівен Лу

0

Ось як я вирішив цю проблему, зауважте, що мені довелося перевірити, чи 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
🦀
ß

བོད་སྐད་ལ 

-1

Ось версія трохи повільніша за вдосконалену версію @ 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()
}

-1

Я зробив це так:

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>())
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.