2015-08-13 1 views
5

Mi chiedevo se è possibile utilizzare .collect() su un iteratore per afferrare gli articoli in un indice specifico. Per esempio, se comincio con una corda, io normalmente faccio:Raccogliere elementi da un iteratore a un indice specifico

let line = "Some line of text for example"; 
let l = line.split(" "); 
let lvec: Vec<&str> = l.collect(); 
let text = &lvec[3]; 

Ma quello che sarebbe stato bello è qualcosa di simile:

let text: &str = l.collect(index=(3)); 

risposta

7

No, non lo è; tuttavia è possibile facilmente filtro prima di voi raccogliere, che in pratica raggiunge lo stesso effetto.

Se si desidera filtrare per indice, è necessario aggiungere l'indice e poi spogliarla seguito:

  • enumerate (per aggiungere l'indice per l'elemento)
  • filter in base a questo indice
  • map per spellare l'index dall'elemento

O in codice:

fn main() { 
    let line = "Some line of text for example"; 
    let l = line.split(" ") 
       .enumerate() 
       .filter(|&(i, _)| i == 3) 
       .map(|(_, e)| e); 
    let lvec: Vec<&str> = l.collect(); 
    let text = &lvec[0]; 
    println!("{}", text); 
} 

Se si desidera ottenere un solo indice (e quindi elemento), utilizzare nth è molto più semplice. Restituisce un Option<&str> qui, che è necessario prendersi cura di:

fn main() { 
    let line = "Some line of text for example"; 
    let text = line.split(" ").nth(3).unwrap(); 
    println!("{}", text); 
} 

Se è possibile avere un predicato arbitraria, ma vuole solo il primo elemento che corrisponde, quindi raccogliendo in un Vec è inefficiente: si consumerà l'intero iteratore (senza pigrizia) e allocare potenzialmente molta memoria che non è affatto necessaria.

Si sono quindi meglio semplicemente chiedendo per il primo elemento con il metodo next del iteratore, che restituisce un Option<&str> qui:

fn main() { 
    let line = "Some line of text for example"; 
    let text = line.split(" ") 
        .enumerate() 
        .filter(|&(i, _)| i % 7 == 3) 
        .map(|(_, e)| e) 
        .next() 
        .unwrap(); 
    println!("{}", text); 
} 

Se si desidera selezionare parte del risultato, in base all'indice , puoi anche usare skip e take prima di raccogliere, ma suppongo che tu abbia già abbastanza alternative qui presentate.

+0

Grazie, molto da vedere qui ma molto utile. Hai detto che la raccolta in un Vec è inefficiente. Sareste in grado di commentare la velocità di esecuzione di line.split.enumerate.filter.map.unwrap più volte per afferrare elementi diversi dalla stringa e raccogliere l'intera cosa in un VEC e quindi utilizzare l'indicizzazione vettoriale per recuperare gli elementi. Ad esempio, come regola generale, se è necessario accedere a 1/4 degli articoli, quale metodo devo seguire? – kezzos

+0

@kezzos: Ho detto che la raccolta di * tutto * in un vettore era inefficiente, e ho anche spiegato i motivi: iterando fino alla fine quando non ti interessa gli ultimi pezzi e l'allocazione della memoria. Se hai bisogno di raccogliere 1/4 di pezzi, allora non mi preoccuperei dell'allocazione di memoria, comunque potresti ancora guadagnare tagliando l'iterazione in breve (usando 'take'). –

+2

C'è anche ['filter_map'] (http://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter_map) per combinare i passi' filter' e 'map'. – Shepmaster

6

C'è una funzione nth su Iterator che fa questo:

let text = line.split(" ").nth(3).unwrap(); 
+0

Grazie, anche questo restituisce un'opzione <&str>? – kezzos

+0

Certo, questo è ciò che è il "unwrap". Fondamentalmente 'fn unwrap (x: Option ) {if let Some (v) = x {v} else {panic! (" Meh ")}}'. – filmor

2

No; è possibile utilizzare take e next, però:

let line = "Some line of text for example"; 
let l = line.split(" "); 
let text = l.skip(3).next(); 

Si noti che questo si traduce in text essere un Option<&str>, come non c'è alcuna garanzia che la sequenza in realtà ha almeno quattro elementi.

Addendum: usando nth è decisamente più breve, anche se io preferisco essere espliciti circa il fatto che l'accesso alla nesimo elemento di un iteratore necessariamente consuma tutti gli elementi prima di esso.

+0

Per quanto riguarda il tuo addendum, vedo il tuo punto. Tuttavia, la ruggine è comunque abbastanza esplicita: se la scrivi in ​​una singola riga va bene comunque perché non utilizzerai la divisione (parzialmente consumata) da nessun'altra parte. Se vuoi dividere la divisione e ottenere il quarto elemento, il compilatore ti costringerà a scrivere 'let mut l = line.split (" ")' per poter usare 'nth'. – filmor

0

Per chiunque sia interessato, puoi fare un sacco di cose interessanti con gli iteratori (grazie Matthieu M), ad esempio per ottenere più "parole" da una stringa secondo il loro indice, puoi usare filter insieme a logico oppure || per testare più indici!

let line = "FCC2CCMACXX:4:1105:10758:14389# 81 chrM 1 32 10S90M = 16151 16062" 
let words: Vec<&str> = line.split(" ") 
          .enumerate() 
          .filter(|&(i, _)| i==1 || i==3 || i==6) 
          .map(|(_, e) | e) 
          .collect();