2016-06-07 33 views
10

Sto provando a usare un pattern con iteratori in Rust e cadere da qualche parte, apparentemente semplice.Ringer iterators e forwarding (peek/multipeek)

Vorrei scorrere attraverso un contenitore e trovare un elemento con un predicato [A] (semplice), ma poi guardare avanti usando un altro predicato e ottenere quel valore [B] e usare [B] per mutare [A] in qualche modo. In questo caso [A] è mutabile e [B] può essere immutabile; questo non fa differenza per me, solo per il correttore di prestiti (giustamente).

Sarebbe utile capire questo con uno scenario semplice, quindi ho aggiunto un piccolo frammento per far vedere alla gente il problema/l'obiettivo tentato. Ho suonato con itertools e inserendo per/while i loop, anche se voglio rimanere il più idiomatico possibile.

sciocco Scenario di esempio

Lookup un numero pari, cercare il numero successivo che è divisibile per 3 e aggiungere il numero iniziale.

#[allow(unused)] 
fn is_div_3(num: &u8) -> bool { 
    num % 3 == 0 
} 

fn main() { 
    let mut data: Vec<u8> = (0..100).collect(); 

    let count = data.iter_mut() 
     .map(|x| { 
      if *x % 2 == 0 { 
       // loop through numbers forward to next is_div_3, 
       // then x = x + that number 
      } 
      true 
     }) 
     .count(); 

    println!("data {:?}, count was {} ", data, count); 
} 

playground

+1

È questo quello che vuoi? https://play.rust-lang.org/?gist=e42f4ff5833f68a56fd4dfe5540992c0&version=stable&backtrace=0 – Adrian

+0

Qualcosa del genere funzionerebbe, sì, grazie. Mi piacerebbe essere il più idiomatico possibile anche se alterando l'elemento in index1, possibilmente permettendo l'iterazione come questa attraverso l'intera lista/vec ecc. Se possibile senza iter di accesso diretto sarebbe buono. Ciò funzionerebbe per la situazione che sto osservando, facile aggiungere un altro accesso diretto e modificare la posizione index1. – dirvine

+0

Ho qualcosa che funziona senza molta indicizzazione [sul box] (https://play.rust-lang.org/?gist=e42f4ff5833f68a56fd4dfe5540992c0&version=stable&backtrace=0) ma è ancora abbastanza dettagliato. Penso che un iteratore basato su 'split_at_mut' sarebbe un mattone da costruzione più interessante (un iteratore che restituisce' (& mut [T], & mut [T]) 'con lo spostamento dei confini ad ogni iterazione). –

risposta

3

Attenzione: L'iteratore presentata destra sotto è unsafe perché permette di ottenere più alias ad un singolo elemento mutevole; salta alla seconda parte per la versione corretta.(Sarebbe bene se il tipo di ritorno contenesse riferimenti immutabili).

Se si è disposti a scrivere l'iteratore della propria finestra, diventa abbastanza semplice.

In primo luogo, l'iteratore in tutti i suoi dettagli scabrosi:

use std::marker::PhantomData; 

struct WindowIterMut<'a, T> 
    where T: 'a 
{ 
    begin: *mut T, 
    len: usize, 
    index: usize, 
    _marker: PhantomData<&'a mut [T]>, 
} 

impl<'a, T> WindowIterMut<'a, T> 
    where T: 'a 
{ 
    pub fn new(slice: &'a mut [T]) -> WindowIterMut<'a, T> { 
     WindowIterMut { 
      begin: slice.as_mut_ptr(), 
      len: slice.len(), 
      index: 0, 
      _marker: PhantomData, 
     } 
    } 
} 

impl<'a, T> Iterator for WindowIterMut<'a, T> 
    where T: 'a 
{ 
    type Item = (&'a mut [T], &'a mut [T]); 

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

     let slice: &'a mut [T] = unsafe { 
      std::slice::from_raw_parts_mut(self.begin, self.len) 
     }; 
     let result = slice.split_at_mut(self.index); 

     self.index += 1; 

     Some(result) 
    } 
} 

richiamato su [1, 2, 3] tornerà (&[], &[1, 2, 3]) poi (&[1], &[2, 3]), ... fino (&[1, 2, 3], &[]). In breve, itera su tutte le potenziali partizioni della slice (senza mischiare).

che è sicuro da usare come:

