Iterator::peekable în Rust

Transformă iteratorul într-un Peekable, care te lasă să te uiți la următorul element (peek) fără să-l consumi.

Sintaxă
fn peekable(self) -> Peekable<Self>

impl<I: Iterator> Peekable<I> {
    pub fn peek(&mut self) -> Option<&I::Item>;
    pub fn next_if(&mut self, func: impl FnOnce(&I::Item) -> bool) -> Option<I::Item>;   // Rust 1.51
}

Ce face Iterator::peekable

Un iterator obișnuit nu îți arată ce urmează fără să-l scoată. peekable() îl învelește într-un Peekable, care are metoda peek(): returnează Option<&Item> cu următorul element, iar acesta rămâne disponibil pentru următorul next().

Este baza analizei de text pas cu pas (un „tokenizer”): citești un caracter, te uiți la următorul ca să decizi dacă numărul continuă, cuvântul se termină etc. next_if(|x| cond) consumă următorul element doar dacă îndeplinește condiția, iar next_if_eq(&v) doar dacă e egal cu v.

peek cere &mut self (poate fi nevoie să citească din sursă), deci iteratorul trebuie declarat mut.

Ce returnează

peekable() returnează un Peekable<Self>. peek() returnează Option<&Item>.

Exemple

Exemplul 1: peek nu consumă

fn main() {
    let mut it = ["Ana", "Mihai", "Ioana"].iter().peekable();
    println!("peek: {:?}", it.peek());
    println!("peek din nou: {:?}", it.peek());
    println!("next: {:?}", it.next());
    while let Some(nume) = it.next() {
        if it.peek().is_some() {
            print!("{nume}, ");
        } else {
            println!("{nume}.");
        }
    }
}

Rezultat

peek: Some("Ana")
peek din nou: Some("Ana")
next: Some("Ana")
Mihai, Ioana.

Exemplul 2: Împărțirea unei expresii în numere și operatori

După o cifră, next_if mai ia cifre cât timp urmează cifre, ca „12” să fie un singur număr.

fn main() {
    let expresie = "12+7*305-4";
    let mut it = expresie.chars().peekable();
    let mut bucati = Vec::new();
    while let Some(c) = it.next() {
        if c.is_ascii_digit() {
            let mut numar = c.to_string();
            while let Some(cifra) = it.next_if(|x| x.is_ascii_digit()) {
                numar.push(cifra);
            }
            bucati.push(numar);
        } else {
            bucati.push(c.to_string());
        }
    }
    println!("{:?}", bucati);
}

Rezultat

["12", "+", "7", "*", "305", "-", "4"]

Exemplul 3: Grupuri de valori consecutive egale

Comprimăm „aaabccdddd” în perechi (literă, de câte ori apare la rând).

fn main() {
    let text = "aaabccdddd";
    let mut it = text.chars().peekable();
    let mut grupuri = Vec::new();
    while let Some(c) = it.next() {
        let mut n = 1;
        while it.next_if_eq(&c).is_some() {
            n += 1;
        }
        grupuri.push((c, n));
    }
    println!("{:?}", grupuri);
    let comprimat: String = grupuri.iter().map(|(c, n)| format!("{n}{c}")).collect();
    println!("{comprimat}");
}

Rezultat

[('a', 3), ('b', 1), ('c', 2), ('d', 4)]
3a1b2c4d

Greșeli frecvente

  • Declari let it = v.iter().peekable(); fără mut: cannot borrow `it` as mutable. peek are nevoie de &mut.
  • Ții referința de la peek() și apoi apelezi next(): conflict de împrumut. Copiază valoarea (it.peek().copied()) sau compar-o imediat.
  • Folosești take_while pentru a citi un prefix și pierzi elementul care a oprit condiția. Cu peekable + next_if, acel element rămâne în iterator.

Comenzi înrudite

  • Iterator: Trait-ul care descrie orice șir de valori produse pe rând: next() dă următoarea valoare, sau None la final.
  • Iterator::take_while: take_while dă elemente cât timp condiția e adevărată și se oprește la primul care nu o îndeplinește; skip_while sare până atunci.
  • Iterator::by_ref: Împrumută iteratorul în loc să-l consume, ca să poți folosi un adaptor pe o parte din el și să continui apoi cu restul.
  • iter::from_fn: Creează un iterator dintr-un closure: fiecare apel al closure-ului dă următorul element, iar None oprește iteratorul.