В настоящее время я работаю над реализацией итератора, который разбивает заданную строку и возвращает подстроки в качестве итератора. Для специального символа он будет возвращать только специальный символ или подстроку буквенно-цифровых символов, которая будет разделена пробелами.
Я предполагаю, что есть какая-то проблема с индексацией из-за символов utf-8, но я не знаю, как с этим справиться.
Это структура и ее реализация итератора.
pub struct SpecialStr<'a> {
string: &'a str,
back: usize,//index of the back of the &str substring.
}
impl<'a> SpecialStr<'a> {
pub fn new(input : &'a str) -> Self {
SpecialStr {string: input, back: 0}
}
}
//anything which is not a alphanumeric or a whitespace.
pub fn is_special(c: char) -> bool {
!c.is_ascii_alphanumeric() && !c.is_whitespace()
}
impl<'a> Iterator for SpecialStr<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
let input_string: &str = self.string;
let max_index = self.string.len();
for front in self.back..max_index {
let character = match self.string.chars().nth(front) {
Some(character) => character,
None => return None,
};
//if the present char is a special character just return it by itself.
if is_special(character) {
self.back += character.len_utf8();
return Some(&input_string[self.back-character.len_utf8()..self.back]);
} else if !character.is_whitespace() {
//if it is not a special character then we are going to select a substring whose end will be at :
//--the one before the next following special character
//--or the one before a whitespace
//--or the one before the end of the sentence.
//then we are going to determine the substring to be selected based on this comparision.
for back in front+character.len_utf8()..max_index {
let character_2 = match self.string.chars().nth(back) {
Some(character) => character,
None => return None,
};
if is_special(character_2) || character_2.is_whitespace() || back == max_index-1 {
self.back = back;
return Some(&input_string[front..self.back]);
}
}
} else {
self.back += 1;
}
}
None
}
}
И это испытание.
fn divide_n_print_3() {
use super::tokenisation::SpecialStr;
let input = "` i love mine, too . happy mother�s day to all";
let new_one = SpecialStr::new(&input);
for i in new_one.into_iter() {
println!("{}", i);
}
}
Я получаю сообщение об ошибке:
thread 'feature_extraction::tokenisation_test::divide_n_print_3' panicked at 'byte index 38 is not a char boundary; it is inside '½' (bytes 37..39) of \`\` i love mine, too . happy mother�s day to all\`', src\\feature_extraction\\tokenisation.rs:74:38
Я понимаю смысл ошибки, но понятия не имею, как ее решить. Любая помощь будет оценена по достоинству.
@cdhowie о, спасибо, проверю
Иногда вы используете self.back
как байтовый индекс, а иногда как символьный индекс. Это может быть только одно из этих двух вещей.
Возможно, это тот случай, когда использование регулярных выражений действительно упрощает проблему.
use regex::Regex;
use std::sync::OnceLock;
fn tokenize(s: &str) -> impl Iterator<Item = &str> {
static REGEX: OnceLock<Regex> = OnceLock::new();
let regex = REGEX.get_or_init(|| Regex::new(r"[[:alnum:]]+|\S").unwrap());
regex.find_iter(s).map(|m| m.as_str())
}
Это возвращает любую последовательную серию буквенно-цифровых символов ASCII, в противном случае любой одиночный символ без пробелов и пропускает все пробелы. (Обратите внимание, что он пропускает все пробелы в Юникоде и учитывает только буквенно-цифровые символы ASCII, поскольку именно это и делает ваш код.)
Если вы предпочитаете реализовать итератор самостоятельно, вот один из вариантов:
struct Tokenizer<'a> {
s: &'a str,
}
impl<'a> Iterator for Tokenizer<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
self.s = self.s.trim_start();
let c = self.s.chars().next()?;
let len = if c.is_ascii_alphanumeric() {
self.s
.find(|c: char| !c.is_ascii_alphanumeric())
.unwrap_or(self.s.len())
} else {
c.len_utf8()
};
let result;
(result, self.s) = self.s.split_at(len);
Some(result)
}
}
Это позволяет избежать большинства проблем, с которыми вы сталкивались при использовании строковых методов для фактической итерации — trim_start()
для пропуска пробелов и find()
для поиска серий буквенно-цифровых символов.
.chars().nth()
будет медленно. Вероятно, вы хотите, чтобы ваш итератор владел CharIndices и прошел через это.