Ответ 1
Возможные решения для нарезки кода
Я знаю, что могу использовать итератор
chars()
и вручную пройти через нужную подстроку, но есть ли более сжатый способ?
Если вы знаете точные байтовые индексы, вы можете нарезать строку:
let text = "Hello привет";
println!("{}", &text[2..10]);
Это печатает "llo pr". Поэтому проблема заключается в том, чтобы узнать точное положение байта. Вы можете сделать это довольно легко с char_indices()
итератора char_indices()
(char_indices()
вы можете использовать char::len_utf8()
chars()
с char::len_utf8()
):
let text = "Hello привет";
let end = text.char_indices().map(|(i, _)| i).nth(8).unwrap();
println!("{}", &text[2..idx]);
В качестве альтернативы вы можете сначала собрать строку в Vec<char>
. Затем индексирование прост, но чтобы напечатать его как строку, вам нужно снова собрать его или написать свою собственную функцию, чтобы сделать это.
let text = "Hello привет";
let text_vec = text.chars().collect::<Vec<_>>();
println!("{}", text_vec[2..8].iter().cloned().collect::<String>());
Почему это не так просто?
Как вы можете видеть, ни одно из этих решений не очень велико. Это преднамеренно по двум причинам:
Поскольку str
является просто буфером UTF8, индексирование по кодовым точкам unicode является операцией O (n). Обычно люди ожидают, что оператор []
будет выполнять операцию O (1). Rust делает эту сложность выполнения явной и не пытается скрыть ее. В обоих вышеизложенных решениях вы можете ясно видеть, что это не O (1).
Но более важная причина:
Кодовые обозначения Unicode обычно не являются полезной единицей
Что делает Python (и то, что вы считаете нужным) не так уж и полезно. Все сводится к сложности языка и, следовательно, сложности юникода. Питонные фрагменты Unicode. Это то, что Руст char
представляет. Это 32-битный бит (было немного меньше бит, но мы округлились до мощности 2).
Но то, что вы на самом деле хотите сделать, - это срезать воспринимаемые пользователем персонажи. Но это явно явно определенный термин. Различные культуры и языки рассматривают разные вещи как "один персонаж". Ближайшим приближением является "кластер графем". Такой кластер может состоять из одного или нескольких кодовых точек Unicode. Рассмотрим этот код Python 3:
>>> s = "Jürgen"
>>> s[0:2]
'Ju'
Удивительно, правда? Это связано с тем, что строка выше:
-
0x004A
ПИСЬМО LATIN CAPITAL J -
0x0075
LATIN SMALL ПИСЬМО U -
0x0308
КОМБИНИРОВАННАЯ ДИАЕРЕЗИЯ - ...
Это пример комбинирующего символа, который отображается как часть предыдущего символа. Нарезка Python делает здесь "неправильную" вещь.
Другой пример:
>>> s = "fire"
>>> s[0:2]
'fir'
Также не то, что вы ожидаете. На этот раз fi
фактически является лигатурой fi
, которая является одной кодовой точкой.
Есть гораздо больше примеров, когда Unicode ведет себя удивительным образом. См. Ссылки внизу для получения дополнительной информации и примеров.
Поэтому, если вы хотите работать с международными строками, которые должны работать повсюду, не делайте codepoint slicing! Если вам действительно нужно семантически рассматривать строку как последовательность символов, используйте кластеры grapheme. Для этого очень удобна unicode-segmentation
ящика.
Дополнительные ресурсы по этой теме: