2012-07-24 14 views
7

ho una sequenza di map-coppie come questo (attualmente circa 17000 paia)iterare su clojure mappa coppie (anello)

(def myseq '({:name "Peter" :rank 2222} {:name "Anna" :rank 111})) 

voglio filtrare specifiche coppie in una nuova sequenza con

(filter (fn [x] (> x 222)) (:rank (first myseq))) 

Ho cercato di ripetere con loop come questo, ma continuo a ricevere thread-death. Inoltre, se utilizzo il filtro su una singola raccolta di mappe, restituisce solo una nuova sequenza, non sono sicuro che sia necessario crearne uno anch'io qui?

(defn remove-lower [number myseq] 
    (loop [i 0] 
     (if (= i (count file)) 
      (println "done") 
      (filter [x] (> x number)) 
       (:rank (first myseq)))) 
    (recur (rest myseq)))) 

Finalmente è in loop il modo più efficiente per ottenere la nuova sequenza di coppie?

migliore, J

risposta

8

Non c'è bisogno di loop/ripresentano qui. filtrare già scorre un ss per voi:

(filter (fn [entry] (> (:rank entry) 220)) myseq) 
+0

Ancora meglio! Ecco come ho provato a farlo prima ma non ci sono riusciti. Grazie. –

+5

'(filtro # (> (: rank%) 220) myseq)' – Ankur

+0

Grazie mille ragazzi, il codice ora funziona e anche molto veloce! –

6

La prima cosa da sapere è che (la maggior parte) le strutture di dati in clojure sono immutabili e la maggior parte delle funzioni sono, beh, funzionale. Ciò significa che non hanno effetti collaterali. Nel tuo caso filter non cambia la sequenza in alcun modo, ne restituisce una nuova, contenente solo gli elementi non filtrati.

Così, per filtrare myseq è necessario fare qualcosa di simile:

(def filtered-seq (filter (fn [x] ...) myseq)) 

Filtro chiamerà la funzione più volte, vincolante x alla voce attualmente filtrato in myseq. Cioè, la prima volta sarà legato a {:name "Peter" :rank 2222}, quindi a {:name "Anna" :rank 111}. filtered-seq conterrà solo gli elementi, per i quali la funzione ha restituito true. myseq sarà non da modificare!

Quindi, si vuole lasciare solo gli elementi con :rank superiore a 222:

(filter (fn [x] (> (:rank x) 222)) myseq) 

Questo è tutto. E un'altra cosa sul filtro è che è pigro. Cioè, gli articoli nella collezione restituita sono "realizzati" (o calcolati) solo quando sono necessari.

Non è necessario utilizzare loop per questo, come filter funziona correttamente, e loop non è pigro.

Detto questo, il vostro loop non funziona perché ha diversi problemi:

  1. recur è al di fuori del loop. In questo caso il clojure tornerà all'inizio della funzione.
  2. è necessario costruire un valore di ritorno ed è necessario mantenere l'elemento "corrente"
  3. è necessario controllare correttamente per la condizione di fine

Il codice potrebbe essere simile a questa (non testata):

(defn remove-lower [number myseq] 
    (loop [sq myseq res []] 
    (if (empty? sq) 
     res 
     (let [current (first sq)] 
      (if (> (:rank current) number) 
       (recur (rest sq) (conj res current)) 
       (recur (rest sq) res)))))) 

si noti come:

  1. recur è ora all'interno del loop
  2. res contiene il valore di ritorno e sq contiene la sequenza attualmente sinistra
  3. ogni recur passa i nuovi valori di sq e res per la prossima iterazione
  4. sq è "a contrazione" ad ogni iterazione, cosicché il ciclo si casualmente uscita a meno che myseq sia infinito. Contrasto a filter, che gestisce perfettamente sequenze infinite.

Come si vede questo è più difficile da leggere e meno generale di filter ed è anche desideroso (non pigro).

+0

Grazie Ivant, feedback fantastico! –