fn main() { 
    let mut data: Vec<u8> = (1..100).collect(); 

    for (head, tail) in WindowIterMut::new(&mut data) { 
     if let Some(element) = head.last_mut() { 
      if *element % 2 == 0 { 
       if let Some(n3) = tail.iter().filter(|i| *i % 3 == 0).next() { 
        *element += *n3; 
       } 
      } 
     } 
    } 

    println!("{:?}", data); 
} 

Purtroppo può essere utilizzato anche come:

fn main() { 
    let mut data: Vec<u8> = (1..100).collect(); 

    let mut it = WindowIterMut::new(&mut data); 
    let first_0 = { it.next(); &mut it.next().unwrap().0[0] }; 
    let second_0 = &mut it.next().unwrap().0[0]; 

    println!("{:?} {:?}", first_0 as *const _, second_0 as *const _); 
} 

che quando tiratura: 0x7f73a8435000 0x7f73a8435000, show-involucro che entrambi i riferimenti mutevoli alias lo stesso elemento.


Dal momento che non è possibile eliminare l'aliasing, è necessario eliminare la mutabilità; o almeno rinviare alla mutabilità interna (Cell qui dal u8 è Copy).

Fortunatamente, Cell non ha costi di esecuzione, ma costa un po 'in termini di ergonomia (tutte quelle .get() e .set()).

Colgo l'occasione per rendere l'iteratore leggermente più generico e rinominarlo dal Window è già un nome usato per un concetto diverso.

struct FingerIter<'a, T> 
    where T: 'a 
{ 
    begin: *const T, 
    len: usize, 
    index: usize, 
    _marker: PhantomData<&'a [T]>, 
} 

impl<'a, T> FingerIter<'a, T> 
    where T: 'a 
{ 
    pub fn new(slice: &'a [T]) -> FingerIter<'a, T> { 
     FingerIter { 
      begin: slice.as_ptr(), 
      len: slice.len(), 
      index: 0, 
      _marker: PhantomData, 
     } 
    } 
} 

impl<'a, T> Iterator for FingerIter<'a, T> 
    where T: 'a 
{ 
    type Item = (&'a [T], &'a T, &'a [T]); 

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

     let slice: &'a [T] = unsafe { 
      std::slice::from_raw_parts(self.begin, self.len) 
     }; 

     self.index += 1; 
     let result = slice.split_at(self.index); 

     Some((&result.0[0..self.index-1], result.0.last().unwrap(), result.1)) 
    } 
} 

lo usiamo come un mattone edificio:

fn main() { 
    let data: Vec<Cell<u8>> = (1..100).map(|i| Cell::new(i)).collect(); 

    for (_, element, tail) in FingerIter::new(&data) { 
     if element.get() % 2 == 0 { 
      if let Some(n3) = tail.iter().filter(|i| i.get() % 3 == 0).next() { 
       element.set(element.get() + n3.get()); 
      } 
     } 
    } 

    let data: Vec<u8> = data.iter().map(|cell| cell.get()).collect(); 

    println!("{:?}", data); 
} 

On the playpen Questo stampa: [1, 5, 3, 10, 5, 15, 7, 17, 9, 22, ...], che sembra corretto.

+0

Molto impressionante. – dirvine

+0

@dirvine: mi sono appena reso conto che, sfortunatamente, questo iteratore non è sicuro perché consente a un utente di ottenere alias mutabili in modo sicuro sullo stesso elemento (se usato al di fuori del protocollo del ciclo). Una correzione sarebbe di restituire solo un elemento mutabile (come '(& mut T, & [T])') per esempio. –

+0

Sì, il metodo rivisto sembra però sicuro e anche efficiente? Bene comunque efficiente come è attualmente possibile. Molto bello il codice Matthieu, questo ti aiuterà di sicuro. La soluzione più pulita che posso vedere. – dirvine

4

Purtroppo sono un po 'in ritardo, ma ecco qui.

E 'non del tutto abbastanza, ma non è così male come l'altro suggerimento:

let mut data: Vec<u8> = (1..100).collect(); 

{ 
    let mut mut_items = data.iter_mut(); 
    while let Some(x) = mut_items.next() { 
     if *x % 2 == 0 { 
      let slice = mut_items.into_slice(); 
      *x += *slice.iter().find(|&x| x % 3 == 0).unwrap(); 
      mut_items = slice.iter_mut(); 
     } 
    } 
} 

println!("{:?}", data); 

[1, 5, 3, 10, 5, 15, 7, 17, 9, 22, ...] 

come con la soluzione di Matthieu M..

La chiave è quella di utilizzare mut_items.into_slice() per "ritrarre" l'iteratore, producendo in modo efficace un clone locale (e quindi sicuro) dell'iteratore.