2016-06-07 38 views
10

Próbuję użyć wzorca z iteratorami w Rust i upadając gdzieś, najwyraźniej proste.Iteratory rdzy i czekamy (peek/multipeek)

Chciałbym iterować przez kontener i znaleźć element z predykatem [A] (prosty), ale następnie oczekiwać na przyszłość przy użyciu innego predykatu i uzyskać tę wartość [B] i użyć [B] do mutowania [A] w pewnym sensie. W tym przypadku [A] jest zmienne, a [B] może być niezmienne; to nie ma znaczenia dla mnie, tylko dla sprawdzającego pożyciu (słusznie).

Pomoże to zrozumieć prosty scenariusz, dlatego dodałem mały fragment, aby pokazać ludziom problem/próbę osiągnięcia celu. Grałem z itertools i włamałem się do pętli for/while, chociaż chcę pozostać tak idiomatyczny jak to tylko możliwe.

głupie Przykład scenariusz

wyszukiwania liczbą parzystą, znaleźć kolejny numer, która jest podzielna przez 3 i dodawać do ilości początkowej.

#[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

Czy tego chcesz? https://play.rust-lang.org/?gist=e42f4ff5833f68a56fd4dfe5540992c0&version=stable&backtrace=0 – Adrian

+0

Coś takiego by działało, tak, dzięki. Chciałbym być tak idiotyczny, jak to tylko możliwe, chociaż zmieniając pozycję w indeksie 1, prawdopodobnie umożliwiając iterację w ten sposób na całej liście/vec itp. Nice though. Jeśli to możliwe bez bezpośredniego dostępu, to byłoby dobrze. To zadziałałoby dla sytuacji, na którą patrzę, łatwo dodać kolejny bezpośredni dostęp i zmienić pozycję indeksu1. – dirvine

+0

Mam coś pracy bez dużego indeksowania [na kojec] (https://play.rust-lang.org/?gist=e42f4ff5833f68a56fd4dfe5540992c0&version=stable&backtrace=0), ale nadal jest dość gadatliwy. Myślę, że iterator oparty na 'split_at_mut' byłby bardziej interesującą cegłą budynku (iterator, który daje' (& mut [T], i mut [T]) 'z przesunięciem granicy w każdej iteracji). –

Odpowiedz

3

Ostrzeżenie: iteracyjnej przedstawione tuż poniżej unsafe, ponieważ pozwala na uzyskanie wielu aliasy jednym zmiennego elementu; przejdź do drugiej części poprawionej wersji.(Byłoby dobrze, gdyby typ powrotu zawierał niezmienne odniesienia).

Jeśli chcesz napisać własny iterator okna, to staje się całkiem łatwe.

pierwsze, iterator we wszystkich swych krwawych szczegółów:

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) 
    } 
} 

Wykonano [1, 2, 3] powróci (&[], &[1, 2, 3]) następnie (&[1], &[2, 3]) ... aż (&[1, 2, 3], &[]). W skrócie, iteruje on po wszystkich potencjalnych partycjach plasterka (bez tasowania).

który jest bezpieczny w użyciu jako:

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); 
} 

Niestety może to być również używany jako:

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 _); 
} 

który po Nakład: 0x7f73a8435000 0x7f73a8435000, show-obudowy że obie Zmienne referencje alias taka sama element.


Ponieważ nie możemy pozbyć się aliasingu, musimy pozbyć się zmienności; lub przynajmniej odroczyć wewnętrzną zmienność (Cell tutaj od u8 jest Copy).

Na szczęście Cell nie ma kosztów runtime, ale kosztuje trochę w ergonomii (wszystkie te .get() i .set()).

Korzystam z okazji, aby uczynić iterator nieco bardziej uniwersalnym i zmienić jego nazwę, ponieważ Window jest już nazwą używaną dla innej koncepcji.

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)) 
    } 
} 

Używamy go jako murowany budynek:

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 to wydruki: [1, 5, 3, 10, 5, 15, 7, 17, 9, 22, ...], co wydaje się prawidłowe.

+0

Bardzo imponujące. – dirvine

+0

@dirvine: Właśnie zdałem sobie sprawę, że, niestety, ten iterator jest niebezpieczny, ponieważ pozwala użytkownikowi uzyskać bezpiecznie zmienne aliasy do tego samego elementu (jeśli jest używany poza protokołem pętli). Poprawiono by tylko zwracanie jednego zmiennego elementu (na przykład '(& mut T, & [T])') na przykład. –

+0

Tak, zmieniona metoda wygląda jednak tak, jakby była bezpieczna i uważam, że również wydajna? Cóż, tak czy inaczej, jak to jest obecnie możliwe. Bardzo ładny kod Matthieu, to na pewno ci pomoże. Najmodniejsze rozwiązanie, jakie widzę. – dirvine

4

Niestety jestem trochę spóźniony, ale tu idzie.

To nie całkowicie ładna, ale nie jest tak źle, jak inne sugestie:

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); 

daje

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

jako roztworem Matthieu M..

Kluczem jest użycie mut_items.into_slice(), aby "przełączyć" iterator, skutecznie tworząc lokalny (a więc bezpieczny) klon iteratora.