Получение пользовательского итератора для работы в Rust над &String

В настоящее время я работаю над реализацией итератора, который разбивает заданную строку и возвращает подстроки в качестве итератора. Для специального символа он будет возвращать только специальный символ или подстроку буквенно-цифровых символов, которая будет разделена пробелами.

Я предполагаю, что есть какая-то проблема с индексацией из-за символов 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

Я понимаю смысл ошибки, но понятия не имею, как ее решить. Любая помощь будет оценена по достоинству.

.chars().nth() будет медленно. Вероятно, вы хотите, чтобы ваш итератор владел CharIndices и прошел через это.
cdhowie 30.09.2023 21:21

@cdhowie о, спасибо, проверю

will the wise 30.09.2023 21:46

Иногда вы используете self.back как байтовый индекс, а иногда как символьный индекс. Это может быть только одно из этих двух вещей.

Sven Marnach 30.09.2023 21:59
В чем разница между методом "==" и equals()
В чем разница между методом "==" и equals()
Это один из наиболее часто задаваемых вопросов новичкам на собеседовании. Давайте обсудим его на примере.
Замена символа по определенному индексу в JavaScript
Замена символа по определенному индексу в JavaScript
В JavaScript существует несколько способов заменить символ в строке по определенному индексу.
0
3
94
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Возможно, это тот случай, когда использование регулярных выражений действительно упрощает проблему.

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() для поиска серий буквенно-цифровых символов.

Другие вопросы по теме