rust - Iterar condicionalmente sobre uno de varios iteradores posibles
(4)
Aquí hay una variación de la brillante solución de @ Niko usando una sola expresión de
match
lugar de varias expresiones
if let
, que puede ser más conveniente cuando se trata de casos más condicionales:
use std::iter;
fn main() {
let x: Option<i64> = None;
// Repeat x 5 times if present, otherwise count from 1 to 5
for i in pick(x) {
println!("{}", i);
}
}
fn pick(opt_x: Option<i64>) -> impl Iterator<Item = i64> {
let mut iter_a = None;
let mut iter_b = None;
match opt_x {
None => iter_a = Some(1..5),
Some(x) => iter_b = Some(iter::repeat(x).take(5)),
}
iter_a.into_iter().flatten().chain(iter_b.into_iter().flatten())
}
Estoy tratando de cambiar el comportamiento basado en una entrada de
Option
a una función.
La idea es iterar en función de si una
Option
dada está presente o no.
Aquí hay un ejemplo mínimo, aunque tonto:
use std::iter;
fn main() {
let x: Option<i64> = None;
// Repeat x 5 times if present, otherwise count from 1 to 5
for i in match x {
None => 1..5,
Some(x) => iter::repeat(x).take(5),
} {
println!("{}", i);
}
}
Me sale un error:
error[E0308]: match arms have incompatible types
--> src/main.rs:7:14
|
7 | for i in match x {
| ______________^
8 | | None => 1..5,
9 | | Some(x) => iter::repeat(x).take(5),
| | ----------------------- match arm with an incompatible type
10 | | } {
| |_____^ expected struct `std::ops::Range`, found struct `std::iter::Take`
|
= note: expected type `std::ops::Range<{integer}>`
found type `std::iter::Take<std::iter::Repeat<i64>>`
Esto tiene mucho sentido, por supuesto, pero realmente me gustaría elegir mi iterador en función de una condición, ya que el código en el ciclo for no es trivial y copiar todo eso solo para cambiar la selección del iterador sería bastante feo e insostenible.
Intenté usar
as Iterator<Item = i64>
en ambos brazos, pero eso me da un error sobre los tipos sin tamaño porque es un objeto de rasgo.
¿Hay una manera fácil de hacerlo?
Podría, por supuesto, usar
.collect()
ya que devuelven el mismo tipo e iteran sobre ese vector.
Lo cual es una buena solución rápida, pero para listas grandes parece un poco excesivo.
Debe tener una referencia a un rasgo :
use std::iter;
fn main() {
let mut a;
let mut b;
let x: Option<i64> = None;
// Repeat x 5 times if present, otherwise count from 1 to 5
let iter: &mut Iterator<Item = i64> = match x {
None => {
a = 1..5;
&mut a
}
Some(x) => {
b = iter::repeat(x).take(5);
&mut b
}
};
for i in iter {
println!("{}", i);
}
}
El principal inconveniente de esta solución es que debe asignar espacio de pila para cada tipo de concreto que tenga. Esto también significa variables para cada tipo. Lo bueno es que solo el tipo utilizado debe inicializarse.
La misma idea pero que requiere la asignación del montón es usar objetos de rasgos en caja :
use std::iter;
fn main() {
let x: Option<i64> = None;
// Repeat x 5 times if present, otherwise count from 1 to 5
let iter: Box<Iterator<Item = i64>> = match x {
None => Box::new(1..5),
Some(x) => Box::new(iter::repeat(x).take(5)),
};
for i in iter {
println!("{}", i);
}
}
Esto es principalmente útil cuando desea devolver el iterador de una función . El espacio de pila ocupado es un puntero único, y solo se asignará el espacio de almacenamiento dinámico necesario.
Personalmente, en lugar de usar
Either
, a menudo prefiero crear una serie de valores de
Option<Iterator>
que se encadenan.
Algo como esto:
use std::iter;
fn main() {
let x: Option<i64> = None;
// Repeat x 5 times if present, otherwise count from 1 to 5
for i in pick(x) {
println!("{}", i);
}
}
fn pick(opt_x: Option<i64>) -> impl Iterator<Item = i64> {
let iter_a = if let None = opt_x {
Some(1..5)
} else {
None
};
let iter_b = if let Some(x) = opt_x {
Some(iter::repeat(x).take(5))
} else {
None
};
iter_a.into_iter().flatten().chain(iter_b.into_iter().flatten())
}
Es un poco menos obvio que usar
Either
, pero evita otra caja y, a veces, funciona con bastante elegancia.
Either
las
cajas
proporciona el tipo
Either
.
Si ambas mitades de
Either
son iteradores, entonces también lo es
Either
:
extern crate either;
use either::Either;
use std::iter;
fn main() {
let x: Option<i64> = None;
// Repeat x 5 times if present, otherwise count from 1 to 5
let iter = match x {
None => Either::Left(1..5),
Some(x) => Either::Right(iter::repeat(x).take(5)),
};
for i in iter {
println!("{}", i);
}
}
Al igual que una respuesta anterior , esto todavía ocupa espacio en la pila para cada tipo concreto que tenga. Sin embargo, no necesita variables individuales para cada valor concreto.
Este tipo también se puede devolver desde una función , a diferencia de las referencias de objeto de rasgo. En comparación con los objetos de rasgos en caja, siempre usará un tamaño fijo en la pila, independientemente del tipo de concreto elegido.
Encontrará este tipo (o equivalente semántico) en otros lugares también, como
futures::Either