Ci sono vari modi per farlo, a seconda esattamente di ciò che si desidera.
Se si dispone di un function
che si desidera applicare a ciascuna riga in un file, è possibile utilizzare codice simile alla risposta di Abhinav:
(with-open [rdr ...]
(doall (map function (line-seq rdr))))
Questo ha il vantaggio che il file viene aperto, processato, e chiuso il più rapidamente possibile, ma impone l'intero file da consumare contemporaneamente.
Se si vuole ritardare l'elaborazione del file che si potrebbe essere tentati di tornare alle linee, ma questo non funziona:
(map function ; broken!!!
(with-open [rdr ...]
(line-seq rdr)))
perché il file è chiuso quando with-open
rendimenti, che è prima di elaborate pigramente il file.
Un modo per aggirare questo è quello di tirare l'intero file in memoria con slurp
:
(map function (slurp filename))
che ha un ovvio svantaggio - l'utilizzo della memoria - ma garantisce che non lasciare il file aperto.
Un'alternativa è quella di lasciare il file aperto fino ad arrivare alla fine della lettura, mentre la generazione di una sequenza pigro:
(ns ...
(:use clojure.test))
(defn stream-consumer [stream]
(println "read" (count stream) "lines"))
(defn broken-open [file]
(with-open [rdr (clojure.java.io/reader file)]
(line-seq rdr)))
(defn lazy-open [file]
(defn helper [rdr]
(lazy-seq
(if-let [line (.readLine rdr)]
(cons line (helper rdr))
(do (.close rdr) (println "closed") nil))))
(lazy-seq
(do (println "opening")
(helper (clojure.java.io/reader file)))))
(deftest test-open
(try
(stream-consumer (broken-open "/etc/passwd"))
(catch RuntimeException e
(println "caught " e)))
(let [stream (lazy-open "/etc/passwd")]
(println "have stream")
(stream-consumer stream)))
(run-tests)
che stampa:
caught #<RuntimeException java.lang.RuntimeException: java.io.IOException: Stream closed>
have stream
opening
closed
read 29 lines
dimostrando che il wasn di file è stato aperto fino a quando non è stato necessario.
Questo ultimo approccio ha il vantaggio di poter elaborare il flusso di dati "altrove" senza tenere tutto in memoria, ma ha anche uno svantaggio importante: il file non viene chiuso fino alla fine del flusso. Se non stai attento, puoi aprire molti file in parallelo o persino dimenticarti di chiuderli (non leggendo completamente lo stream).
La scelta migliore dipende dalle circostanze: è un compromesso tra valutazione lenta e risorse di sistema limitate.
PS: lazy-open
è definito da qualche parte nelle librerie? Sono arrivato a questa domanda cercando di trovare una tale funzione e ho finito per scrivere il mio, come sopra.
Grazie mille, ma cosa succede se volevo mantenere tutta la lista in memoria (non essere pigro), quale sarebbe il modo migliore allora? Come hai detto per alcune operazioni ho bisogno di andare oltre la lista più e più volte (supponiamo di avere abbastanza memoria per mantenere l'intera lista). – Ali
In tal caso, mantieni semplicemente un riferimento al capo della lista pigra. Sarà caricato pigramente la prima volta e poi rimarrà caricato. Qualcosa come: '(nomi def (con-apri [rdr (clojure.java.io/reader"/path/to/names/file ")] (line-seq rdr)))' –
Beh, non credo così. Poiché hai circondato "line-seq" con "with-open", il flusso sottostante verrà chiuso automaticamente al suo ritorno. Quindi non rimane nulla dietro i tuoi "nomi" var. Quindi in pratica dovresti 1: '(def rdr (clojure.java.io/reader"/path/to/names/file "))' then 2: '(def names (line-seq rdr))' then 3 : '(. rdr close)'. Infine, ora puoi giocare con i tuoi "nomi" come: '(conteggi nomi)' –