2009-10-30 16 views
26

dire che ho un vettore:Clojure: chiamare una funzione per ogni elemento in un vettore con esso indice

(def data ["Hello" "World" "Test" "This"]) 

E voglio popolare una tabella da qualche parte che ha un api:

(defn setCell 
    [row col value] 
    (some code here)) 

allora qual è il modo migliore per ottenere le seguenti chiamate ad accadere:

(setCell 0 0 "Hello") 
(setCell 0 1 "World") 
(setCell 0 2 "Test") 
(setCell 0 3 "This") 

ho trovato che il seguente funzionerà:

(let [idv (map vector (iterate inc 0) data)] 
    (doseq [[index value] idv] (setCell 0 index value))) 

Ma c'è un modo più veloce che non richiede un nuovo idv di infrastruttura temporanea?

risposta

10

Il modo in cui lo si fa è idiomatico (e identico allo clojure.contrib.seq-utils/indexed in effetti). Se davvero si vuole evitare che la struttura dei dati in più, si può fare questo:

(loop [data data, index 0] 
    (when (seq data) 
    (setCell 0 index (first data)) 
    (recur (rest data) (inc index)))) 

userei la versione a meno che non ci fosse un buon motivo per non però.

+5

Clojure 1.2 ha aggiunto 'map-indexed', che è l'opzione più semplice e migliore che abbia visto fino ad ora. Vedi [risposta di Rollo in basso] (http://stackoverflow.com/a/5992602/109618). –

27

È possibile ottenere lo stesso effetto in modo molto clojure-idiomatico semplicemente mappando gli indici insieme ai dati.

(map #(setCell 0 %1 %2) (iterate inc 0) data) 

Si consiglia di avvolgere questo in un (doall o (doseq per effettuare le chiamate accadere ora. Va bene mappare un seq infinito insieme a quello finito perché la mappa si fermerà quando il seq più breve si esaurirà.

+1

Bello, non conoscevo questo comportamento della mappa quando applicato a più raccolte. – pmf

+10

3 anni di follow-up: (iterate inc 0) è meglio scritto con just (range) –

1

ho fatto un breve confronto tra le prestazioni delle opzioni sofar:

; just some function that sums stuff 
(defn testThis 
    [i value] 
(def total (+ total i value))) 

; our test dataset. Make it non-lazy with doall  
(def testD (doall (range 100000))) 

; time using Arthur's suggestion 
(def total 0.0) 
(time (doall (map #(testThis %1 %2) (iterate inc 0) testD))) 
(println "Total: " total) 

; time using Brian's recursive version 
(def total 0.0) 
(time (loop [d testD i 0] 
    (when (seq d) 
    (testThis i (first d)) 
    (recur (rest d) (inc i))))) 
(println "Total: " total) 

; with the idiomatic indexed version 
(def total 0.0) 
(time (let [idv (map vector (iterate inc 0) testD)] 
    (doseq [[i value] idv] (testThis i value)))) 
(println "Total: " total) 

Risultati sul mio portatile 1 nucleo:

"Elapsed time: 598.224635 msecs" 
    Total: 9.9999E9 
    "Elapsed time: 241.573161 msecs" 
    Total: 9.9999E9 
    "Elapsed time: 959.050662 msecs" 
    Total: 9.9999E9 

conclusione preliminare:

Utilizzare il loop/ricorrono soluzione.

+0

Ricco suggerito quando microbenchmarking esegue ogni test alcune dozzine di volte e prende l'ultimo per ottenere l'ottimizzazione dell'hot-spot riscaldata sulla funzione prima. testQuesto è molto più leggero della funzione mappa, quindi il ciclo più stretto possibile sarà migliore. –

+0

Credo di aver bisogno di testare con l'hot-spot ottimizzato. Ho appena eseguito lo stesso identico test in Python e lì è stato eseguito in 80 msec. –

+0

Chiamare 'def' in questo modo potrebbe inabissare i segnalibri. –

8

il modo più bello sarebbe quello di utilizzare clojure.contrib.seq-utils/indexed, che sarà simile a questa (utilizzando destrutturazione):

(doseq [[idx val] (indexed ["Hello" "World" "Test" "This"])] 
    (setCell 0 idx val)) 
+1

Come [clojure-contrib è ora deprecato] (http://clojure.github.io/clojure-contrib/seq-utils-api.html), dove è finita la funzione 'indexed'? Il documento [Where Did Clojure.Contrib Go] (http://dev.clojure.org/display/community/Where+Did+Clojure.Contrib+Go) non sembra dire nulla al riguardo. – Alexey

+1

Ho finito per scriverne uno solo. '(defn indexed [coll] (map-indexed vector coll))' solo perché a volte il '(per [idx val] (indexed coll) (..))' produce codice più chiaro di map-indexed. – prabhasp

27

un po 'tardi nel gioco, ma per le persone che accedono questa pagina: ora c'è (dal clojure 1.2) una funzione map-indexed disponibile in clojure.core.

Un problema (a meno che non mi sbagli): non esiste un equivalente "pmap", il che significa che i calcoli indicizzati sulla mappa non possono essere facilmente parallelizzati. In tal caso, mi riferisco alle soluzioni offerte sopra.

+1

Si può fare un 'pmap-indexed' includendo un iteratore incrementale come prima raccolta in pmap:' (defn pmap-indexed [f coll] (pmap f (iterate inc 0) coll)) '. – tkocmathla

Problemi correlati