2009-11-17 6 views
9

Jestem zarówno nowy, jak i stary do programowania - głównie po prostu piszę dużo małych skryptów Perla w pracy. Clojure pojawił się właśnie wtedy, gdy chciałem nauczyć się Lisp, więc staram się nauczyć Clojure również nie znając Javy. To trudne, ale jak dotąd było zabawnie.Nowicjusz przetwarzający pliki CSV w Clojure

Widziałem kilka przykładów podobnych problemów z moimi, ale nic, co całkiem odwzorowuje moją przestrzeń problemową. Czy istnieje kanoniczny sposób wyodrębniania list wartości dla każdej linii pliku CSV w Clojure?

Oto aktualny działający kod Perla; Komentarze zawarte tylko dla 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. 
} 

mam zastanawiać lub zadziwiać się trochę w Clojure i starał się go razem kostka w bezwzględnej styl:

; 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"))) 

nie spodziewam ostatnią linię rzeczywiście działa, ale ma to znaczenie. Szukam czegoś wzdłuż linii:

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

Dzięki, Bill

+2

'my ($ x, $ y, $ z, $ p) = split /, /;' –

+0

Dobra uwaga - TIMTOWTDI. Dzięki. –

Odpowiedz

8

Oto jeden ze sposobów:

(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 wiąże *out* takie, że wszystko wydrukować trafi do pliku lub strumień, który określasz, a nie standardowe wyjście.

Korzystanie z niego nie jest idiomatyczne. Lepszym sposobem jest użycie let. Używam destructuring, aby przypisać 4 pola każdego wiersza do 4let -związków nazw; wtedy możesz zrobić z nimi to, co chcesz.

Jeśli przeprowadzasz iterację w celu uzyskania efektów ubocznych (np. We/wy), powinieneś wybrać numer doseq. Jeśli chciał zebrać każdą linię do hash-map i zrobić coś z nimi później, można użyć for:

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

Dokładnie to, czego potrzebowałem! A także elegancko wykonane! doseq nie ma dla mnie większego sensu, a teraz widzę, że źle zrozumiałem także kilka innych rzeczy. Próbowałem twojego kodu w ClojureBox i działało; Byłem także w stanie zawrzeć to w funkcji i to też zadziałało, więc wydaje mi się, że postawiłem na dobrej drodze. Dzięki jeszcze raz. –

15

Proszę nie używać zagnieżdżonych def tych. Nie robi, co myślisz, że robi. def jest zawsze globalny! W przypadku mieszkańców użyj let zamiast tego. Podczas gdy funkcje biblioteczne są dobrze znane, tutaj jest wersja, która aranżuje niektóre funkcje programowania funkcjonalnego w ogóle, a w szczególności clojure.

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

(defn translate-coords

Docfrings można uzyskać w REPL poprzez (doc translate-coords). Działa np. dla wszystkich podstawowych funkcji. Dostarczanie jednego jest dobrym pomysłem.

tłumacz to funkcja (może anonimowa), która wyodrębnia tłumaczenie z otaczającego zestawu znaków. Możemy więc ponownie użyć tych funkcji z różnymi regułami transformacji. Wskazówki dotyczące tego typu pozwalają uniknąć refleksji dla konstruktorów.

[translator #^String infile #^String outfile]

Otwórz pliki. with-open będzie dbał o to, aby pliki były zamykane po opuszczeniu jego ciała. Czy to przez normalne "upuszczanie na dół", czy to przez wyrzucony wyjątek.

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

Powiązanie strumienia *out* tymczasowo z plikiem wyjściowym. Tak więc każdy wydruk wewnątrz wiązania będzie drukował do pliku.

(binding [*out* out]

W map oznacza: wziąć nast i zastosować daną funkcję do każdego elementu i zwraca nast wyników. #() to notacja krótka dla anonimowej funkcji. Potrzeba jednego argumentu, który jest wypełniony na %. doseq jest w zasadzie pętlą nad wejściem. Ponieważ robimy to dla efektów ubocznych (mianowicie drukowania do pliku), doseq jest właściwą konstrukcją. Zasada kciuka: map: leniwy => dla wyniku, doseq: chętny => dla efektów ubocznych.

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

println dba o \n na końcu linii. interpose przyjmuje seq i dodaje pierwszy argument (w naszym przypadku "") między jego elementami. (apply str [1 2 3]) jest odpowiednikiem (str 1 2 3) i jest użyteczne do dynamicznego konstruowania wywołań funkcji. ->> to stosunkowo nowe makro w clojure, które pomaga nieco w czytelności. Oznacza to "weź pierwszy argument i dodaj go jako ostatni element do wywołania funkcji". Podany ->> jest równoważny z: (println (apply str (interpose " " (translator coords)))). (Edit: Kolejna uwaga: ponieważ separator jest \space moglibyśmy tu napisać równie dobrze (apply println (translator coords)), ale wersja interpose pozwala również parametrize separator jak my z funkcją tłumacz, natomiast wersja skrócona będzie przewodowego \space.)

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

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

Tutaj używamy destrukturyzacji (uwaga podwójna [[]]). Oznacza to, że argument funkcji jest czymś, co można przekształcić w seq, np. wektor lub listę. Przypisz pierwszy element do y, drugi do x i tak dalej.

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

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

Tu znowu mniej wzburzony:

(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")

Nadzieja to pomaga.

Edycja: Do odczytu CSV prawdopodobnie potrzebujesz czegoś takiego jak OpenCSV.

+1

Dzięki za tutorial - jest tam mnóstwo użytecznych informacji, które zajmą mi trochę czasu. Zamodelowałem swoją funkcję na tę, której używałeś tutaj i działało jak czar. Dzięki jeszcze raz! –