Как условно связать итераторы?

Допустим, у меня есть:

let it = [1, 2, 3].into_iter();
let jt = [4, 5, 6].into_iter();
let kt = [7, 8, 9].into_iter();

Тогда у меня есть логические условия i, j и k. Я хочу создать итератор, который условно связывает it, jt и kt вместе на основе значений i, j и k. Могу ли я сделать это, используя только встроенную функциональность Rust Iterator?

Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
2
0
109
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вы столкнетесь с небольшой проблемой, если захотите использовать голые итераторы:

Если написать следующее:

let iter = [1, 2, 3].into_iter();
let iter = if some_condition {
  iter.chain([4, 5, 6])
} else {
  iter
}

Вы получите ошибку, которая сводится к следующему:

  = note: expected struct `std::iter::Chain<std::array::IntoIter<_, _>, std::array::IntoIter<{integer}, 3>>`
             found struct `std::array::IntoIter<_, _>`

iter имеет тип IntoIter, но iter.chain() имеет тип Chain<IntoIter, ...>

Чтобы обойти это, у вас есть несколько вариантов:

  • вы можете использовать трейт-объект, который немного похож на interface из таких языков, как Java, но немного теряет производительность:
let iter = [1, 2, 3].into_iter();
let mut iter: Box<dyn Iterator<Item = i32>> = Box::new(iter);
if some_condition {
  iter = Box::new(iter.chain([4, 5, 6]));
}
  • или, возможно, лучшее решение, если вы можете пожертвовать ленью, просто использовать Vec:
// save heap allocations by pre-allocating the whole vec
let len = if some_condition { 6 } else { 3 };  
let mut items = Vec::with_capacity(len);

items.extend([1, 2, 3]);
if some_condition {
  items.extend([4, 5, 6]);
}
Ответ принят как подходящий

Вы можете превратить Option в итератор.

let it = i.then_some([1, 2, 3]).into_iter().flatten();
let jt = j.then_some([4, 5, 6]).into_iter().flatten();
let kt = k.then_some([7, 8, 9]).into_iter().flatten();
let iter = it.chain(jt).chain(kt);

Если условие ложно, то condition.then_some(...) вернет None, создав пустой итератор. В противном случае возвращается Some(...). into_iter().flatten() превратит Option<impl IntoIterator<Item=T>> в impl Iterator<Item=T>.

Это хорошее применение ящику либо. Either реализует Iterator, когда и левая, и правая стороны также реализуют Iterator, поэтому его можно легко использовать для объединения итераторов в цепочку.

Имея любые три итератора it, jt и kt, которые выполняют итерацию по одному и тому же Item, с сопутствующими логическими значениями i, j и k, вы можете написать функцию, которая связывает их вместе следующим образом:

use either::Either;
use std::iter;

fn chain<'a, I, J, K, Item>(
    it: I,
    jt: J,
    kt: K,
    i: bool,
    j: bool,
    k: bool,
) -> iter::Chain<
    iter::Chain<Either<I, iter::Empty<Item>>, Either<J, iter::Empty<Item>>>,
    Either<K, iter::Empty<Item>>,
>
where
    I: Iterator<Item = Item>,
    J: Iterator<Item = Item>,
    K: Iterator<Item = Item>,
{
    let iter = if i {
        Either::Left(it)
    } else {
        Either::Right(iter::empty())
    };
    let iter = iter.chain(if j {
        Either::Left(jt)
    } else {
        Either::Right(iter::empty())
    });
    let iter = iter.chain(if k {
        Either::Left(kt)
    } else {
        Either::Right(iter::empty())
    });
    iter
}

Вызов этой функции приведет к условному вводу итератора. Например, вызов

let it = [1, 2, 3].into_iter();
let jt = [4, 5, 6].into_iter();
let kt = [7, 8, 9].into_iter();

chain(it, jt, kt, true, false, true).collect::<Vec<_>>();

дает

[1, 2, 3, 7, 8, 9]

как и ожидалось.

Вы можете попробовать это, используя эту игровую площадку.

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