Can a var be a key in a map? - dictionary

(def nextStudentNumber 1000)
(defn s
[lastName firstName]
(let [student {:nextStudentNumber {:lastName lastName
:firstName firstName
:grades {}
:id (inc nextStudentNumber)}}]
In this instance, I have created the var nextStudentNumber and I want to have map that keys that change on the of the student.

Yes, like any object a var can be a key in a map:
{#'foo 42}
=> {#'user/foo 42}
Since that won't help you, here is an example construct for your question:
(->> [{:name "Mara"} {:name "Lara"} {:name "Clara"}]
(map-indexed #(assoc %2 :id %1)) ;; assign an ID to each student
(group-by :id)) ;; Create a hash-map where students can be looked up by ID
=> {0 [{:name "Mara, :id 0}], 1 [{:name "Lara", :id 1}]
2 [{:name "Clara", :id 2}]}
Notice that this example is a bit redundant (as index lookup can be performed in a vector directly by invoking it with the index).
However, it shows the basic idea of working with immutable data: In independent steps you generate IDs and do grouping. You can compose such steps in many ways, like generating IDs for new students, adding them to a collection of existing students and grouping them by ID. Notice that the (group-by :id) step works independently of how the IDs are generated and tolerates scenarios where two students have the same ID which you can then detect and handle.
The imperative approach is very untypical in functional Clojure world. If you have many functions that allocate students (and in your thinking should invoke s), rather make them pure and let them return collections of students and give them names like load-students-from-file, generate-ids and use constructs like concat, into to combine collections returned by them without the need to mess with state, until you do I/O with the student data.
If, out of curiosity, you still want to mess with state, here is another (rather unidiomatic) code-sample:
(def student-count (atom 0))
(def students-by-id (atom {}))
(defn add-student! [first-name last-name]
(let [next-id (swap! student-count inc)]
(swap! students-by-id
assoc next-id {:first-name first-name, :last-name last-name
:id next-id})))
E. g.:
(add-student! "Mara" "Mad")
(add-student! "Lara" "Light")
(#students-by-id 1)
=> {:first-name "Lara", :last-name "Light", :id 1}
As you can see, everything is complected in one step here.

{:nextStudentNumber {:lastName lastName
:firstName firstName
:grades {}
:id (inc nextStudentNumber)}
I assume you want this transformed into something like:
{:1000 {:lastName lastName
:firstName firstName
:grades {}
:id (inc nextStudentNumber)}
in which case you'll want:
{(keyword (str nextStudentNumber)) {:lastName lastName
:firstName firstName
:grades {}
:id (inc nextStudentNumber)}
There are some other smelly things like camel case and inc. We use levitating snake in clojure so lastName would be last-name. I don't know what you're doing with inc but it's giving off heavy imperative vibes.

Related

Why should update be preferred over assoc?

In clojure, to edit the values of the keys of a map, there are 2 options available:
update
assoc
The only difference that I could find from the clojure documentation is that the update function does the following extra thing:
If the key does not exist, nil is passed as the old value.
Are there any specific use cases where in I should use update or assoc other than this?
Or in which cases I should prefer what (If I have missed these use cases).
They both state clear intent in your code when used properly:
assoc hints to the reader, that new kv-pairs are added (or
replacing the values for existing keys) and that they tend to not be
relying on the current values for that keys.
update hints, that one value for a key gets updated and the original
value (if present) maybe used for this calculation.
(update {} :a (fnil inc 0))
; → {:a 1}
(update {:a 41} :a (fnil inc 0))
; → {:a 42}
(assoc {:z 0} :a 42 :b 23}
; → {:z 0, :a 42, :b 23}
You an emulate update with assoc as follows
(assoc m :foo (f (:foo m)))
which looks clunky and is better off as:
(update m :foo f)
which returns a new map with f applied on the the value corresponding to the :foo key.

How to update a reagent atom filtering in a nested vector of maps in Clojure

Let's say I have a reagent atom with a vector of maps like this:
(def my-atom (reagent/atom {:id 256
:name "some name"
:lines [{:code "ab43" :name "first nested name" :quantity 4}
{:code "bc22" :name "second nested name" :quantity 1}
{:code "lu32" :name "third nested name" :quantity 1}}] }))
How can I update the value of a key :quantity at a certain vector nested index, for example: update line with code "bc22" to 10 quantity.
This need to filter to get the index of vector, but haven't the index because filter by "code":
(swap! my-atom assoc-in [:lines 1 :quantity] 10)
I can find with filter, but I can't swap! quantity:
(->> (:lines #my-atom)
(filter #(= (:code %) "bc22")
first))
You can stick with the use of assoc-in but to do so, you have to retrieve the index associated to a given code from the vector of the :lines field in some way.
For example, I would a helper function:
(defn code->index [data code]
(->> data
:lines
(map-indexed (fn [i v] [i v]))
(filter (fn [[_ v]] (= (:code v) code)))
ffirst))
;; (code->index #my-atom "bc22")
;; => 1
And then use it in the swap:
(swap! my-atom assoc-in [:lines (code->index #my-atom "bc22") :quantity] 10)
(require
'[com.rpl.specter :as s])
(let [*a (atom {:id 256
:name "some name"
:lines [{:code "ab43" :name "first nested name" :quantity 4}
{:code "bc22" :name "second nested name" :quantity 1}
{:code "lu32" :name "third nested name" :quantity 1}]})]
(s/setval [s/ATOM :lines s/ALL #(-> % :code (= "bc22")) :quantity] 10 *a))
You've got options here. You could look up the index of the item, you could map over the list, updating only the item your interested in.
Depending on the specifics of the situation, you could also look at either storing the index of the element when the component is rendered, or instead build a set of cursors which are passed to your component. In that case you simply update he cursor like you would an atom, at it handles updating the backing atom efficiently.
Personally, I look at this and wonder if you are using the correct data structure in the first place. It seems probable that code is a natural key here, especially since you are looking to update a line based on it. Perhaps a map with code as the key and the full line as the value would make more sense. This also makes certain undesirable situations impossible (e.x. multiple lines with the same code). Of course you'd lose ordering (unless you re-established it somehow), which may or may not be an issue.

Recursively mutating values in a map of maps with Specter

complete Clojure newbie here. This is a simple question, but I cant seem to get it:
Given that I have a nested hasmap of unknown depth, how do I use Specter's transform() to mutate the values of the data structure? I imagine a recursive-path is required here, but I cant get it to work. a working example is what I'm after and unfortunately, there isn't one in the docs.
(there is an example for set-val on a recursive map, but I don't know how to transform that into a transform use-case)
EDIT: More details were requested, so here they are:
I'm interested in a transform form that can mutate all the values of a nested maps - of any depth). for example, this transform would be able to increment all the values in the following maps (and any other nested map):
{:a 1 :b {:c 2 :d {:e 3}}}
AND
{:a 1 :b {:c 2}}
AND
{:a 1}
the line of code I'm interested in might look sth like this:
(transform <missing selector here> inc data)
Using the linked example:
(def MAP-NODES
(recursive-path [] p
(if-path map?
(continue-then-stay MAP-VALS p))))
(transform [MAP-NODES MAP-VALS number?] inc data)
Be sure to always study the Clojure CheatSheet. For something this simple, it might be easiest to just use clojure.walk/postwalk
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require [clojure.walk :as walk]))
(dotest
(let [data {:a 1 :b {:c 2 :d {:e 3}}}
result (walk/postwalk (fn [item]
(if (int? item)
(inc item)
item))
data)]
(is= result {:a 2, :b {:c 3, :d {:e 4}}})))
For more advanced problems, you may be interested in the function walk-with-parents, which allows you to inspect all items on the path from the root node through to the current node.

clsql: Why are tables being added "magically"?

Suppose I have the following two tables
(clsql:def-view-class class-a ()
((field-one :type integer
:db-kind :key
:db-constraints :auto-increment)
(field-two :type string
:initarg :two
:db-constraints :not-null)))
(clsql:def-view-class class-b ()
((b-one :type integer
:initarg :b-one
:db-kind :key)))
(clsql:create-view-from-class 'class-a)
(clsql:create-view-from-class 'class-b)
Now I want entries of class-a that do not have a corresponding entry in class-b. ("Corresponding" meaning that class-a.field-one maps to class-b.b-one.) Suppose that, class-a has the following two entries:
(clsql:update-records-from-instance (make-instance 'class-a :two "hello"))
(clsql:update-records-from-instance (make-instance 'class-a :two "world"))
So, the query to issue would be:
(clsql:select 'class-a :from [class-a] ; class-b is added "magically"
:where [not [in [class-a field-one]
[select [class-b b-one] :from [class-b]
:where [= [class-a field-one]
[class-b b-one]]]]])
However, this adds class-b as well. (With a purposeful error, I get the query executed as:)
SELECT CLASS_A.FIELD_ONE,CLASS_A.FIELD_TWO FROM CLASS_A,CLASS_B
WHERE (NOT ((CLASS_A.FIELD_ONE
IN (SELECT CLASS_B.B_ONE FROM CLASS_B
WHERE (CLASS_A.FIELD_ONE = CLASS_B.B_ONE)))))
On the other hand,
(clsql:select [*] :from [class-a] ; see the [*]
:where [not [in [class-a field-one]
[select [class-b b-one] :from [class-b]
:where [= [class-a field-one]
[class-b b-one]]]]])
is translated to the expected:
SELECT * FROM CLASS_A
WHERE (NOT ((CLASS_A.FIELD_ONE
IN (SELECT CLASS_B.B_ONE FROM CLASS_B
WHERE (CLASS_A.FIELD_ONE = CLASS_B.B_ONE)))))
Is this a bug or is there some reasoning behind this translation?
As of version 6.7.0, redefining the function clsql-sys::%tables-for-query makes it work "as expected", without breaking anything in the webapp I am working on:
(in-package :clsql-sys)
(defun %tables-for-query (classes from where inner-joins)
(declare (ignore where inner-joins))
(union (mapcar #'select-table-sql-expr classes)
(listify from)))
But I haven't run the test-cases clsql-tests.

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

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))

Resources