2016-06-07 4 views
10

Ich versuche, ein Muster mit Iteratoren in Rust zu verwenden und irgendwo scheinbar einfach herunterzufallen.Rust Iteratoren und freuen (peek/multipeek)

Ich würde gerne durch einen Container iterieren und ein Element mit einem Prädikat [A] (einfach) finden, aber dann mit einem anderen Prädikat vorwärts schauen und diesen Wert [B] erhalten und [B] verwenden, um [A] zu mutieren irgendwie. In diesem Fall ist [A] veränderlich und [B] kann unveränderlich sein; das macht für mich keinen Unterschied, nur für den Border Checker (zu Recht).

Es würde helfen, dies mit einem einfachen Szenario zu verstehen, also habe ich ein kleines Schnipsel hinzugefügt, damit die Leute das Problem/das versuchte Ziel sehen können. Ich habe mit Itertools gespielt und breche für/while-Loops, obwohl ich so idiomatisch wie möglich bleiben möchte.

Dumme Beispielszenario

Lookup eine gerade Zahl ist, findet die nächste Nummer, die von 3 und zu der ursprünglichen Zahl teilbar ist.

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

Wollen Sie das? https://play.rust-lang.org/?gist=e42f4ff5833f68a56fd4dfe5540992c0&version=stable&backtrace=0 – Adrian

+0

So etwas würde funktionieren, ja, danke. Ich würde gerne so idiomatisch wie möglich sein, obwohl ich das Item bei Index1 ändere und möglicherweise eine Iteration wie diese durch die ganze Liste/Vec usw. erlaubt. Wenn möglich ohne direkten Zugang wäre es gut. Dies würde für die Situation, die ich sehe, funktionieren, einfach, einen weiteren direkten Zugriff hinzuzufügen und die Index1-Position zu ändern. – dirvine

+0

Ich habe etwas funktioniert ohne viel Indexierung [auf dem Laufstall] (https://play.rust-lang.org/?gist=e42f4ff5833f68a56fd4dfe5540992c0&version=stable&backtrace=0), aber es ist immer noch ziemlich ausführlich. Ich denke, dass ein Iterator, der auf "split_at_mut" basiert, ein interessanterer Building Brick wäre (ein Iterator, der '(& mut [T], & mut [T])' mit der Grenzverschiebung bei jeder Iteration ergibt). –

Antwort

3

Warnung: Der Iterator rechts unten dargestellt ist, weil es eine unsafe mehrere Aliase auf ein einzelnes veränderliches Element zu erhalten, ermöglicht; Weiter zum zweiten Teil für die korrigierte Version.(Es wäre in Ordnung, wenn der Rückgabetyp unveränderliche Referenzen enthalten würde).

Wenn Sie bereit sind, Ihren eigenen Fenster-Iterator zu schreiben, wird es ziemlich einfach.

Zuerst der Iterator in all seinen blutigen Details:

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

auf [1, 2, 3] Ausgeführt es (&[], &[1, 2, 3]) dann (&[1], &[2, 3]) zurück, ... bis (&[1, 2, 3], &[]). Kurz gesagt, es iteriert über alle möglichen Partitionen der Scheibe (ohne Mischen).

Welche sicher zu verwenden ist als:

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

Leider kann es auch verwendet werden, wie:

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

, die bei Auflage: 0x7f73a8435000 0x7f73a8435000, Show-Gehäuse, dass beide wandelbar Referenzen alias der gleichen Element.


Da wir Aliasing nicht loswerden können, müssen wir Mutabilität loswerden; oder zumindest auf Interior Wandelbarkeit (Cell hier seit u8 ist Copy).

Zum Glück hat Cell keine Laufzeitkosten, aber es kostet ein bisschen in Ergonomie (all diese .get() und .set()).

Ich nutze die Gelegenheit, um den Iterator auch etwas generischer zu machen, und benenne ihn um, da Window bereits ein benutzter Name für ein anderes Konzept ist.

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

Wir verwenden es als Baustein:

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 diese Drucke: [1, 5, 3, 10, 5, 15, 7, 17, 9, 22, ...], die richtige zu sein scheint.

+0

Sehr beeindruckend. – dirvine

+0

@dirvine: Ich habe gerade festgestellt, dass dieser Iterator leider unsicher ist, weil er einem Benutzer ermöglicht, sicher veränderbare Aliase für dasselbe Element zu erhalten (wenn er außerhalb des Schleifenprotokolls verwendet wird). Ein Fix wäre, nur ein veränderbares Element (wie zum Beispiel '(& mut T, & [T])') zurückzugeben. –

+0

Ja, die überarbeitete Methode sieht zwar so aus, als wäre sie sicher und ich denke auch effizient? Naja jedenfalls so effizient wie es derzeit möglich ist. Sehr netter Code Matthieu, das wird sicher helfen. Die beste Lösung, die ich sehen kann. – dirvine

4

Leider bin ich ein bisschen spät, aber hier geht.

Es ist nicht ganz hübsch, aber es ist nicht so schlimm wie die anderen Vorschlag:

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

gibt

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

wie bei Matthieu M. Lösung.

Der Schlüssel besteht darin, mut_items.into_slice() zu verwenden, um den Iterator zu "reborrowen", was effektiv einen lokalen (und somit sicheren) Klon des Iterators erzeugt.