Каков эффективный способ синтаксического анализа строки односимвольных команд, каждая из которых необязательно сопровождается целым числом повторений? - PullRequest
0 голосов
/ 02 октября 2018

Я внедряю робота, который принимает такие приказы, как L (поворот налево), R (поворот направо) и M (движение вперед).Эти ордера могут быть увеличены с помощью квантификатора типа M3LMR2 (передвинуть на 3 шага, повернуть налево, сделать один шаг, повернуть лицо).Это эквивалент MMMLMRR.

Я закодировал структуру робота, которая может понимать следующее перечисление:

pub enum Message {                                                                                                                                                            
    TurnLeft(i8),                                                                                                                                                             
    TurnRight(i8),                                                                                                                                                            
    MoveForward(i8),                                                                                                                                                          
}

Robot::execute(&mut self, orders: Vec<Message>) выполняет свою работу правильно.

Теперь я изо всех сил пытаюсь написать что-то приличное для разбора строк, жонглирования с &str, String, char и небезопасных срезов, потому что токены могут содержать 1 или более символов.

Я пробовал сопоставление с регулярным выражением (почти сработало), но я действительно хочу токенизировать строку:

fn capture(orders: &String, start: &usize, end: &usize) -> Message {
    unsafe {
        let order = orders.get_unchecked(start..end);
        // …
    };

    Message::TurnLeft(1) // temporary
}

pub fn parse_orders(orders: String) -> Result<Vec<Message>, String> {
    let mut messages = vec![];
    let mut start: usize = 0;
    let mut end: usize = 0;

    while end < orders.len() && end != start {
        end += 1;

        match orders.get(end) {
            Some('0'...'9') => continue,
            _ => {
                messages.push(capture(&orders, &start, &end));
                start = end;
            }
        }
    }

    Ok(messages)
}

Это не компилируется и неуклюже.

Идея состоит в том, чтобы написать синтаксический анализатор, который превратит строку заказа в вектор Message:

let messages = parse_order("M3LMR2");
println!("Messages => {:?}", messages);
// would print
// [Message::MoveForward(3), Message::TurnLeft(1), Message::MoveForward(1), Message::TurnRight(2)]

Каким будет эффективный / элегантный способ сделать это?

1 Ответ

0 голосов
/ 02 октября 2018

Вы можете сделать это очень просто с помощью итератора, используя parse и некоторую базовую обработку String:

#[derive(Debug, PartialEq, Clone)]
enum Message {                                                                                                                                                            
    TurnLeft(u8),                                                                                                                                                             
    TurnRight(u8),                                                                                                                                                            
    MoveForward(u8),                                                                                                                                                          
}

struct RobotOrders(String);

impl RobotOrders {
    fn new(source: impl Into<String>) -> Self {
        RobotOrders(source.into())
    }
}

impl Iterator for RobotOrders {
    type Item = Message;

    fn next(&mut self) -> Option<Message> {
        self.0.chars().next()?;
        let order = self.0.remove(0);
        let n_digits = self.0.chars().take_while(char::is_ascii_digit).count();
        let mut number = self.0.clone();
        self.0 = number.split_off(n_digits);
        let number = number.parse().unwrap_or(1);

        Some(match order {
            'L' => Message::TurnLeft(number),
            'R' => Message::TurnRight(number),
            'M' => Message::MoveForward(number),
            _ => unimplemented!(),
        })
    }
}

fn main() {
    use Message::*;
    let orders = RobotOrders::new("M3LMR2");
    let should_be = [MoveForward(3), TurnLeft(1), MoveForward(1), TurnRight(2)];

    assert!(orders.eq(should_be.iter().cloned()));
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...