Проверьте, содержат ли строки файла заданный шаблон без регулярного выражения в Rust

#string #rust #matching

Вопрос:

Я начну с того, что скажу, что я новичок в ржавчине. На самом деле, это первая программа Rust, которую я пытаюсь написать.

Я могу прочитать (большой) файл строка за строкой и проверить, какие строки содержат шаблон "PerfectSwitch-0 : Message:" со следующим кодом:

 use std::fs::File;
use std::io::{self, prelude::*, BufReader};

fn main() -> io::Result<()>{
    let file = File::open("../test.out")?;
    let reader = BufReader::new(file);

    for line in reader.lines(){
        let line = line.unwrap();
        if line.contains("PerfectSwitch-0: Message:"){
            println!("{}", line);
        }
    }

    Ok(())
}
 

Однако на самом деле я хочу изменить этот код таким образом, чтобы мой шаблон соответствовал "PerfectSwitch-0 : Message:" , "PerfectSwitch-1 : Message:" , "PerfectSwitch-2 : Message:" , …, "PerfectSwitch-8 : Message:" и "PerfectSwitch-9 : Message:" без регулярного выражения.

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

Я пробовал писать if line.contains("PerfectSwitch-?: Message:") , но, что неудивительно, это не сработало.

Кто-нибудь знает, возможно ли это?

Спасибо

Комментарии:

1. Почему регулярное выражение является излишним? Почему вы думаете, что это замедлит вашу программу? Может быть, это ускорит процесс! Почему бы не попробовать?

2. Я не уверен. Как я уже сказал, это мой первый опыт работы с ржавчиной. Казалось «проще» не использовать регулярное выражение. Но я определенно попробую. Если возможно делать то, что я хочу, без регулярных выражений, было бы неплохо провести некоторые сравнения.

3. Я думаю, что использовать регулярное выражение для этого конкретного случая было бы проще. В стандартной библиотеке нет средств сопоставления шаблонов. Таким образом, вам нужно будет либо создать свой собственный (не простой), либо просто реализовать свой собственный небольшой синтаксический анализатор (возможно, простой, но не такой простой, как простое использование регулярного выражения).

4. Ответ разрешил ваш вопрос?

Ответ №1:

Я бы попробовал regex в этом случае и сначала посмотрел, соответствует ли он вашим требованиям к производительности.

Один из плюсов, на мой взгляд, regex заключается в том, что его легче анализировать и изменять, когда вы возвращаетесь к коду.

Например, учитывая следующие входные данные для анализа:

 let input = vec![
    "PerfectSwitch-42 : Message:",
    "PerfectSwitch- : Message:",
    "Message :",
    "PerfectSwitch-271828 : Message:",
    "PerfectSwitch-314159 : Message:",
    "PerfectSwitch-",
];
 

Мы могли бы сделать следующее:

 use regex::Regex;

fn main() {
    let re = Regex::new(r"^PerfectSwitch-[0-9]  : Message:").unwrap();

    let result = input
        .iter()
        .filter(|amp;s| re.is_match(amp;s))
        .collect::<Vec<_>>();
}
 

Или напишите грубое рукописное решение:

 fn contains_switch(s: amp;str) -> bool {
    let mut cursor = 0;
    
    // Return early if the string is not at least as long as:
    // - The length of "PerfectSwitch-" (14)
    // - One or more ASCII digit(s)     (1..)
    // - One ASCII whitespace           (1) 
    // - The length of ": Message:"     (10) 
    if s.len() < 26 {
        return false;
    }
    
    // Match on and consume "PerfectSwitch-"
    if amp;s[..14] !=  "PerfectSwitch-" {
        return false;
    }
    cursor  = 14;

    // Match on and consume ASCII digits
    let digits = s[cursor..].bytes().take_while(u8::is_ascii_digit).count();
    if digits == 0 {
        return false;
    }
    cursor  = digits;
    
    // Match on and consume ASCII whitespace
    if amp;s[cursor..cursor   1] != " " {
        return false;
    }
    cursor  = 1;
    
    // Match on and consume ": Message:"
    if s.len() < cursor   10 {
        return false;
    }
    amp;s[cursor..cursor   10] == ": Message:"
}

fn main() {
    let result = input
        .iter()
        .filter(|amp;s| contains_switch(s))
        .collect::<Vec<_>>();
}
 

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

В обоих случаях это должно дать вам:

 [
    "PerfectSwitch-42 : Message:",
    "PerfectSwitch-271828 : Message:",
    "PerfectSwitch-314159 : Message:",
]
 

Показатель

Повторяя более 1 000 000 случайно сгенерированных строк, glassbench сопоставляя их , мы получаем следующее:

 ┌─┬───────────────┬──────────────┬─────────────┐
│#│     task      │total duration│mean duration│
├─┼───────────────┼──────────────┼─────────────┤
│1│re_is_match    │  2.641099049s│   52.82198ms│
│2│contains_switch│  1.999254015s│    7.37732ms│
└─┴───────────────┴──────────────┴─────────────┘
 

Учитывая вышеуказанные результаты и компромисс в обслуживании и удобочитаемости, я бы действительно предпочел использовать regex ящик.

Комментарии:

1. Избегать ящика с регулярными выражениями вообще не кажется хорошей идеей, если только вам не нужно, чтобы двоичный файл был небольшим. Это очень хорошо настроенный ящик и часто самое эффективное (и удобное) решение.

2. Кроме того, случайно сгенерированные данные, вероятно, являются одним из худших случаев для ящика регулярных выражений. 🙂

Ответ №2:

Вы можете перебирать все возможные значения:

 let line = line.unwrap();
for i in 0..=9 {
    if line.contains(amp;format!("PerfectSwitch-{}: Message:", i)) {
        println!("{}", line);
    }
}
 

Хотя вы, вероятно, захотите пересмотреть свое предположение о том, что регулярные выражения плохи. regex Библиотека Rust работает очень быстро, и я сомневаюсь, что какой-либо небольшой прирост производительности, который вы получите здесь, перевесит недостаток ремонтопригодности, возникающий при развертывании вашего собственного кода синтаксического анализа.