Каков канонический способ взять один дополнительный элемент в итераторе после сбоя предиката?

#rust #iterator

#Ржавчина #итератор

Вопрос:

У меня есть Vec<bool> , и я хочу создать итератор, который останавливается на первом и включает false его; Как я могу это реализовать? Я рассматривал take_while метод, но он останавливает один элемент раньше. Например, если входные данные были vec![true, true, true, false, false] , мне нужен итератор, эквивалентный vec![true, true, true, false].iter() , но я не могу найти хороший способ выразить это функциональным способом. Я рассмотрел следующие методы:

 // method 1
// This one triggers the side effects of do_something_with_x
// for the right elements, but it seems like bad style. It
// also doesn't give me an iterator that I can chain on, I
// would need to use the side effect creatively.
input
    .iter()
    .map(|x| {
        do_something_with_x(x);
        x
    })
    .take_while(|x| *x)
    .for_each(|_|());

// method 2
// This seems a little better, but I don't like how I have
// to iterate through it twice. My code isn't performance
// sensitive so that's not the reason, but it just looks bad to me.
let len = input.iter().position(|x| !*x);
let result = input.iter().take(len);
 

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

Ответ №1:

Вы можете использовать Iterator::scan , который создает новый итератор, вызывая замыкание с каждым элементом и изменяемым состоянием.

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

 input
    .iter()
    .scan(false, |is_done, item| {
        if *is_done { return None; }
        if !item { *is_done = true; }
        Some(item)
    })
    .for_each(|_| {});