Передать значение из итератора, не останавливая итерацию

У меня есть функция, которая анализирует входную строку:

fn parse_input(s: &str) -> ((usize, usize), BTreeMap<(usize, usize), Tile>){
 let mut start: (usize, usize) = (0,0);
 let grid = s.split("\n").enumerate().flat_map(|(r,l)| {
     l.chars().enumerate().map(move |(col, c)| {
         let t = classify_tile(c);
         match t {
             Tile::Start => {
                  *start = (r, col);
                  ((r,col), t)
                 },
             _ => ((r,col), t) 
         }
     })
 }).collect::<BTreeMap<(usize, usize), Tile>>();
 (start, grid)
}

По сути, я хочу зафиксировать значение r и col начальной плитки (которая уникальна, встречается только один раз). Но в настоящее время, если я попытаюсь изменить кортеж внутри итератора, я предполагаю, что из-за причин заимствования и области действия значение не будет изменено за пределами итератора. Однако важно, чтобы итератор завершился.

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

Должен ли я просто сделать это как вложенные циклы? Действительно ли итерация здесь более эффективна?

изменить: функция classify_tile возвращает тип перечисления. Старт, Почва или Труба. Раздел *start = (r,col) — это тот бит, который не работает. Это была моя попытка решить эту проблему. Хотя все остальное работает.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

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


Но давайте сначала разберемся, в чем проблема.

В качестве более простого примера давайте посчитаем все заглавные буквы (глупым способом):

let text = "Hello World";

let mut count = 0;

text.split_whitespace()
   .flat_map(|s| {
        s.chars().map(|c| {
            if c.is_uppercase() {
                count += 1;
            }
        })
    })
    .for_each(|_| {});

assert_eq!(count, 2);

Если мы попытаемся скомпилировать это, мы получим ту же проблему, что и вы:

error: captured variable cannot escape `FnMut` closure body
  --> src\main.rs:85:13
   |
80 |       let mut count = 0;
   |           --------- variable defined here
...
84 |           .flat_map(|s| {
   |                       - inferred to be a `FnMut` closure
85 | /             s.chars().map(|c| {
86 | |                 if c.is_uppercase() {
87 | |                     count += 1;
   | |                     ----- variable captured here
88 | |                 }
89 | |             })
   | |______________^ returns a closure that contains a reference to a captured variable, which then escapes the closure body
   |
   = note: `FnMut` closures only have access to their captured variables while they are executing...
   = note: ...therefore, they cannot allow references to captured variables to escape

Почему это?

Замыкание map() требует изменяемого заимствования count, это само по себе совершенно нормально.

Проблема возникает, когда мы добавляем в смесь flat_map(). Поскольку flat_map() потенциально создает несколько итераторов. Каждый из этих созданных итераторов требует изменяемого заимствования count. Это, конечно, недопустимо, поскольку мы не можем изменять что-либо более одного раза.


В целом, это легко решить, вы просто реструктурируете итератор:

text.split_whitespace()
    .flat_map(|s| s.chars())
    .map(|c| {
        if c.is_uppercase() {
            count += 1;
        }
    })
    .for_each(|_| {});

или

text.split_whitespace()
    .map(|s| s.chars())
    .flatten()
    .map(|c| {
        if c.is_uppercase() {
            count += 1;
        }
    })
    .for_each(|_| {});

Решение этой проблемы для вашего итератора может выглядеть примерно так:

let grid = s
    .split("\n")
    .enumerate()
    .flat_map(|(r, l)| {
        l.chars().enumerate().map(move |(col, c)| {
            let t = classify_tile(c);
            match t {
                Tile::Start => ((r, col), t),
                _ => ((r, col), t),
            }
        })
    })
    .inspect(|((r, col), t)| match t {
        Tile::Start => {
            start = (*r, *col);
        }
        _ => {}
    })
    .collect::<BTreeMap<(usize, usize), Tile>>();

или

let grid = s
    .split("\n")
    .enumerate()
    .flat_map(|(r, l)| l.chars().enumerate().map(move |(col, c)| ((r, col), c)))
    .map(|((r, col), c)| {
        let t = classify_tile(c);
        match t {
            Tile::Start => {
                start = (r, col);
                ((r, col), t)
            }
            _ => ((r, col), t),
        }
    })
    .collect::<BTreeMap<(usize, usize), Tile>>();

Его можно записать несколькими способами.

Потрясающее объяснение, спасибо! Я решил использовать опцию проверки, поскольку второе решение похоже на то, что ему придется дважды перебирать данные? Что сделало бы это аналогом простого поиска координаты в btreemap?

thefrollickingnerd 23.04.2024 07:11

Оба фактически делают одно и то же. В любом случае «двойная итерация» не происходит. Основная разница, конечно, в том, обрабатывается ли он в map() или применяется дополнительный inspect().

vallentin 23.04.2024 07:19

Разве каждая карта не должна каким-то образом обрабатывать данные? Значит, второй Солн просматривает данные несколько раз? Или я неправильно понимаю, как работают итераторы?

thefrollickingnerd 23.04.2024 08:14

Возможно, я неправильно истолковываю то, что вы подразумеваете под «двойной итерацией». Рассмотрим iter.map(a).map(b), тогда это не обрабатывается map 1 элемент 1, 2, 3, затем map 2 элемент 1, 2, 3. Это будет эффективно обрабатываться как for x in ... то x = b(a(x))

vallentin 23.04.2024 08:43

@thefrollickingnerd map ленив, поэтому вообще не перебирает данные. Что он делает, так это маркирует данные, чтобы при последующей итерации (если) они были преобразованы на лету. Множественные вызовы map просто добавляют этапы обработки, но они по-прежнему не перебирают данные. В вашем случае единственная итерация выполняется collect. Наличие нескольких map, каждый из которых добавляет один этап обработки, или наличие одного map, добавляющего несколько этапов обработки, фактически эквивалентно.

Jmb 23.04.2024 09:38

Спасибо за разъяснения, действительно прояснил вопрос недопонимания с моей стороны.

thefrollickingnerd 24.04.2024 02:51

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