Regex и unicode

Добрый день.
Нахожусь в поиске библиотеки для работы с регулярками. Гугл так или иначе приводит к regex. Её метод find_at выдаёт начало найденого текста относительно начала строки в байтах, а мне надо в символах. Например есть строка привет мне надо что бы find выдавал начало подстроки ивет со 2-го символа а не с 4-го байта. Как мне подойти к решению задачи?

Обычные строки, str и String, в rust кодируются в utf-8. Символ utf-8 (можно грубо считать что буква), имеет длину от 1 до 4 байт, в зависимости от количества единиц в начале первого байта:

1 байт  - 0xxxxxxx
2 байта - 110xxxxx
3 байта - 1110xxxx
4 байта - 11110xxx

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

1 лайк

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

Ясно, спасибо за ответы. Думал может есть путь попроще чем погружаться в тонкости unicode и кодировок. У меня задача учебная, попробую обойтись без регулярок и запрограммировать конечный автомат руками, перегнав строку в Vec<char>.

Да тут не так что бы особо нюансы, вроде. Я вот что-то такое имел в виду как стартовую точку:

use regex::Regex;
use unicode_segmentation::UnicodeSegmentation as US;

fn index_byte_to_grapheme(s: &str, index: usize) -> Option<usize> {
    for (grapheme_i, (byte_i, _grapheme)) in US::grapheme_indices(s, true).enumerate() {
        // dbg!((grapheme_i, byte_i, _grapheme));
        if byte_i == index {
            return Some(grapheme_i);
        }
    }
    None
}

fn main() {
    let s = "абвгд";
    let re = Regex::new("вг").unwrap();
    let mat = re.find_at(s, 0).unwrap();
    dbg!(mat);
    let start_grapheme_i = index_byte_to_grapheme(s, mat.start());
    let end_grapheme_i = index_byte_to_grapheme(s, mat.end());
    dbg!(start_grapheme_i);
    dbg!(end_grapheme_i);
}

(песочница)

1 лайк

Если будете использовать только ASCII, то ASCII является подмножеством utf-8, и поэтому с ним смело можно работать как с однобайтовым char массивом. Совсем просто будет.

Если только с ascii то понятно, мне было интересно как быть когда надо хотя бы с кирилицей работать. Сталкиваюсь с rust впервые, в других языках, таких как python или Go обычно в стандартной библиотеке есть такие вещи.

Так и Rust есть, непонятно зачем unicode_segmentation,
можно вызвать метод char_indices у &str и понять какой char соответствует найденному смещению байтах.

Если только с кириллицей надо работать и всё, то графемные кластеры тебе не актуальны и хватит работы с кодпоинтами. Как @Dushistov пишет - с этим справятся и встроенные char’ы языка.

непонятно зачем unicode_segmentation

Для нормальной работы с юникодом (см ссылку на статью выше)? Например, что бы эмоджи из нескольких кодпоинтов не взрывали код - изначально же в вопросе не было обозначено, что нужна работа только с кириллицей.