2012-08-16 14 views
7

Sto cercando di prendere 5 righe con i loro numeri di riga da un file di grandi dimensioni (> 1 GB) con Clojure. Sono quasi arrivato ma vedo cose strane e voglio capire cosa sta succedendo.Estrarre tranquillamente le righe dal file di grandi dimensioni

Finora ho ottenuto:

(defn multi-nth [values indices] 
    (map (partial nth values) indices)) 

(defn read-lines [file indices] 
    (with-open [rdr (clojure.java.io/reader file)] 
    (let [lines (line-seq rdr)] 
     (multi-nth lines indices)))) 

Ora, (read-lines "my-file" [0]) opere senza un problema. Tuttavia, passando [0 1] mi dà il seguente stacktrace:

java.lang.RuntimeException: java.io.IOException: Stream closed 
     Util.java:165 clojure.lang.Util.runtimeException 
     LazySeq.java:51 clojure.lang.LazySeq.sval 
     LazySeq.java:60 clojure.lang.LazySeq.seq 
     Cons.java:39 clojure.lang.Cons.next 
      RT.java:769 clojure.lang.RT.nthFrom 
      RT.java:742 clojure.lang.RT.nth 
     core.clj:832 clojure.core/nth 
     AFn.java:163 clojure.lang.AFn.applyToHelper 
     AFn.java:151 clojure.lang.AFn.applyTo 
     core.clj:602 clojure.core/apply 
     core.clj:2341 clojure.core/partial[fn] 
     RestFn.java:408 clojure.lang.RestFn.invoke 
     core.clj:2430 clojure.core/map[fn] 

Sembra che il flusso è stato chiuso prima di poter leggere la seconda riga dal file. È interessante notare che se estraggo manualmente una riga dal file con qualcosa come (nth lines 200), la chiamata multi-nth funziona per tutti i valori < = 200.

Qualche idea su cosa sta succedendo?

risposta

9

map (e line-seq) restituiscono sequenze pigro, quindi nessuna delle righe viene necessariamente letta al momento della chiamata a ritorni con-open, che chiude il file.

fondamentalmente, è necessario rendersi conto del valore di tutto il ritorno prima con-aperta, per i quali è possibile utilizzare DOALL:

(defn multi-nth [values indices] 
    (map (partial nth values) indices)) 

(defn read-lines [file indices] 
    (with-open [rdr (clojure.java.io/reader file)] 
    (let [lines (line-seq rdr)] 
     (doall (multi-nth lines indices))))) 

o qualcosa di simile. tieni a mente che il tuo multi-nth ti tiene al capo della riga seq mentre cerchi le linee specificate, il che significa che manterrà tutte le linee in alto fino a quando l'ultimo specificato in memoria - e usare nth come questo significa che tu stai ripetendo la sequenza seq ripetutamente per ogni indice: ti consigliamo di risolverlo.

aggiornamento:

Qualcosa di simile a questo lavoro. È un po 'più brutto di quello che mi piace, ma mostra il principio, penso: nota che gli indici qui devono essere un set.

(defn multi-nth [values indices] 
(keep 
    (fn [[number line]] 
    (if (contains? indices number) 
     line)) 
    (map-indexed vector values))) 

(multi-nth '(a b c d e) #{2 3}) 
    => c d 
+0

Buon punto. Avrò bisogno di utilizzare chiamate di metodo di accesso casuale Java di livello inferiore per farlo bene? –

+0

Penso che si possa ottenere un codice decente utilizzando il map-indexed e il filtro. Aggiornerò tra un minuto ... –

+0

Ah ok, è grandioso. Se è sintatticamente più brutto di quanto vorresti, puoi usare keep-indexed ed eventualmente condensare la funzione filtro. Guardando dentro ora ... –

5

with-file chiude il file una volta il corpo è stato eseguito. Quindi, una volta eseguito lo multi-nth, il file viene chiuso, il che significa che si finisce con una sequenza lenta che punta a un file chiuso.

(read-lines "my-file" [0]) funziona perché viene realizzato solo il primo elemento della sequenza lenta.

Per risolvere il problema, è necessario forzare la sequenza da realizzare con doall:

(defn multi-nth [values indices] 
    (doall (map (partial nth values) indices))) 

Per una spiegazione molto dettagliata si veda https://stackoverflow.com/a/10462159/151650

+0

Ah. Ha senso. Grazie! –