In Clojure, How do I update a nested map correctly? - dictionary

I've just started learning Clojure, after many years of Java (and PHP/JavaScript) experience. What a challenge :-)
How do I update a map of values idiomatically? When I use the map function on a map it doesn't return a map, it returns a sequence.
I'm working on a small app where I have a list of tasks. What I'd like to do is alter some of the values in some of the individual tasks, then update the list of original tasks. Here are the tasks I'm testing with:
(defrecord Task [key name duration])
(def tasks
(atom
{
"t1" (->Task "t1" "Task 1" 10)
"t2" (->Task "t2" "Task 2" 20)
"t3" (->Task "t3" "Task 3" 30)
}
))
I've put the tasks in a hashmap, using a string key so it has fast, direct access to any task in the map. Each task holds the key as well, so I know what it's key is when I'm passing individual tasks to other functions.
To update the durations I'm using map and update-in to iterate over and selectively update the duration of each task, and returning the modified tasks.
Here's the function:
(defn update-task-durations
"Update the duration of each task and return the updated tasks"
[tasks]
; 1) Why do I have to convert the result of the map function,
; from a sequence then back to a map?
(into {}
(map
(fn [task]
(println task) ; debug
(update-in
task
; 2) Why do I have to use vector index '1' here
; to get the value of the map entry?
[1 :duration]
(fn [duration]
(if (< duration 20)
(+ duration 1)
(+ duration 2)
)
)
)
) tasks))
)
I print the before/after values with this:
(println "ORIGINAL tasks:")
(println #tasks)
(swap! tasks update-task-durations)
(println "\nUPDATED tasks:")
(println #tasks)
1) The main problem I'm having is that the map function returns a sequence, and not a map, so I'm having to convert the sequence back to a map again using into {} which seems to me to be unnecessary and inefficient.
Is there a better way to do this? Should I be using a function other than map?
Could I arrange my data structures better, while still being efficient for direct access to individual tasks?
Is it ok to convert a (potentially very large) sequence to a map using into {} ?
2) Also, inside my function parameter, that I pass to the map function, each task is given to me, by map, as a vector of the form [key value] when I would expect a map entry, so to get the value from the map entry I have to pass the following keys to my update-in [1 :duration] This seems a bit ugly, is there a better/clearer way to access the map entry rather than using index 1 of the vector?

