Как я могу перебрать строку с разделителями, накапливая состояние из предыдущих итераций без явного отслеживания состояния? - PullRequest
2 голосов
/ 26 февраля 2020

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

Например, для данной строки "ab:cde:fg", итератор должен вернуть следующее:

  1. "ab"
  2. "ab:cde"
  3. "ab:cde:fg"

Простое решение

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

let mut state = String::new();
for part in "ab:cde:fg".split(':') {
    if !state.is_empty() {
        state.push_str(":");
    }
    state.push_str(part);
    dbg!(&state);
}

Недостатком здесь является необходимость явно отслеживать состояние с помощью дополнительной изменяемой переменной.

Использование scan

Я думал, что scan можно использовать для скрытия состояния:

    "ab:cde:fg"
        .split(":")
        .scan(String::new(), |state, x| {
            if !state.is_empty() {
                state.push_str(":");
            }
            state.push_str(x);
            Some(&state)
        })
        .for_each(|x| { dbg!(x); });

Однако эта ошибка завершается ошибкой:

не может вывести подходящее время жизни для выражения заимствования из-за противоречивых требований

Что такое проблема с scan v ersion и как это можно исправить?

Ответы [ 2 ]

4 голосов
/ 26 февраля 2020

Зачем даже строить новую строку? Вы можете получить индексы : и использовать срезы для исходной строки.

fn main() {
    let test = "ab:cde:fg";

    let strings = test
        .match_indices(":")            // get the positions of the `:`
        .map(|(i, _)| &test[0..i])     // get the string to that position
        .chain(std::iter::once(test)); // let's not forget about the entire string

    for substring in strings {
        println!("{:?}", substring);
    }
}

( Постоянная ссылка на игровую площадку )

1 голос
/ 26 февраля 2020

Прежде всего, давайте обманем и получим ваш код для компиляции, чтобы мы могли проверить проблему под рукой. Мы можем сделать это путем клонирования state. Кроме того, давайте добавим некоторое сообщение отладки:

fn main() -> () {
    "ab:cde:fg"
        .split(":")
        .scan(String::new(), |state, x| {  // (1)
            if !state.is_empty() {
                state.push_str(":");
            }
            state.push_str(x);
            eprintln!(">>> scan with {} {}", state, x);
            Some(state.clone())
        })
        .for_each(|x| {                    // (2)
            dbg!(x);
        });
}

Это приводит к следующему выводу :

scan with ab ab
[src/main.rs:13] x = "ab"
scan with ab:cde cde
[src/main.rs:13] x = "ab:cde"
scan with ab:cde:fg fg
[src/main.rs:13] x = "ab:cde:fg"

Обратите внимание, как eprintln! и dbg! выводит чередуются? Это результат Iterator лень . Однако на практике это означает, что наш промежуточный String заимствуется дважды:

  • в анонимной функции |state, x| в state (1)
  • в анонимной функции |x| in, ну, x (2)

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

Самый простой выход - clone. Более разумный выход использует match_indices и строковые фрагменты.

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