简体   繁体   中英

Clojure function not returning a map

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}

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"}}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM