2009-11-17 13 views
9

Soy nuevo y viejo para la programación; la mayoría de las veces escribo un montón de pequeñas secuencias de Perl en el trabajo. Clojure salió justo cuando quería aprender Lisp, así que estoy tratando de aprender Clojure sin saber Java tampoco. Es difícil, pero ha sido divertido hasta ahora.Newbie transformación de archivos CSV en Clojure

He visto varios ejemplos de problemas similares a los míos, pero nada que se corresponda con mi espacio problema. ¿Existe alguna forma canónica de extraer listas de valores para cada línea de un archivo CSV en Clojure?

Aquí hay un código de trabajo real de Perl; comentarios incluidos para los no Perlers:

# convert_survey_to_cartography.pl 
open INFILE, "< coords.csv";  # Input format "Northing,Easting,Elevation,PointID" 
open OUTFILE, "> coords.txt";  # Output format "PointID X Y Z". 
while (<INFILE>) {     # Read line by line; line bound to $_ as a string. 
    chomp $_;      # Strips out each line's <CR><LF> chars. 
    @fields = split /,/, $_;  # Extract the line's field values into a list. 
    $y = $fields[0];    # y = Northing 
    $x = $fields[1];    # x = Easting 
    $z = $fields[2];    # z = Elevation 
    $p = $fields[3];    # p = PointID 
    print OUTFILE "$p $x $y $z\n" # New file, changed field order, different delimiter. 
} 

he desconcertado a cabo un poco en Clojure y trató de improvisar juntos en un estilo imperativo:

