es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

¿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,>

Tags:  ,

Answer

  1. Avatar for 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 que Split sea dueño del iterador y del separador. Podemos implementar Split de manera similar a cómo se implementa SplitMut en la biblioteca estándar de Rust.

    struct Split<i, t=""> {
        iter: std::iter::Peekable<><i>>,
        sep: T,
        finished: bool,
    }
    
    impl<i, t=""> Split<i, t=""> 
    where
        I: Iterator<item =="" t="">,
        T: PartialEq,
    {
        fn new(iter: I, sep: T) -> Split<i, t=""> {
            Split {
                iter: iter.enumerate().peekable(),
                sep,
                finished: false,
            }
        }
    }
    
    impl<i, t=""> Iterator for Split<i, t=""> 
    where
        I: Iterator<item =="" t="">,
        T: PartialEq,
    {
        type Item = Vec<t>;
    
        fn next(&mut self) -> Option<self::item> {
            if self.finished {
                return None;
            }
    
            let mut result = Vec::new();
    
            loop {
                match self.iter.next() {
                    Some((_, val)) if val == &self.sep => {
                        return Some(result);
                    }
                    Some((_, val)) => {
                        result.push(val);
                    }
                    None => {
                        self.finished = true;
                        return if result.is_empty() {
                            None
                        } else {
                            Some(result)
                        }
                    }
                }
            }
        }
    }
    

    La estructura Split ahora es propietaria del iterador y del separador, y usamos enumerate 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 devolver None 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étodo new con el iterador y el separador como argumentos, y luego iterar sobre él:

    let v = vec![1, 2, 3, 0, 1, 2, 0, 4, 5, 0];
    
    for inner_iter in Split::new(v.into_iter(), 0) {
        inner_iter.iter().for_each(|x| print!("{} ", x));
        println!();
    }
    

    Tenga en cuenta que debemos pasar el iterador a Split como un iterador en propiedad (v.into_iter()), ya que Split 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,>

Comments are closed.