#rust #iteration
Вопрос:
Я ищу способы прервать итерацию бесконечного итератора. Я обнаружил, что try_fold
это соответствует моей цели. Однако для этого необходимо сделать неловкую вещь-вернуться Err
к успешному делу. Что я хочу понять, так это то, является ли это идиоматическим способом делать вещи. Единственный другой способ , который я могу придумать для этого, — использовать обычное for
или что-то вроде find
сохранения внешнего состояния (что кажется еще более странным!). Я знаю , что в клоджуре есть reduced
, но я не смог найти эквивалент для ржавчины.
Вот минимальный жизнеспособный пример. Пример циклически обходит начальный Vec, суммируя каждый элемент по ходу, и останавливается на первой сумме, превышающей 10. Т. е. он возвращает 12, потому что 1 5 - 3 1 5 - 3 1 5 = 12
:
fn main() {
let seed = vec![1, 5, -3];
let res = seed.iter().cycle().try_fold(0, |accum, value| {
let next = accum value;
if next > 10 {
Err(next)
} else {
Ok(next)
}
});
if let Err(res) = res {
println!("{:?}", res);
} else {
unreachable!();
}
}
Часть, которая кажется мне странной if let Err(res) = res
, — это то, что она является положительным условием и действительно единственным выходом из цикла (следовательно, почему другая ветвь недоступна).
Есть ли «лучший» способ?
Комментарии:
1. Я бы посоветовал просто не делать этого. Лучше поговорите о своей конкретной проблеме.
Ответ №1:
Просто, просто повторите обычную итерацию
fn main() {
let seed = vec![1, 5, -3];
let mut accum = 0;
for value in seed.iter().cycle() {
accum = value;
if accum > 10 {
break;
}
}
println!("{:?}", accum)
}
Ответ №2:
Вы можете использовать scan
и find
:
fn main() {
let seed = vec![1, 5, -3];
let res = seed
.iter()
.cycle()
.scan(0, |accum, value| {
*accum = value;
Some(*accum)
})
.find(|amp;x| x > 10)
.unwrap();
println!("{:?}", res);
}
На самом деле это немного короче , чем использование fold_while
from itertools
, которое выглядело бы так:
use itertools::{FoldWhile::{Continue, Done}, Itertools};
fn main() {
let seed = vec![1, 5, -3];
let res = seed
.iter()
.cycle()
.fold_while(0, |accum, value| {
let next = accum value;
if next > 10 {
Done(next)
} else {
Continue(next)
}
})
.into_inner();
println!("{:?}", res);
}
Комментарии:
1. О, я не знал об этом! Часть о том, чтобы иметь внутреннее состояние и подвергать внешнему итератору разные вещи, немного расплавляет мой мозг. Но я определенно изучу его дальше, чтобы немного лучше понять его семантику, спасибо!
Ответ №3:
Рассмотрите возможность использования itertools::fold_while, если вы не возражаете против использования хорошо зарекомендовавшей себя внешней библиотеки. Могут быть и другие полезные расширения, если вам нравится этот стиль программирования.
Комментарии:
1. Интересно, я не знал об итер-инструментах!
fold_while
действительно выглядит гораздо лучше подходящим для этой цели, чем мое странное решениеtry_fold
. Спасибо!2. Я также признаю, что, поскольку itertools не является частью stdlib, это, вероятно, не «идиоматично», и
for
решение, предложенное в другом ответе, было бы более распространенным способом сделать это. Я приму этот ответ, потому что он научил меня новой библиотеке, но принятие другого, вероятно, было бы столь же адекватным.3. @jgpaiva В Rust использование внешних ящиков является нормой, и я бы даже сказал, что использование популярных ящиков идиоматично, если это делает ваш код чище.
Ответ №4:
На сегодняшний день нет очевидного ответа на ваш вопрос, что на самом деле очень удивительно. Как предположил @peter-hall, scan
это идиоматический функциональный ответ на проблему, но scan
известно , что Rust некрасив в использовании.
На мой взгляд try_fold
, остается самый ржавый вариант. Вот мои комбинированные предложения, чтобы сделать петлевые складки более удовлетворительными (выберите одно или несколько изменений!) :
let res = {
use Result::{Err as Break, Ok as Next};
seed.iter()
.cycle()
.try_fold(0, |accum, value| match accum value {
res if res > 10 => Break(res),
next => Next(next),
})
.unwrap_err()
};
Использование a match
помогает называть случаи лучше, чем if
/ else
на мой взгляд. Псевдонимы Result
типов также дают некоторую семантику, описывающую разрыв отступа.
Если вы не хотите использовать unwrap_err
, кто-то предложил заменить его на
let res = iter.try_fold(...).unwrap_or_else(|res| res);
Также можно использовать неопровержимый шаблон:
let (Ok(res) | Err(res)) = iter.try_fold(...);
Если бы это было для моего собственного кода, я бы сделал все как можно проще и ванильнее:
let res = seed
.iter()
.cycle()
.try_fold(0, |accum, value| match accum value {
res if res > 10 => Err(res),
acc => Ok(acc),
})
.unwrap_err();
Чтение .unwrap_err()
достаточно ясно для меня, потому что это означает, что Err
тип возврата, который мы ожидаем. Это мой лучший выстрел, ура!