EL_
EL_

Reputation: 21

How to put the records of my file into a defined list and use it after in my code? - CLOJURE

(defn loadData [filename]
(with-open [rdr (io/reader filename)]
(doseq [line (line-seq rdr)
        :let [ [id & data]
              (clojure.string/split line #"\|")] ]
            [
                (Integer/parseInt id) 
                data 
              ])
  
  ))

My file's content:

1|Xxx|info1|222-2222
2|Yyy|info2|333-3333
3|Zzz|info3|444-4444

How do I get this function to return a list like : { {1, Xxx, info1, 222-2222}
{2, Yyy, info2, 333-3333}
{3, Zzz, info3, 444-4444}
}

I also want to know how to get my function loadData to return this list above instead of NIL.

Please help! Thanks in advance...

Upvotes: 0

Views: 58

Answers (1)

piotr-yuxuan
piotr-yuxuan

Reputation: 36

(def my-file-content
  (.getBytes "1|Xxx|info1|222-2222\n2|Yyy|info2|333-3333\n3|Zzz|info3|444-4444"))

;; See https://guide.clojure.style/ for common naming style.
(defn load-data
  [filename]
  ;; Well done, `with-open` is correctly used here!
  (with-open [rdr (io/reader filename)]
    ;; See https://clojuredocs.org/clojure.core/doseq for examples of `for` and `doseq`. After reading examples and documentation, it will probably become clear why `for` is what you want, and not `doseq`.
    (for [line (line-seq rdr)
          :let [[id & data] (clojure.string/split line #"\|")]]
      [(Integer/parseInt id)
       data])))

;; But `(load-data my-file-content)` raises an exception because `for` returns a lazy sequence. Nothing happens before you actually display the result, so the course of actions is:
;; - Open a reader;
;; - Define computations in a `LazySeq` as result of `for`;
;; - Close the reader;
;; - The function returns the lazy seq;
;; - Your REPL wants to display it, so it tries to evaluate the first elements;
;; - Now it actually tries to read from the reader, but it is closed.

;; This lazyness explains why this returns no exception (you don't look at what's inside the sequence):
(type (load-data my-file-content))
;; => clojure.lang.LazySeq

;; The shortest fix is:
(defn load-data
  [filename]
  (with-open [rdr (io/reader filename)]
    ;; https://clojuredocs.org/clojure.core/doall
    (doall
      (for [line (line-seq rdr)
            :let [[id & data] (clojure.string/split line #"\|")]]
        [(Integer/parseInt id)
         data]))))

(load-data my-file-content)
;; => ([1 ("Xxx" "info1" "222-2222")] [2 ("Yyy" "info2" "333-3333")] [3 ("Zzz" "info3" "444-4444")])

;; This is not what you want. Here is the shortest fix to coerce this (realised) lazy sequence into what you want:

(defn load-data
  [filename]
  (with-open [rdr (io/reader filename)]
    ;; https://clojuredocs.org/clojure.core/doall
    (doall
      (for [line (line-seq rdr)
            :let [[id & data] (clojure.string/split line #"\|")]]
        (cons (Integer/parseInt id) data)))))

(load-data my-file-content)
;; => '((1 "Xxx" "info1" "222-2222") (2 "Yyy" "info2" "333-3333") (3 "Zzz" "info3" "444-4444"))
;; Also, note that a list is represented as `'(1 2 3)` or `[1 2 3]` for a vector. `{1 2 3}` is a syntax error, curly brackets are for maps.

(defn load-data
  [filename]
  (with-open [rdr (io/reader filename)]
    (->> (line-seq rdr)
         (mapv #(clojure.string/split % #"\|")))))

(load-data my-file-content)
;; => [["1" "Xxx" "info1" "222-2222"] ["2" "Yyy" "info2" "333-3333"] ["3" "Zzz" "info3" "444-4444"]]
;; Note that here we no longer have a list of lists, but a vector of vectors. In Clojure lists are lazy and vectors are not, so this is why `mapv`, which returns a vector, works fine with an open reader.

Upvotes: 1

Related Questions