A popular way to solve this mapping-over-maps problem is with zipmap:
(defn map-vals
"Returns the map with f applied to each item."
[f m]
(zipmap (keys m)
(map f (vals m))))
(defn update-task-durations
[tasks]
(let [update-duration (fn [duration]
(if (< duration 20)
(+ 1 duration)
(+ 2 duration)))]
(->> tasks
(map-vals #(update % :duration update-duration)))))
(swap! tasks update-task-durations)
For Clojure < 1.7, use (update-in % [:duration] ... instead.
Alternatively, you could also use destructuring to simplify your current solution without defining a utility function:
(->> tasks
(map (fn [[k task]]
[k (update task :duration update-duration)]))
(into {})
Why?
map only deals with sequences. If you're into type signatures, this means that map always has the same type (map :: (a -> b) -> [a] -> [b]), but it also means that all you'll get out of map is a seq-of-something.
map calls seq on its collection parameter before doing anything, and seq-ing a map gives you a sequence of key-val pairs.
Don't worry too much about efficiency here. into is fast and this is pretty idiomatic.

Just get more alternatives:
Instead of a map you can use a for
(into {}
(for [[key value] your-map]
[key (do-stuff value)]))
A faster way is reduce-kv
(reduce-kv
(fn [new-map key value]
(assoc new-map key (do-stuff value)))
{}
your-map))
Of course you can also use a simple reduce
(reduce (fn [m key]
(update m key do-stuff))
your-map
(keys your-map))

Related

call function to all items of sequence

I am trying to generate a nested map in Clojure.
The problem is that.
Firstly I have a sequence ("first" "second" "third" ...).
Secondly I have a function by calling which with each element with sequence I want to receive a map like this {: first (function
first)}, {: second (function second)} and ..... .
Finally i want to gether all result to a new map {:first {...} :second {...} :third {...}}.
Maybe somebody gives me some an instruction or approaches how to do it.
p.s
So i create a draft of my function (it does not work).
(defn finder [sequence function]
(for [x [sequence]
:let [ pair {:x (function x)}
mappa {} (assoc-in mappa pair)]]
mappa))
This is a nice occasion to use juxt
(defn finder [sequence function]
(into {} (map (juxt keyword function) sequence)))
You can use map to construct key-value pair and into to construct the resulting hashmap:
(defn finder [sequence function]
(into {} (map (fn [k] [(keyword k) (function k)]) sequence)))
You could functions like keyword and into:
(defn finder [sequence function]
(->> sequence
(map #(vector (keyword %) (function %)))
(into {})))
You can zipmap over two sequences of function applications, since keyword is just a function too.
(defn finder [sequence function]
(apply zipmap (map #(map % sequence) [keyword function])))

Why am i getting a ClassCastException in this recursive function [duplicate]

This question already has an answer here:
Wrong number of args (0) passed to: PersistentVector on loop/recur function
(1 answer)
Closed 5 years ago.
I've written a recursive function to get a total cost of a journey.
costOfPath simply makes a call to ubergraph to get the cost for each journey, then this function adds them and displays it.
(defn routeCost [parcel cost]
"Calculate the total route cost"
(if (empty? parcel)
(print "Total Journey Cost: " cost)
((def first-parcel (first parcel))
(def start (:start first-parcel))
(def finish (:finish first-parcel))
(def value (costOfPath start finish))
(def parcel-two (rest parcel))
(routeCost parcel-two (+ cost value)))))
(routeCost task8 0)
Task 8 looks as such:
(def task8 [(Parcel. :main-office :r131 "Plastic Wallets" "Delivery" 1)
(Parcel. :r131 :r111 "CDs" "Delivery" 1)
(Parcel. :r111 :r121 "USBs" "Collection" 2)
(Parcel. :r121 :main-office "USBs" "Delivery" 2)])
The function prints out the correct cost, but gives a classCastException.
ClassCastException practice_ubergraph.core.Parcel cannot be cast to clojure.lang.IFn clojure.lang.Var.fn (Var.java:363)
The parcel record:
(defrecord Parcel [start
finish
package
run-type
weight
])
Why is this happening and how can i stop it?
EDIT: I think its something to do with the IF statement and the way i've put the brackets around the block.
As Tony says, it's a good idea to try to limit your use of defs to the top level.
The reason you see a ClassCastException is probably this line:
((def first-parcel (first parcel))
You're defining first-parcel and then calling it immediately with the outer set of parentheseis.
Compare it to this example which generates a similar exception:
((def a 1))
In this example a gets the value 1. def returns the var #'user/a, so the expression that is evaluated is:
(#'user/a)
The value of #'user/a is 1, and 1 is then treated as a function.
Generally, if you see cannot be cast to clojure.lang.IFn look for a double set of brackets.
Please do NOT use def in a function.
Here is a much better one
(defn route-cost [parcel cost]
"Calculate the total route cost"
(if (empty? parcel)
(print "Total Journey Cost: " cost)
(let [{:keys [start finish]} (first parcel)
value (cost-of-path start finish)]
(route-cost (rest parcel) (+ cost value)))))
The essence of clojure is that you could write your code as concise as possible . usually we use kebab-case in clojure to differentiate Java
Use let in your function will fix everything

Adding "not" to each item in a sequence in Clojure

I am trying to add (not(X)) to all my items X in a sequence.
For example:
Convert (a b) to
( (not(a)) (not(b)) )
When I use (map (fn [x] (not(x))) mylist), it tries to evaluate the nots and return booleans.
When I use (map (fn [x] '(not(x))) mylist), it just returns a list of (not(x)) without actually putting in my list's variables.
(a b) --> ( (not(a)) (not(b)) ) ? Thanks!
user=> (map (fn [x] (list 'not (list x))) '(a b))
((not (a)) (not (b)))
The ' single quote operator is convenient for making lists because it prevents evaluation, but it isn't usable in your case because you have content inside the resulting list that you want to be evaluated.
Another option would have been ` AKA quasiquote, which allows selective unquoting, but also namespaces symbols (once again, not useful in your case, where you want the symbol used literally).
You can make it more readable and get rid of numerous list calls
by using syntax-quote reader macro:
user> (map (fn [x] `(~'not (~x))) '(a b))
((not (a)) (not (b)))
(see clojure reader documentation's section on [syntax quoting](
http://clojure.org/reader))
unquote-quote not (~'not) is used here to insert literal not symbol instead of namespace-prefixed clojure.core/not

Recursive Clojure function not recurring when called from last place in the calling function

This is my caller
(resolveEntity [r entity-id]
(println "resolve" entity-id)
(recursive-get r entity-id)
(cache entity-id)
)
Called function is
(defn recursive-get [r entity-id]
(println "recursive" entity-id)
(let [e (f (merge {} (-> r :conns first d/db (d/entity entity-id))))]
(alter-var-root #'cache assoc entity-id e)
(for [[k v] e]
(if (:db/isComponent (k components))
(if (not= (class v) Long)
(map #(recursive-get r %) v)
(recursive-get r v)
)))))
The called function is called just once. If I remove the last line in the caller (cache entity-id), then it recurs every time that I want it to, but I need to return something else (cache entity-id).
I tested a similar but simpler code (a recursive function not called at the tail of a calling function) the REPL and it worked, so I am left crashing my head against the table.
You have been bitten by a Lazy-Bug!
If you remove the last line then the return value of the function is the result of (recursive-get r entity-id) which the repl then iterates through so it can print it. The act of printing each value causes each entry in the lazy collection to be evaluated. When you put another line after that, the result of the map is ignored: nothing reads the entries, and they remain in the unrealized lazy state forever and the computation never happens.
To fix this wrap it in a call to dorun:
(dorun (recursive-get r entity-id))
Or if you need to save the result then use doall instead.

Simple Calculator (Addition function input) in Clojure

I'm trying to write a simple calculator with addition, subtraction, etc.
My problem is with getting the user input. How do I turn the string of numerical values into a vector? And also what is a better way to write the program?
(ns scalc.core)
(defn add
[numbers]
(println (apply + numbers)))
(defn numchoose
[]
(println "What numbers?: ")
(let [numbers (read-line)] numbers))
(defn opchoose
[]
(println "What operation would you like to do?: ")
(let [operation (read-line)]
(if (= operation "add")
(do
(println "You chose to add.")
(let [numvect (numchoose)]
(add [numvect]))))))
(defn -main
[& args]
(opchoose)
(numchoose))
And this is the error:
~/clj/scalc 1/7 % lein trampoline run -m scalc.core
What operation would you like to do?:
add
You chose to add.
What numbers?:
5 7
Exception in thread "main" java.lang.ClassCastException: Cannot cast java.lang.String to java.lang.Number
at java.lang.Class.cast(Class.java:3005)
at clojure.core$cast.invoke(core.clj:318)
at clojure.core$_PLUS_.invoke(core.clj:927)
at clojure.lang.AFn.applyToHelper(AFn.java:161)
at clojure.lang.RestFn.applyTo(RestFn.java:132)
at clojure.core$apply.invoke(core.clj:601)
at scalc.core$add.invoke(core.clj:5)
at scalc.core$opchoose.invoke(core.clj:21)
at scalc.core$_main.doInvoke(core.clj:27)
at clojure.lang.RestFn.invoke(RestFn.java:397)
at clojure.lang.Var.invoke(Var.java:411)
at user$eval15.invoke(NO_SOURCE_FILE:1)
at clojure.lang.Compiler.eval(Compiler.java:6511)
at clojure.lang.Compiler.eval(Compiler.java:6501)
at clojure.lang.Compiler.eval(Compiler.java:6477)
at clojure.core$eval.invoke(core.clj:2797)
at clojure.main$eval_opt.invoke(main.clj:297)
at clojure.main$initialize.invoke(main.clj:316)
at clojure.main$null_opt.invoke(main.clj:349)
at clojure.main$main.doInvoke(main.clj:427)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:419)
at clojure.lang.AFn.applyToHelper(AFn.java:163)
at clojure.lang.Var.applyTo(Var.java:532)
at clojure.main.main(main.java:37)
EDIT: the solved program now looks like this:
(ns scalc.core)
(defn add [numbers]
(reduce + numbers))
(defn numchoose []
(let [nums (re-seq #"\d+" (read-line))]
(map #(Integer/parseInt %) nums)))
(defn delegate []
(println "What operation would you like to do?: ")
(let [operation (read-line)]
(when (= operation "add")
(println "You chose to add.")
(println "What numbers? ")
(add (numchoose)))))
(defn -main
[& args]
(delegate))
For getting the numbers, you can use re-seq:
(re-seq #"\d+" "123 456 789") => ("123" "456" 789")
You still only have strings rather than numbers though. You can use read-string to get the numbers (read-string is convenient, but not safe in all cases. Here we make sure there are really only numbers in these strings so it's fine).
(read-string "5") => 5
Instead of (apply + numbers) you could use reduce: (reduce + numbers), also your add function really shouldn't print anything (you should try to separate functional functions from side-effecty functions whenever possible).
This (let [numbers (read-line)] numbers) is equal to (read-line). Don't overcomplicate things!
Instead of
(if (= operation "add")
(do ... ))
you can write
(when (= operation "add")
...)
when is just a macro that's useful when you don't need the else case in your ifs (it wraps everything after the condition in a do, and evaluates to nil when the condition evaluates to false).

Resources