Search code examples
clojure

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...


Solution

  • (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.