; convert-survey-to-cartography.clj 
(use 'clojure.contrib.duck-streams) 
(let 
    [infile "coords.csv" outfile "coords.txt"] 
    (with-open [rdr (reader infile)] 
    (def coord (line-seq rdr)) 
    (...then a miracle occurs...) 
    (write-lines outfile ":x :y :z :p"))) 

no espero la última línea para que realmente funcione, pero logra transmitir el mensaje. Estoy buscando algo en la línea de:

(def values (interleave (:p :y :x :z) (re-split #"," coord))) 

Gracias, Bill

+2

'mi ($ x, $ y, $ z, $ p) = dividir /, /;' –

+0

Buen punto - TIMTOWTDI. Gracias. –

Respuesta

8

Aquí hay una manera:

(use '(clojure.contrib duck-streams str-utils))     ;;' 
(with-out-writer "coords.txt" 
    (doseq [line (read-lines "coords.csv")] 
    (let [[x y z p] (re-split #"," line)] 
     (println (str-join \space [p x y z]))))) 

with-out-writer une *out* de tal manera que todo lo que imprima irá al nombre de archivo o secuencia que especifique, en lugar de salida estándar.

Usar def como lo está usando no es idiomático. Una mejor manera es usar let. Estoy utilizando la desestructuración para asignar los 4 campos de cada línea a 4 let - nombres de destino; entonces puedes hacer lo que quieras con esos.

Si está iterando sobre algo con el propósito de efectos colaterales (por ejemplo, E/S), generalmente debería ir por doseq. Si se quería obtener hasta cada línea en un hash-mapa y hacer algo con ellos más tarde, se puede usar for:

(with-out-writer "coords.txt" 
    (for [line (read-lines "coords.csv")] 
    (let [fields (re-split #"," line)] 
     (zipmap [:x :y :z :p] fields)))) 
+0

¡Exactamente lo que necesitaba! Y elegantemente hecho también! doseq no tenía mucho sentido para mí hasta ahora, y ahora puedo ver que no entendí algunas otras cosas. Probé tu código en ClojureBox y funcionó; También pude cerrarlo en una función y eso funcionó también, así que esto parece haberme puesto en el camino correcto. Gracias de nuevo. –

15

Por favor, no use anidados de Def. No funciona, lo que piensas que hace. def es siempre global! Para los lugareños, use let en su lugar. Si bien las funciones de la biblioteca son agradables de conocer, aquí hay una versión que orquesta algunas características de la programación funcional en general y clojure en particular.

(import 'java.io.FileWriter 'java.io.FileReader 'java.io.BufferedReader) 

(defn translate-coords

Las cadenas de documentos se pueden consultar en el REPL a través de (doc translate-coords). Funciona eg. para todas las funciones básicas. Así que el suministro de uno es una buena idea.

"Reads coordinates from infile, translates them with the given 
    translator and writes the result to outfile."

traductor es una función (quizás anónima) que extrae la traducción de la plantilla repetitiva circundante. Entonces podemos reutilizar estas funciones con diferentes reglas de transformación. Las sugerencias de tipo aquí evitan la reflexión para los constructores.

[translator #^String infile #^String outfile]

Abra los archivos. with-open se ocupará de que los archivos se cierren cuando se deja su cuerpo. Ya sea a través de un "drop off the bottom" normal o sea a través de una excepción lanzada.

(with-open [in (BufferedReader. (FileReader. infile)) 
       out (FileWriter. outfile)]

ligamos la corriente *out* temporalmente al archivo de salida. De modo que cualquier impresión dentro del enlace se imprimirá en el archivo.

(binding [*out* out]

Los medios map: tomar la SEC y aplicar la función dada a cada elemento y devolver la SEC de los resultados. El #() es una notación abreviada para una función anónima. Se necesita un argumento, que se rellena en el %. El doseq es básicamente un bucle sobre la entrada. Como hacemos eso para los efectos secundarios (es decir, para imprimir en un archivo), doseq es la construcción correcta. Regla de oro: map: lazy => para el resultado, doseq: ansioso => ​​por efectos secundarios.

 (doseq [coords (map #(.split % ",") (line-seq in))]

println se encarga de la \n al final de la línea. interpose toma la secuencia y agrega el primer argumento (en nuestro caso "") entre sus elementos. (apply str [1 2 3]) es equivalente a (str 1 2 3) y es útil para construir llamadas a funciones dinámicamente. El ->> es una macro relativamente nueva en clojure, que ayuda un poco con la legibilidad. Significa "tomar el primer argumento y agregarlo como último elemento a la llamada de función". El dado ->> es equivalente a: (println (apply str (interpose " " (translator coords)))). (Edit: Otra nota: dado que el separador es \space, que podría aquí escriben igual de bien (apply println (translator coords)), pero la versión interpose permite parametrizar también el separador como lo hicimos con la función de traductor, mientras que la versión corta sería cablear \space.)

 (->> (translator coords) 
      (interpose " ") 
      (apply str) 
      println))))) 

(defn survey->cartography-format 
    "Translate coords in survey format to cartography format."

Aquí utilizamos desestructuración (observe el doble [[]]). Significa que el argumento para la función es algo que se puede convertir en una secuencia, por ejemplo. un vector o una lista. Enlace el primer elemento a y, el segundo a x y así sucesivamente.

[[y x z p]] 
    [p x y z]) 

(translate-coords survey->cartography-format "survey_coords.txt" "cartography_coords.txt")

Una vez más menos entrecortado:

(import 'java.io.FileWriter 'java.io.FileReader 'java.io.BufferedReader) 

(defn translate-coords 
    "Reads coordinates from infile, translates them with the given 
    translator and writes the result to outfile." 
    [translator #^String infile #^String outfile] 
    (with-open [in (BufferedReader. (FileReader. infile)) 
       out (FileWriter. outfile)] 
    (binding [*out* out] 
     (doseq [coords (map #(.split % ",") (line-seq in))] 
     (->> (translator coords) 
      (interpose " ") 
      (apply str) 
      println))))) 

(defn survey->cartography-format 
    "Translate coords in survey format to cartography format." 
    [[y x z p]] 
    [p x y z]) 

(translate-coords survey->cartography-format "survey_coords.txt" "cartography_coords.txt")

Espero que esto ayude.

Editar: Para la lectura de CSV, probablemente quieras algo como OpenCSV.

+1

Gracias por el tutorial, hay MUCHA información útil que me llevará algo de tiempo para digerir. Modelé una función mía en la que usaste aquí y funcionó como un amuleto. ¡Gracias de nuevo! –

Cuestiones relacionadas