string - separar - ¿Por qué es mayúscula la primera letra de una cadena tan complicada en Rust?
separar palabra en letras python (3)
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
&str
s no se puede indexar así.
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úscula el primer elemento del vector, que crea un iterador, que indexo, creando una
Option
, que desenvuelvo para darme la primera letra mayúscula .
Luego convierto el vector en un iterador, que convierto en una
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;
¿Hay una manera más fácil que esta, y si es así, qué? Si no, ¿por qué Rust está diseñado de esta manera?
¿Hay una manera más fácil que esta, y si es así, qué? Si no, ¿por qué Rust está diseñado de esta manera?
Pues sí y no. Su código es, como señaló la otra respuesta, no correcto, y entrará en pánico si le da algo como བོད་ སྐད་ ལ་. Así que hacer esto con la biblioteca estándar de Rust es aún más difícil de lo que inicialmente pensaste.
Sin embargo, Rust está diseñado para fomentar la reutilización del código y facilitar la incorporación de bibliotecas. Entonces, la forma idiomática de capitalizar una cadena es realmente bastante sabrosa:
extern crate inflector;
use inflector::Inflector;
let capitalized = "some string".to_title_case();
¿Por qué es tan complicado?
Desglosemos, línea por línea
let s1 = "foobar";
Hemos creado una cadena literal que está 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 vienes de una región del mundo que escribe principalmente caracteres encontrados 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 tomar 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 personajes. 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úscula. Desafortunadamente para aquellos de nosotros que crecimos hablando inglés, no siempre hay un mapeo simple de una "letra pequeña" a una "letra grande" . Nota al margen: los llamamos mayúsculas y minúsculas porque una caja de letras estaba por encima de la otra caja de letras en el día .
Este código entrará en pánico cuando un punto de código no tenga una variante en mayúscula correspondiente.
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úscula que tiene varios caracteres, como el
ß
alemán.
Tenga en cuenta que es posible que ß nunca se capitalice en The Real World, este es el ejemplo que siempre puedo recordar y buscar.
A partir del 29/06/2017, 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 en 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 esa
String
.
Es un problema simple
Desafortunadamente, esto no es verdad. ¿Quizás deberíamos tratar de convertir el mundo al Esperanto ?
Supongo que
char::to_uppercase
ya maneja correctamente Unicode.
Sí, ciertamente lo espero.
Desafortunadamente, Unicode no es suficiente en todos los casos.
Gracias a
Huon por señalar
el
I turco
, donde las versiones en mayúscula (
İ
) y minúscula (
i
) tienen un punto.
Es decir, no hay
una
mayúscula adecuada de la letra
i
;
también depende de la
locale
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.
Un
char
tiene 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 haber alguna terminología no coincidente aquí.
Un
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 está en un límite de caracteres.
Una de las razones por las que la indexación de una cadena para obtener un carácter nunca se implementó es porque mucha gente usa mal las cadenas como matrices de caracteres ASCII. La indexación de una cadena para establecer un carácter nunca podría ser eficiente: tendría que poder reemplazar 1-4 bytes con un valor que también sea 1-4 bytes, lo que hace que el resto de la cadena rebote bastante.
to_uppercase
podría devolver un carácter en mayúscula
Como se mencionó anteriormente,
ß
es un solo carácter que, cuando se escribe con mayúscula, se convierte en
dos caracteres
.
Soluciones
Vea también la respuesta de trentcl que solo escribe en mayúscula los caracteres ASCII.
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 uppercase o unicode en crates.io y dejaría que alguien más inteligente que yo lo maneje.
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
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(),
}
}
No es especialmente complicado si puede limitar su entrada a cadenas solo ASCII.
Desde Rust 1.23,
str
tiene un método
make_ascii_uppercase
(en versiones anteriores de Rust, estaba disponible a través del rasgo
AsciiExt
).
Esto significa que puede poner mayúsculas a los segmentos de cadena solo 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 convertirá
"taylor"
en
"Taylor"
, pero no convertirá
"édouard"
en
"Édouard"
.
(
playground
)
Usar con precaución.