2017-08-20 87 views
5

Biorąc pod uwagę to definicja foo:Warunkowo powrócić pusty iterator z flat_map

let foo = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]]; 

Chciałbym móc napisać kod tak:

let result: Vec<_> = foo.iter() 
    .enumerate() 
    .flat_map(|(i, row)| if i % 2 == 0 { 
     row.iter().map(|x| x * 2) 
    } else { 
     std::iter::empty() 
    }) 
    .collect(); 

ale zgłasza błąd o if a także klauzule o niekompatybilnych typach. Próbowałem usunięcie map czasowo i próbowałem definiowanie pusty wektor zewnątrz zamknięcia i powrót iterator nad tym tak:

let empty = vec![]; 

let result: Vec<_> = foo.iter() 
    .enumerate() 
    .flat_map(|(i, row)| if i % 2 == 0 { 
     row.iter() //.map(|x| x * 2) 
    } else { 
     empty.iter() 
    }) 
    .collect(); 

To wydaje trochę głupie ale kompiluje. Jeśli spróbuję odkomentować kod map, to nadal narzeka on na klauzule if i else mające niezgodne typy. Oto część z komunikatem o błędzie:

error[E0308]: if and else have incompatible types 
    --> src/main.rs:6:30 
    | 
6 |   .flat_map(|(i, row)| if i % 2 == 0 { 
    | ______________________________^ 
7 | |    row.iter().map(|x| x * 2) 
8 | |   } else { 
9 | |    std::iter::empty() 
10 | |   }) 
    | |_________^ expected struct `std::iter::Map`, found struct `std::iter::Empty` 
    | 
    = note: expected type `std::iter::Map<std::slice::Iter<'_, {integer}>, [[email protected]/main.rs:7:28: 7:37]>` 
       found type `std::iter::Empty<_>` 

Playground Link

Wiem, że mógłbym napisać coś, że robi to, co chcę z kilku zagnieżdżonych for pętli, ale chciałbym wiedzieć, czy istnieje lakoniczny sposób, żeby to napisać za pomocą iteratorów.

Odpowiedz

4

Ponieważ Rust jest wpisany statycznie, a każdy krok w łańcuchu iteratora zmienia wynik na nowy typ, który porywa poprzednie typy (chyba że korzystasz z obiektów z zapętlonymi cechami), będziesz musiał napisać to w taki sposób, aby objąć obie gałęzie według tego samego rodzaju.

Jednym ze sposobów przekazywania warunkowej pustki za pomocą jednego typu jest implementacja iteratora TakeWhile.

.flat_map(|(i, row)| { 
    let iter = row.iter().map(|x| x * 2); 
    let take = i % 2 == 0; 
    iter.take_while(|_| take) 
}) 

Jeśli nie przeszkadza ignorując Edge-przypadek, w którym iterator wejściowy foo może mieć więcej niż usize elementy można też użyć Take zamiast z 0 lub usize :: MAX. Ma tę zaletę, że zapewnia lepsze size_hint() niż TakeWhile.

+0

"*' | _ | jeśli i% 2 == 0 {true} else {false} '*" Proszę, po prostu '| _ | i% 2 == 0' ...; -] – ildjarn

+0

@ildjarn hah, tak. Podniosłem również modulo z zamknięcia. chociaż i tak kompilator może ją zoptymalizować. – the8472

4

W Twoim konkretnym przykładzie, można użyć filter usunąć niechciane elementy przed wywołaniem flat_map:

let result: Vec<_> = foo.iter() 
    .enumerate() 
    .filter(|&(i, _)| i % 2 == 0) 
    .flat_map(|(_, row)| row.iter().map(|x| x * 2)) 
    .collect(); 

Jeśli kiedykolwiek chcesz go używać z map zamiast flat_map można łączyć wywołań filter i map za pomocą filter_map, która przyjmuje funkcję zwracającą Option i zachowuje tylko elementy, które są Some(thing).