¿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
.
Es un simple problema
Desafortunadamente, esto no es verdad. ¿Quizás deberíamos esforzarnos por convertir el mundo al esperanto ?
Supongo que char::to_uppercase
ya maneja correctamente Unicode.
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.
¿Por qué la necesidad de todas las conversiones de tipos de datos?
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.
la indexación podría devolver un carácter Unicode de varios bytes
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.
to_uppercase
podría devolver un carácter en mayúsculas
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(),
}
}
ß
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.