string - str - Cortar una cadena que contiene caracteres Unicode
utf-8 caracteres especiales (2)
Posibles soluciones al corte de punto de código.
Sé que puedo usar el iterador
chars()
y recorrer manualmente la subcadena deseada, pero ¿hay alguna forma más concisa?
Si conoce los índices de bytes exactos, puede cortar una cadena:
let text = "Hello привет";
println!("{}", &text[2..10]);
Esto imprime "llo пр". Entonces el problema es encontrar la posición exacta del byte. Puedes hacerlo con bastante facilidad con el iterador char_indices()
(alternativamente, puedes usar chars()
con char::len_utf8()
):
let text = "Hello привет";
let end = text.char_indices().map(|(i, _)| i).nth(8).unwrap();
println!("{}", &text[2..idx]);
Como otra alternativa, primero puede recopilar la cadena en Vec<char>
. Luego, la indexación es simple, pero para imprimirla como una cadena, debe recopilarla nuevamente o escribir su propia función para hacerlo.
let text = "Hello привет";
let text_vec = text.chars().collect::<Vec<_>>();
println!("{}", text_vec[2..8].iter().cloned().collect::<String>());
¿Por qué esto no es más fácil?
Como puede ver, ninguna de estas soluciones es tan buena. Esto es intencional, por dos razones:
Como str
es simplemente un búfer UTF8, la indexación por puntos de código Unicode es una operación O (n). Generalmente, la gente espera que el operador []
sea una operación O (1). Rust hace que esta complejidad de tiempo de ejecución sea explícita y no intenta ocultarla. En las dos soluciones anteriores puede ver claramente que no es O (1).
Pero la razón más importante:
Los puntos de código Unicode generalmente no son una unidad útil
Lo que Python hace (y lo que crees que quieres) no es tan útil. Todo se reduce a la complejidad del lenguaje y, por lo tanto, a la complejidad de Unicode. Python rebanadas puntos de código Unicode. Esto es lo que representa un char
Rust. Es 32 bits grande (bastarían unos pocos bits menos, pero redondeamos a una potencia de 2).
Pero lo que realmente quieres hacer es cortar los caracteres percibidos por el usuario . Pero este es un término explícitamente vagamente definido. Diferentes culturas e idiomas consideran diferentes cosas como "un personaje". La aproximación más cercana es un "grupo de grafemas". Dicho clúster puede constar de uno o más puntos de código Unicode. Considera este código de Python 3:
>>> s = "Jürgen"
>>> s[0:2]
''Ju''
Sorprendente, ¿verdad? Esto es porque la cadena de arriba es:
-
0x004A
LETRA J MAYÚSCULA LATINA -
0x0075
LETRA PEQUEÑA LATINA U -
0x0308
DIAERESIS COMBINADA - ...
Este es un ejemplo de un carácter de combinación que se representa como parte del carácter anterior. Cortar Python hace lo "incorrecto" aquí.
Otro ejemplo:
>>> s = "fire"
>>> s[0:2]
''fir''
Tampoco es lo que esperas. Esta vez, fi
es en realidad el fi
ligadura, que es un punto de código.
Hay muchos más ejemplos en los que Unicode se comporta de una manera sorprendente. Vea los enlaces en la parte inferior para obtener más información y ejemplos.
Por lo tanto, si desea trabajar con cadenas internacionales que deberían poder funcionar en todas partes, ¡no haga cortes de punto de código! Si realmente necesita ver la cadena semánticamente como una serie de caracteres , use grupos de grafemas. Para hacer eso, la caja unicode-segmentation
es muy útil.
Más recursos sobre este tema:
Tengo una pieza de texto con caracteres de diferente bytelength.
let text = "Hello привет";
Necesito tomar una parte de la cadena dada los índices de caracteres de inicio (incluido) y final (excluidos). Probé esto
let slice = &text[start..end];
y obtuve el siguiente error
thread ''main'' panicked at ''byte index 7 is not a char boundary; it is inside ''п'' (bytes 6..8) of `Hello привет`''
Supongo que sucede ya que las letras cirílicas son de múltiples bytes y la notación [..]
toma caracteres usando índices de bytes . ¿Qué puedo usar si quiero dividir usando índices de caracteres , como hago en Python?
slice = text[start:end]
?
Sé que puedo usar el iterador chars()
y recorrer manualmente la subcadena deseada, pero ¿hay alguna forma más concisa?
Una cadena codificada en UTF-8 puede contener caracteres, que consta de varios bytes. En su caso, comienza en el índice 6 (incluido) y termina en la posición 8 (exclusivo), por lo que la indexación 7 no es el comienzo del personaje. Es por esto que tu error ocurrió.
Puede usar str::char_indices
para resolver esto (recuerde que llegar a una posición en UTF-8 es O(n)
):
fn get_utf8_slice(string: &str, start: usize, end: usize) -> Option<&str> {
assert!(end >= start);
string.char_indices().nth(start).and_then(|(start_pos, _)| {
string[start_pos..]
.char_indices()
.nth(end - start + 1)
.map(|(end_pos, _)| &string[start_pos..end_pos])
})
}
Puede usar str::chars()
si está de acuerdo con obtener una String
:
let string: String = text.chars().take(end).skip(start).collect();