web-dev-qa-db-fra.com

Existe-t-il des équivalents à slice :: chunks / windows pour que les itérateurs bouclent sur des paires, des triplets, etc.?

Il peut être utile de parcourir plusieurs variables à la fois, en chevauchant ( slice::windows ), ou non ( slice::chunks ).

Cela ne fonctionne que pour les tranches; est-il possible de le faire pour les itérateurs, en utilisant des tuples pour plus de commodité?

Quelque chose comme ce qui suit pourrait être écrit:

for (prev, next) in some_iter.windows(2) {
    ...
}

Sinon, pourrait-il être implémenté comme un trait sur les itérateurs existants?

20
ideasman42

TL; DR: La meilleure façon d'avoir chunks et windows sur un itérateur/collection arbitraire est de commencer par collect dans un Vec et itérer sur que .


La syntaxe exacte demandée est impossible dans Rust.

Le problème est que dans Rust, la signature d'une fonction dépend des types , et non des valeurs , et bien que la typologie dépendante existe, peu de langages l'implémentent (c'est difficile).

C'est pourquoi chunks et windows renvoient une sous-tranche en passant; le nombre d'éléments dans un &[T] ne fait pas partie du type et peut donc être décidé au moment de l'exécution.


Imaginons que vous ayez demandé: for slice in some_iter.windows(2) à la place.

Où le stockage sur lequel cette tranche se trouverait-il?

Il ne peut pas vivre:

  • dans la collection d'origine car un LinkedList n'a pas de stockage contigu
  • dans l'itérateur en raison de la définition de Iterator::Item, il n'y a pas de durée de vie disponible

Donc, malheureusement, les tranches ne peuvent être utilisées que lorsque le stockage de sauvegarde est une tranche.


Si les allocations dynamiques sont acceptées, il est possible d'utiliser Vec<Iterator::Item> Comme Item de l'itérateur de segmentation.

struct Chunks<I: Iterator> {
    elements: Vec<<I as Iterator>::Item>,
    underlying: I,
}

impl<I: Iterator> Chunks<I> {
    fn new(iterator: I, size: usize) -> Chunks<I> {
        assert!(size > 0);

        let mut result = Chunks {
           underlying: iterator, elements: Vec::with_capacity(size)
        };
        result.refill(size);
        result
    }

    fn refill(&mut self, size: usize) {
        assert!(self.elements.is_empty());

        for _ in 0..size {
            match self.underlying.next() {
                Some(item) => self.elements.Push(item),
                None => break,
            }
        }
    }
}

impl<I: Iterator> Iterator for Chunks<I> {
    type Item = Vec<<I as Iterator>::Item>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.elements.is_empty() {
            return None;
        }

        let new_elements = Vec::with_capacity(self.elements.len());
        let result = std::mem::replace(&mut self.elements, new_elements);

        self.refill(result.len());

        Some(result)
    }
}

fn main() {
    let v = vec!(1, 2, 3, 4, 5);

    for slice in Chunks::new(v.iter(), 2) {
        println!("{:?}", slice);
    }
}

Reviendra:

[1, 2]
[3, 4]
[5]

Le lecteur avisé se rendra compte que je suis passé subrepticement de windows à chunks.

windows est plus difficile, car il renvoie plusieurs fois le même élément, ce qui nécessite que l'élément soit Clone. De plus, comme il doit renvoyer un Vec complet à chaque fois, il devra en interne conserver un Vec<Vec<Iterator::Item>>.

Ceci est laissé comme exercice au lecteur.


Enfin, une note sur performance: toutes ces allocations vont faire mal (surtout dans le cas windows).

La meilleure stratégie d'allocation consiste généralement à allouer un seul morceau de mémoire, puis à en vivre (à moins que la quantité ne soit vraiment énorme, auquel cas le streaming est requis).

Cela s'appelle collect::<Vec<_>>() dans Rust.

Et puisque Vec a une méthode chunks et windows (grâce à l'implémentation de Deref<Target=[T]>), Vous pouvez alors l'utiliser à la place:

for slice in v.iter().collect::<Vec<_>>().chunks(2) {
    println!("{:?}", slice);
}

for slice in v.iter().collect::<Vec<_>>().windows(2) {
    println!("{:?}", slice);
}

Parfois, les meilleures solutions sont les plus simples.

14
Matthieu M.

Il est possible de prendre des morceaux d'un itérateur en utilisant Itertools::tuples , jusqu'à 4 tuples:

extern crate itertools;    

use itertools::Itertools;

fn main() {
    let some_iter = vec![1, 2, 3, 4, 5, 6].into_iter();

    for (prev, next) in some_iter.tuples() {
        println!("{}--{}", prev, next);
    }
}

( aire de jeux )

1--2
3--4
5--6

Ainsi que jusqu'à 4 fenêtres Tuple avec Itertools::Tuple_windows :

extern crate itertools;

use itertools::Itertools;

fn main() {
    let some_iter = vec![1, 2, 3, 4, 5, 6].into_iter();

    for (prev, next) in some_iter.Tuple_windows() {
        println!("{}--{}", prev, next);
    }
}

( aire de jeux )

1--2
2--3
3--4
4--5
5--6
13
Shepmaster