¿Cómo puedo dividir un iterador por un separador en un iterador de iteradores?
Estoy tratando de implementar un adaptador de iterador que sea similar a std::views::split
de las rangos de C++, pero me he perdido en el sistema de tipos y las duraciones de Rust.
Más específicamente, quiero tener una función iter_split
que tome un iterador y un separador, y produzca una serie de iteradores de manera que un nuevo iterador comienza cuando el iterador original emite el separador. Es mejor ilustrarlo con el siguiente ejemplo:
let v = vec![1, 2, 3, 0, 1, 2, 0, 4, 5, 0];
for inner_iter in iter_split(v.iter(), &0) {
inner_iter.for_each(|x| print!("{} ", x));
println!();
}
La salida esperada es:
1 2 3
1 2
4 5
Intenté definirlo de la siguiente manera (en este punto es más como un pseudocódigo):
struct Split<i, t=""> {
iter: I,
sep: T,
}
impl<i, t=""> Split<i, t=""> {
fn new(iter: I, sep: T) -> Split<i, t=""> {
Split { iter, sep }
}
}
impl<i, t=""> Iterator for Split<i, t="">
where
I: Iterator<item =="" t="">,
T: std::cmp::PartialEq,
{
type Item = /* ??? */;
fn next(&mut self) -> Option<self::item> {
let inner_iter = self.iter.by_ref().take_while(|x| x != self.sep).peekable();
match inner_iter.peek() {
None => None,
Some(_) => Some(inner_iter),
}
}
}
fn iter_split<'a, I, T>(iter: I, sep: T) -> Split<i, t="">
where
I: Iterator<item =="" t="">,
T: std::cmp::PartialEq,
{
Split::new(iter, sep)
}
Este enfoque tiene varios problemas:
* No sé qué escribir como el tipo para Item
: debería ser algo como Peekable<takewhile<...>>
, pero como paso el iterador (y técnicamente el separador) por referencia, también debo tener en cuenta sus duraciones, y no estoy acostumbrado a ellas todavía.
* No puedo usar un closure ya que no podré devolverlo tal cual. Sin embargo, esto al menos se puede aliviar definiendo otro adaptador que se comporte como TakeWhile
, pero en lugar de verificar un predicado, compare la salida con el separador.
Parece que probablemente pueda resolver algunos de ellos utilizando Box
, pero no estoy seguro. ¿Puedo hacerlo sin cajas?</takewhile<…></i,></i,></i,></i,></i,></i,></i,>
davy.ai
El problema con la implementación actual es que la estructura
Split
está tratando de contener una referencia tanto al iterador como al separador, lo que significa que la vida útil de la estructura está vinculada a la vida útil tanto del iterador como del separador. Para evitar problemas de vida útil, podemos cambiar nuestro enfoque y hacer queSplit
sea dueño del iterador y del separador. Podemos implementarSplit
de manera similar a cómo se implementaSplitMut
en la biblioteca estándar de Rust.La estructura
Split
ahora es propietaria del iterador y del separador, y usamosenumerate
para obtener el índice de los elementos para poder realizar un seguimiento de dónde se encuentra el separador. También realizamos un seguimiento de si el iterador se ha consumido por completo o no, para poder devolverNone
después del último subiterador.El método
next
funciona iterando sobre el iterador interno y agregando elementos a un vector de resultados hasta que nos encontremos con el separador o el iterador se agote. Luego, devolvemos el vector de resultados como el siguiente elemento.Para usar esta estructura
Split
, simplemente podemos llamar a su métodonew
con el iterador y el separador como argumentos, y luego iterar sobre él:Tenga en cuenta que debemos pasar el iterador a
Split
como un iterador en propiedad (v.into_iter()
), ya queSplit
ahora es propietario del iterador.Esta implementación no requiere el uso de
Box
y debería funcionar como se esperaba.</i,></i,></i,></i,></i,></i,>