Search code examples
clojurelisp

Converting string to nested map in Clojure


I have a file containing some text like:

1|apple|sweet
2|coffee|bitter
3|gitpush|relief

I want to work with this input using a map. In Java or Python, I would have made a nested map like:

{1: {thing: apple, taste: sweet},
2: {thing: coffee, taste: bitter},
3: {thing: gitpush, taste: relief}}

Or even a list inside the map like:

{1: [apple, sweet],
2: [coffee, bitter],
3: [grape, sour]}

The end goal is to access the last two column's data efficiently using the first column as the key. I want to do this in Clojure and I am new to it. So far, I have succeeded in creating a list of map using the following code:

(def cust_map (map (fn [[id name taste]] 
       (hash-map :id (Integer/parseInt id)
                 :name name 
                 :taste taste ))
     (map #(str/split % #"\|") (line-seq (clojure.java.io/reader path)))))

And I get this, but it's not what I want.

({1, apple, sweet},
{2, coffee, bitter},
{3, gitpush, relief})

It would be nice if you can show me how to do the most efficient of, or both nested map and list inside map in Clojure. Thanks!


Solution

  • When you build a map with hash-map, the arguments are alternative keys and values. For example:

    (hash-map :a 0 :b 1)
    => {:b 1, :a 0}
    

    From what I understand, you want to have a unique key, the integer, which maps to a compound object, a map:

    (hash-map 0 {:thing "apple" :taste "sweet"})
    

    Also, you do not want to call map, which would result in a sequence of maps. You want to have a single hash-map being built. Try using reduce:

    (reduce (fn [map [id name taste]]
              (merge map
                     (hash-map (Integer/parseInt id)
                               {:name name :taste taste})))
            {}
            '(("1" "b" "c")
              ("2" "d" "e")))
    

    --- edit

    Here is the full test program:

    (import '(java.io BufferedReader StringReader))
    
    (def test-input (line-seq
                     (BufferedReader.
                      (StringReader.
                       "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 a-map
      (reduce
       (fn [map [id name address phone]]
         (merge map
                (hash-map (Integer/parseInt id)
                          {:name name :address address :phone phone})))
       {}
       (map #(clojure.string/split % #"\|") test-input)))
    
    a-map
    => {1 {:name "John Smith", :address "123 Here Street", :phone "456-4567"}, 2 {:name "Sue Jones", :address "43 Rose Court Street", :phone "345-7867"}, 3 {:name "Fan Yuhong", :address "165 Happy Lane", :phone "345-4533"}}