0

I am new to Clojure and functional programming. I have a basic idea about the data structures in Clojure such as maps, lists, and vectors.

I am trying to write a function that returns a nested map. The map is correctly displayed inside the function. The below code is from the read-customer-file function

([input-list new-map place-holder]
        (if (not-empty input-list)
            (do
                (def string-input (str (first input-list)))
                (def cust-id (get (first input-list) 0))
                (def cust-name (get (first input-list) 1))
                (def cust-address (get (first input-list) 2))
                (def cust-phone (get (first input-list) 3))
                (def temp-map (conj new-map {(keyword cust-id) {:cust-name cust-name, :cust-address cust-address, :cust-phone cust-phone}}))
                (read-customer-file (rest input-list) temp-map ())
            )
            (do 
                (map str new-map)
                ;(print (get-in new-map [:1 :cust-name]))
                (print new-map)
            )
        )
    )

This takes an input of list of vectors as below:

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

and returns a nested-map as below:

{:3 {:cust-name Fan Yuhong, :cust-address 165 Happy Lane, :cust-phone 345-4533}, :2 {:cust-name Sue Jones, :cust-address 43 Rose Court Street, :cust-phone 345-7867}, :1 {:cust-name John Smith, :cust-address 123 Here Street, :cust-phone 456-4567}}

This is what I am trying to achieve and is working perfectly fine within the function. However, if I try to define a variable outside the function with the value of the return type, I do not get a nested-map but a list of strings. For this, I just remove the (print new-map) part.

(do 
    (map str new-map)
)

And call it from outside the function definition as follows:

(def customer-info-list read-customer-file)
(println (customer-info-list))

The result I get is different from what I expected and cannot perform the map-related functionalities such as get and get-in.

([:3 {:cust-name Fan Yuhong, :cust-address 165 Happy Lane, :cust-phone 345-4533}] [:2 {:cust-name Sue Jones, :cust-address 43 Rose Court Street, :cust-phone 345-7867}] [:1 {:cust-name John Smith, :cust-address 123 Here Street, :cust-phone 456-4567}])

I would very much appreciate any kind of help with this. I am aware that my code is a bit messy and I should use let instead of def for variable names. But I am just starting Clojure and this is my first program.

UPDATE

I solved the problem. What I did is change the map into a sorted-map inside the function. So the last return from the function will be a sorted map.

(do 
    (into (sorted-map) new-map)
)

However, the returned value will still be a string if assigned to a variable., so I converted it to a sorted-map once again. Then it finally got converted to a nested-map.

(def customer-info-list read-customer-file)
(def cust-map (into (sorted-map) (customer-info-list)))
(println cust-map)
(println (get-in cust-map [:1 :cust-name]))
(println (get cust-map :2))

The output from the above three print statements is as expected.

{:1 {:cust-name John Smith, :cust-address 123 Here Street, :cust-phone 456-4567}, :2 {:cust-name Sue Jones, :cust-address 43 Rose Court Street, :cust-phone 345-7867}, :3 {:cust-name Fan Yuhong, :cust-address 165 Happy Lane, :cust-phone 345-4533}}
John Smith
{:cust-name Sue Jones, :cust-address 43 Rose Court Street, :cust-phone 345-7867}
1
  • 2
    def-ing inside your function is usually something you don't want to do. Use let instead. Commented Jun 1, 2020 at 8:12

2 Answers 2

2

there is a number of mistakes in your code, connected with the lack of basic clojure knowledge:

1) don't use def/defn inside functions, rather use let

2) adding to a map is usually done with assoc

3) it is always better to return some significant value from the function, and introspect it outside.

i would rewrite your function in a more clojure way like this: (i intentonaally keep placeholder argument, though it isn't used anywhere in your code, so i can't infer it's purpose)

(defn read-customer-file [input-list new-map placeholder]
  (if-let [[[id name address phone] & input-tail] (seq input-list)]
    (read-customer-file input-tail
                        (assoc new-map (keyword id)
                               {:cust-name name
                                :cust-address address
                                :cust-phone phone})
                        ())
    new-map))

in repl:

user> (read-customer-file [["a" "b" "c" "d"] ["e" "f" "g" "h"]] {} ())
;;=> {:a {:cust-name "b", :cust-address "c", :cust-phone "d"},
;;    :e {:cust-name "f", :cust-address "g", :cust-phone "h"}}

but what you really need, is some higher order data processing function like map:

(defn read-customer-file [input-list placeholder]
  (into {}
        (map (fn [[id name address phone]]
               [(keyword id) {:cust-name name
                              :cust-phone phone
                              :cust-address address}])
             input-list)))

user> (read-customer-file [["a" "b" "c" "d"] ["e" "f" "g" "h"]] {})
;;=> {:a {:cust-name "b", :cust-phone "d", :cust-address "c"},
;;    :e {:cust-name "f", :cust-phone "h", :cust-address "g"}}

or reduce:

(defn read-customer-file [input-list placeholder]
  (reduce (fn [res [id name address phone]]
            (assoc res (keyword id) {:cust-name name
                                     :cust-phone phone
                                     :cust-address address}))
          {} input-list))

also, it's always a good idea to abstract out your business logic (in your case file record to customer conversion) to some specialized function, to keep the processing function more general:

(defn file-rec->customer [[id name address phone]]
  [(keyword id) {:cust-name name
                 :cust-address address
                 :cust-phone phone}])

(defn read-data-file [record-processor input-list placeholder]
  (into {} (map record-processor) input-list))

user> (read-data-file file-rec->customer [["a" "b" "c" "d"] ["e" "f" "g" "h"]] {})
;;=> {:a {:cust-name "b", :cust-address "c", :cust-phone "d"},
;;    :e {:cust-name "f", :cust-address "g", :cust-phone "h"}}
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for such a detailed answer. I can see that my functional programming skills are lacking as I am new to this paradigm.
0

I was able to solve the problem by converting it to a sorted-map while returning. Also, the returned value was again converted to a sorted-map which did the trick. Please check the update section.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.