giotto1
giotto1

Reputation: 59

Clojure - read text file and enter it as a list

I am having trouble with some basic IO operations using Clojure. I have a text file which I need to read, split with the "|" character, and enter into a list for later processing. Here are the contents of my text file:

1|John Smith|123 Here Street|456-4567 
2|Sue Jones|43 Rose Court Street|345-7867 
3|Fan Yuhong|165 Happy Lane|345-4533

And here is my current code:

((defn -main [] 
(println "Enter an option: \n")

(let [choice (read-line)]
  
  (cond (= choice "1") 
        (let [cust-contents (slurp "file.txt")
              nums-as-strings (clojure.string/split cust-contents #"|")
              numbers (map read-string nums-as-strings)]
              (print numbers)
        ) 
  )
) ) )


(-main)

I would think this code to work, however here is the error I get when running my program:

(; Execution error at user/eval7923$-main (REPL:11).
; EOF while reading

Could anyone please guide me on where I went wrong and on how to fix this?

Upvotes: 0

Views: 345

Answers (3)

Gwang-Jin Kim
Gwang-Jin Kim

Reputation: 10010

Add [clojure-csv/clojure-csv "2.0.1"] to your lein dependencies. Do lein deps to install it.

Put into a file e.g. "/home/me/test.csv"

1|John Smith|123 Here Street|456-4567 
2|Sue Jones|43 Rose Court Street|345-7867 
3|Fan Yuhong|165 Happy Lane|345-4533
(ns csvread
  (:require [clojure-csv.core :as csv]))

(defn parse-table [file]
  (csv/parse-csv (slurp file) :delimiter \|))

(defn parse-table-from-string [s]
  (csv/parse-csv s :delimiter \|))

Apply it:

(parse-table "/home/me/test.csv")
;; => (["1" "John Smith" "123 Here Street" "456-4567"] 
;;     ["2" "Sue Jones" "43 Rose Court Street" "345-7867"] 
;;     ["3" "Fan Yuhong" "165 Happy Lane" "345-4533 "])

(def s "1|John Smith|123 Here Street|456-4567 
2|Sue Jones|43 Rose Court Street|345-7867 
3|Fan Yuhong|165 Happy Lane|345-4533")

(parse-table-from-string s)

Upvotes: 0

Martin Půda
Martin Půda

Reputation: 7576

That error is actually caused when you call read-string with argument " ".

And where that " " comes from? You used the wrong regex in clojure.string/split, you should use #"\|" instead of #"|".

And even then, you can't call read-string on everything, as it would soon crash again, trying to parse "456-4567".

Also, ending parentheses belong to the same line.

If your file contains newlines, you will need clojure.string/split-lines and if you will also implement 2 Display Product Table, 3. Display Sales Table and so on (I know the context for this code), case will be better than cond.

Here is your code with some improvements:

(ns homework.core
  (:require [clojure.string :as s])
  (:gen-class))

(defn -main []
  (println "Enter an option: \n")
  (let [choice (read-line)]
    (case choice
          "1" (->> (s/split-lines (slurp "file.txt"))
                   (mapv #(s/split % #"\|"))
                   println)
          "Wrong number")))

(-main)

Upvotes: 3

Alan Thompson
Alan Thompson

Reputation: 29984

For one thing, you should not have 2 left parentheses in a row like ((defn ....

Also, CSV parsing has many libraries to remove the drudgery of reinventing the wheel. My favorite is illustrated below:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test)
  (:require
    [tupelo.csv :as csv]))

(verify
  ; ***** NOTE *****  most CSV has a header line, which is added below for convenience!
  (let [data-str "id|name|address|phone
                  1|John Smith|123 Here Street|456-4567
                  2|Sue Jones|43 Rose Court Street|345-7867
                  3|Fan Yuhong|165 Happy Lane|345-4533 "
        entity-maps (csv/csv->entities data-str {:separator \|})]
    (is= entity-maps
      [{:address "123 Here Street", :id "1", :name "John Smith", :phone "456-4567"}
       {:address "43 Rose Court Street", :id "2", :name "Sue Jones", :phone "345-7867"}
       {:address "165 Happy Lane", :id "3", :name "Fan Yuhong", :phone "345-4533"}])))

This unit test leaves out the file-reading part as that just complicates things in a unit test.

The above solution is based on my favorite template project, which also has a list of documentation sources toward the end.

You can find examine the source code for tupelo.csv and also the unit tests.


To clarify your code, here is a cleaned-up version:

(verify
  (let [data-str        "1|John Smith|123 Here Street|456-4567
                         2|Sue Jones|43 Rose Court Street|345-7867
                         3|Fan Yuhong|165 Happy Lane|345-4533 "
        data-lines      (str/split-lines data-str)
        token-table-str (vec (for [line data-lines]
                               (let [token-strs (str/split line #"\|")]
                                 (mapv str/trim token-strs))))]
    (is= token-table-str
      [["1" "John Smith" "123 Here Street" "456-4567"]
       ["2" "Sue Jones" "43 Rose Court Street" "345-7867"]
       ["3" "Fan Yuhong" "165 Happy Lane" "345-4533"]])))

Notice that I didn't parse the data past the String stage.

Upvotes: 0

Related Questions