Chciałbym zacząć wielką literę od a &str
. To prosty problem i mam nadzieję na proste rozwiązanie. Intuicja każe mi zrobić coś takiego:
let mut s = "foobar";
s[0] = s[0].to_uppercase();
Ale &str
nie można go indeksować w ten sposób. Jedyny sposób, w jaki byłem w stanie to zrobić, wydaje się zbyt zawiły. Konwertuję na &str
iterator, konwertuję iterator na wektor, wielkie litery to pierwsza pozycja w wektorze, co tworzy iterator, do którego indeksuję, tworząc Option
znak, który rozwijam, aby uzyskać pierwszą wielką literę. Następnie konwertuję wektor na iterator, który zamieniam na a String
, który zamieniam na 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;
Czy jest łatwiejszy sposób niż ten, a jeśli tak, to co? Jeśli nie, dlaczego Rust został zaprojektowany w ten sposób?
ß
przypadku interpretacji języka niemieckiego należy pisać wielkimi literami . Wskazówka: to nie jest pojedynczy znak. Nawet stwierdzenie problemu może być skomplikowane. Na przykład niewłaściwe byłoby pisanie dużej litery pierwszego znaku nazwiskavon Hagen
. To wszystko jest aspektem życia w globalnym świecie, w którym od tysięcy lat istniały rozbieżne kultury z różnymi praktykami, a my staramy się zgnieść to wszystko na 8 bitów i 2 linie kodu.char::to_uppercase
rzeczywiście rozwiązuje ten problem, ale odrzucasz jego wysiłki, biorąc tylko pierwszy punkt kodowy (nth(0)
) zamiast wszystkich punktów kodu, które składają się naOdpowiedzi:
Dlaczego jest tak zagmatwany?
Podzielmy to, linia po linii
let s1 = "foobar";
Stworzyliśmy ciąg literału zakodowany w UTF-8 . UTF-8 pozwala kodować 1,114,112 punktów kodowych z Unicode w sposób, który jest dość zwarta, jeśli pochodzą z regionu świata, że typy w większości znaków znalezionych w kodzie ASCII , standard utworzony w 1963. UTF-8 jest zmienna długość kodowanie, co oznacza, że pojedynczy punkt kodowy może zająć od 1 do 4 bajtów . Krótsze kodowanie jest zarezerwowane dla ASCII, ale wiele Kanji zajmuje 3 bajty w UTF-8 .
let mut v: Vec<char> = s1.chars().collect();
Tworzy to wektor
char
aktorów. Znak to 32-bitowa liczba, która jest bezpośrednio mapowana do punktu kodowego. Jeśli zaczęliśmy od tekstu tylko ASCII, czterokrotnie zwiększyliśmy nasze wymagania dotyczące pamięci. Gdybyśmy mieli kilka postaci z planu astralnego , być może nie używaliśmy dużo więcej.v[0] = v[0].to_uppercase().nth(0).unwrap();
Spowoduje to pobranie pierwszego punktu kodowego i zażądanie konwersji na wariant z dużymi literami. Na nieszczęście dla tych z nas, którzy dorastali mówiąc po angielsku, nie zawsze istnieje proste odwzorowanie „małej litery” na „dużą” . Uwaga dodatkowa: nazywamy je dużymi i małymi literami, ponieważ w tamtych czasach jedno pudełko z literami znajdowało się nad drugim .
Ten kod wywoła panikę, gdy punkt kodowy nie ma odpowiadającego mu wariantu z dużymi literami. Właściwie nie jestem pewien, czy takie istnieją. Może również semantycznie zawieść, gdy punkt kodowy ma wariant z wielkimi literami, który ma wiele znaków, na przykład niemiecki
ß
. Zauważ, że ß może nigdy nie być pisane wielką literą w The Real World, jest to jedyny przykład, który zawsze pamiętam i którego szukam. W rzeczywistości od 2017-06-29 oficjalne zasady pisowni niemieckiej zostały zaktualizowane, aby zarówno „ẞ”, jak i „SS” były poprawnymi wielkimi literami !let s2: String = v.into_iter().collect();
Tutaj konwertujemy znaki z powrotem do UTF-8 i wymagamy nowej alokacji, aby je przechowywać, ponieważ oryginalna zmienna była przechowywana w stałej pamięci, aby nie zajmować pamięci w czasie wykonywania.
let s3 = &s2;
A teraz odniesiemy się do tego
String
.Niestety to nieprawda. Może powinniśmy podjąć próbę nawrócenia świata na esperanto ?
Tak, mam taką nadzieję. Niestety, Unicode nie we wszystkich przypadkach wystarcza. Dzięki Huon dla wskazując na tureckiej I , gdzie zarówno górna ( İ ) i małe litery ( I ) wersje mają kropkę. Oznacza to, że nie ma jednej właściwej wielkości litery
i
; zależy to również od ustawień regionalnych tekstu źródłowego.Ponieważ typy danych, z którymi pracujesz, są ważne, gdy martwisz się o poprawność i wydajność. A
char
ma 32 bity, a łańcuch jest zakodowany w formacie UTF-8. To są różne rzeczy.W tym miejscu może występować niedopasowana terminologia. A
char
to wielobajtowy znak Unicode.Cięcie łańcucha jest możliwe, jeśli idziesz bajt po bajcie, ale standardowa biblioteka będzie panikować, jeśli nie jesteś na granicy znaków.
Jednym z powodów, dla których indeksowanie łańcucha w celu uzyskania znaku nigdy nie zostało zaimplementowane, jest to, że tak wiele osób niewłaściwie używa ciągów znaków jako tablic znaków ASCII. Indeksowanie ciągu znaków w celu ustawienia znaku nigdy nie mogłoby być wydajne - musiałbyś być w stanie zastąpić 1-4 bajty wartością, która również ma 1-4 bajty, powodując, że reszta ciągu odbija się dość często.
Jak wspomniano powyżej,
ß
to pojedynczy znak, który po zapisaniu wielkimi literami staje się dwoma znakami .Rozwiązania
Zobacz także odpowiedź trentcl, która zawiera tylko wielkie litery w znakach ASCII.
Oryginalny
Gdybym miał napisać kod, wyglądałby tak:
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("ß")); }
Ale prawdopodobnie wyszukałbym wielkie litery lub unicode w crates.io i pozwoliłbym zająć się tym komuś mądrzejszemu ode mnie.
Ulepszony
Mówiąc o „kimś mądrzejszym ode mnie”, Veedrac wskazuje, że prawdopodobnie bardziej wydajne jest przekonwertowanie iteratora z powrotem na plasterek po uzyskaniu dostępu do pierwszych dużych punktów kodu . Pozwala to
memcpy
na resztę bajtów.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(), } }
źródło
Cóż, tak i nie. Twój kod, jak wskazała druga odpowiedź, jest nieprawidłowy i będzie panikować, jeśli podasz mu coś w rodzaju བོད་ སྐད་ ལ་. Zatem zrobienie tego ze standardową biblioteką Rusta jest jeszcze trudniejsze niż początkowo sądziłeś.
Jednak Rust został zaprojektowany, aby zachęcać do ponownego użycia kodu i ułatwiać wprowadzanie bibliotek. Więc idiomatyczny sposób kapitalizacji ciągu jest w rzeczywistości całkiem przyjemny:
extern crate inflector; use inflector::Inflector; let capitalized = "some string".to_title_case();
źródło
.to_sentence_case()
.Nie jest to szczególnie skomplikowane, jeśli możesz ograniczyć swoje dane wejściowe do ciągów znaków ASCII.
Od wersji Rust 1.23
str
mamake_ascii_uppercase
metodę (w starszych wersjach Rusta była dostępna poprzezAsciiExt
cechę). Oznacza to, że możesz ze względną łatwością pisać wielkimi literami w plasterkach zawierających tylko ASCII:fn make_ascii_titlecase(s: &mut str) { if let Some(r) = s.get_mut(0..1) { r.make_ascii_uppercase(); } }
To zmieni się
"taylor"
w"Taylor"
, ale nie zmieni się"édouard"
w"Édouard"
. ( plac zabaw )Używaj ostrożnie.
źródło
r
zmienny? Widzę, żes
to zmiennastr
. Ohhhh ok: Mam odpowiedź na moje własne pytanie:get_mut
(zwane tutaj z zakresem) wyraźnie zwracaOption<&mut>
.W ten sposób rozwiązałem ten problem, zauważ, że musiałem sprawdzić, czy self nie jest ascii przed zamianą na wielkie litery.
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()); }
Wynik
źródło
Oto wersja, która jest nieco wolniejsza niż ulepszona wersja @ Shepmaster, ale także bardziej idiomatyczna :
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() }
źródło
Zrobiłem to w ten sposób:
fn str_cap(s: &str) -> String { format!("{}{}", (&s[..1].to_string()).to_uppercase(), &s[1..]) }
Jeśli nie jest to ciąg ASCII:
fn str_cap(s: &str) -> String { format!("{}{}", s.chars().next().unwrap().to_uppercase(), s.chars().skip(1).collect::<String>()) }
źródło