Reputation: 43
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}
Upvotes: 0
Views: 253
Reputation: 17849
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"}}
Upvotes: 2
Reputation: 43
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.
Upvotes: 0