У меня есть функция, которая анализирует входную строку:
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) — это тот бит, который не работает. Это была моя попытка решить эту проблему. Хотя все остальное работает.





Вы можете делать все, что хотите, если немного реструктурируете свой итератор.
Но давайте сначала разберемся, в чем проблема.
В качестве более простого примера давайте посчитаем все заглавные буквы (глупым способом):
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>>();
Его можно записать несколькими способами.
Оба фактически делают одно и то же. В любом случае «двойная итерация» не происходит. Основная разница, конечно, в том, обрабатывается ли он в map() или применяется дополнительный inspect().
Разве каждая карта не должна каким-то образом обрабатывать данные? Значит, второй Солн просматривает данные несколько раз? Или я неправильно понимаю, как работают итераторы?
Возможно, я неправильно истолковываю то, что вы подразумеваете под «двойной итерацией». Рассмотрим iter.map(a).map(b), тогда это не обрабатывается map 1 элемент 1, 2, 3, затем map 2 элемент 1, 2, 3. Это будет эффективно обрабатываться как for x in ... то x = b(a(x))
@thefrollickingnerd map ленив, поэтому вообще не перебирает данные. Что он делает, так это маркирует данные, чтобы при последующей итерации (если) они были преобразованы на лету. Множественные вызовы map просто добавляют этапы обработки, но они по-прежнему не перебирают данные. В вашем случае единственная итерация выполняется collect. Наличие нескольких map, каждый из которых добавляет один этап обработки, или наличие одного map, добавляющего несколько этапов обработки, фактически эквивалентно.
Спасибо за разъяснения, действительно прояснил вопрос недопонимания с моей стороны.
Потрясающее объяснение, спасибо! Я решил использовать опцию проверки, поскольку второе решение похоже на то, что ему придется дважды перебирать данные? Что сделало бы это аналогом простого поиска координаты в btreemap?