Me gustaría poner en mayúscula la primera letra de a &str
. Es un problema simple y espero una solución simple. La intuición me dice que haga algo como esto:
let mut s = "foobar";
s[0] = s[0].to_uppercase();
Pero los &str
correos electrónicos no se pueden indexar de esta manera. La única forma en que he podido hacerlo parece demasiado complicada. Convierto el &str
en un iterador, convierto el iterador en un vector, en mayúsculas el primer elemento del vector, lo que crea un iterador, en el que indexo, creando un Option
, que desenvuelvo para darme la primera letra en mayúscula. Luego convierto el vector en un iterador, que convierto en a String
, que convierto en 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;
¿Existe una forma más fácil que esta, y si es así, cuál? Si no es así, ¿por qué Rust está diseñado de esta manera?
ß
cuando se interprete como alemán. Pista: no es un solo personaje. Incluso el planteamiento del problema puede resultar complicado. Por ejemplo, sería incorrecto poner en mayúscula el primer carácter del apellidovon Hagen
. Todo esto es un aspecto de vivir en un mundo global que ha tenido miles de años de culturas divergentes con diferentes prácticas y estamos tratando de aplastar todo eso en 8 bits y 2 líneas de código.char::to_uppercase
hecho maneja este problema, pero desperdicia sus esfuerzos tomando solo el primer punto de código (nth(0)
) en lugar de todos los puntos de código que componen la capitalizaciónRespuestas:
¿Por qué es tan complicado?
Vamos a analizarlo, línea por línea
let s1 = "foobar";
Hemos creado una cadena literal codificada en UTF-8 . UTF-8 nos permite codificar los 1,114,112 puntos de código de Unicode de una manera bastante compacta si viene de una región del mundo que escribe principalmente caracteres que se encuentran en ASCII , un estándar creado en 1963. UTF-8 es una longitud variable codificación, lo que significa que un solo punto de código puede ocupar de 1 a 4 bytes . Las codificaciones más cortas están reservadas para ASCII, pero muchos Kanji toman 3 bytes en UTF-8 .
let mut v: Vec<char> = s1.chars().collect();
Esto crea un vector de
char
acciones. Un carácter es un número de 32 bits que se asigna directamente a un punto de código. Si comenzamos con texto solo ASCII, hemos cuadruplicado nuestros requisitos de memoria. Si tuviéramos un montón de personajes del plano astral , entonces tal vez no hayamos usado mucho más.v[0] = v[0].to_uppercase().nth(0).unwrap();
Esto toma el primer punto de código y solicita que se convierta a una variante en mayúsculas. Desafortunadamente para aquellos de nosotros que crecimos hablando inglés, no siempre hay un mapeo simple uno a uno de una "letra pequeña" a una "letra grande" . Nota al margen: los llamamos mayúsculas y minúsculas porque un cuadro de letras estaba encima del otro cuadro de letras en el día .
Este código entrará en pánico cuando un punto de código no tenga una variante correspondiente en mayúsculas. No estoy seguro de si existen, en realidad. También podría fallar semánticamente cuando un punto de código tiene una variante en mayúsculas que tiene varios caracteres, como el alemán
ß
. Tenga en cuenta que es posible que ß nunca se escriba con mayúscula en The Real World, este es el ejemplo que siempre puedo recordar y buscar. A partir del 2017-06-29, de hecho, las reglas oficiales de ortografía alemana se han actualizado para que tanto "ẞ" como "SS" sean mayúsculas válidas .let s2: String = v.into_iter().collect();
Aquí convertimos los caracteres nuevamente a UTF-8 y requerimos una nueva asignación para almacenarlos, ya que la variable original se almacenó en memoria constante para no ocupar memoria en tiempo de ejecución.
let s3 = &s2;
Y ahora tomamos una referencia a eso
String
.Desafortunadamente, esto no es verdad. ¿Quizás deberíamos esforzarnos por convertir el mundo al esperanto ?
Sí, ciertamente eso espero. Desafortunadamente, Unicode no es suficiente en todos los casos. Gracias a huon por señalar la I turca , donde tanto la versión mayúscula ( İ ) como la minúscula ( i ) tienen un punto. Es decir, no hay una mayúscula adecuada de la letra
i
; también depende de la ubicación del texto fuente.Porque los tipos de datos con los que está trabajando son importantes cuando le preocupa la corrección y el rendimiento. A
char
es de 32 bits y una cadena está codificada en UTF-8. Son cosas diferentes.Puede que haya alguna terminología que no coincida aquí. A
char
es un carácter Unicode de varios bytes.Es posible cortar una cadena si va byte a byte, pero la biblioteca estándar entrará en pánico si no se encuentra en un límite de caracteres.
Una de las razones por las que nunca se implementó la indexación de una cadena para obtener un carácter es porque mucha gente hace un mal uso de las cadenas como matrices de caracteres ASCII. Indexar una cadena a establecer un carácter nunca podría ser eficiente: tendría que poder reemplazar de 1 a 4 bytes con un valor que también sea de 1 a 4 bytes, lo que hace que el resto de la cadena rebote bastante.
Como se mencionó anteriormente,
ß
es un carácter único que, cuando se escribe en mayúscula, se convierte en dos caracteres .Soluciones
Vea también la respuesta de trentcl que solo caracteres ASCII en mayúsculas.
Original
Si tuviera que escribir el código, se vería así:
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("ß")); }
Pero probablemente buscaría mayúsculas o Unicode en crates.io y dejaría que alguien más inteligente que yo lo manejara.
Mejorado
Hablando de "alguien más inteligente que yo", Veedrac señala que probablemente sea más eficiente convertir el iterador nuevamente en un segmento después de acceder a los primeros puntos de código de capital. Esto permite una parte
memcpy
del resto de los bytes.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(), } }
fuente
Bueno, sí y no. Su código, como señaló la otra respuesta, no es correcto y entrará en pánico si le da algo como བོད་ སྐད་ ལ་. Entonces, hacer esto con la biblioteca estándar de Rust es incluso más difícil de lo que pensaba inicialmente.
Sin embargo, Rust está diseñado para fomentar la reutilización de código y facilitar la incorporación de bibliotecas. Entonces, la forma idiomática de poner en mayúscula una cadena es bastante aceptable:
extern crate inflector; use inflector::Inflector; let capitalized = "some string".to_title_case();
fuente
.to_sentence_case()
.No es especialmente complicado si puede limitar su entrada a cadenas solo ASCII.
Desde Rust 1.23,
str
tiene unmake_ascii_uppercase
método (en versiones anteriores de Rust, estaba disponible a través delAsciiExt
rasgo). Esto significa que puede escribir en mayúsculas secciones de cadenas solo en ASCII con relativa facilidad:fn make_ascii_titlecase(s: &mut str) { if let Some(r) = s.get_mut(0..1) { r.make_ascii_uppercase(); } }
Esto se convertirá
"taylor"
en"Taylor"
, pero no se convertirá"édouard"
en"Édouard"
. ( patio de recreo )Úselo con precaución.
fuente
r
mutable? Veo ques
es mutablestr
. Ohhhh ok: tengo la respuesta para mi propia pregunta:get_mut
(llamado aquí con un rango) devuelve explícitamenteOption<&mut>
.Así es como resolví este problema, observe que tuve que verificar si self no es ascii antes de transformarlo en mayúsculas.
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()); }
Salida
fuente
Aquí hay una versión que es un poco más lenta que la versión mejorada de @ Shepmaster, pero también más idiomática :
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() }
fuente
Lo hice de esta manera:
fn str_cap(s: &str) -> String { format!("{}{}", (&s[..1].to_string()).to_uppercase(), &s[1..]) }
Si no es una cadena ASCII:
fn str_cap(s: &str) -> String { format!("{}{}", s.chars().next().unwrap().to_uppercase(), s.chars().skip(1).collect::<String>()) }
fuente