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

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.

Related

How to remove element from mutable vector?

I know how to create mutable vector:
(defn create-vector []
(intern *ns* (symbol "my-vector" (ref []))
=>my-namespace/my-vector
I know how to add element to that vector:
(dosync (alter my-vector conj "test"))
=>["test"]
Now I have "test" string in my mutable vector. But how I can remove it? I tried to use lazy-sequence function remove
(dosync (alter my-vector remove "test"))
but it doesn't work. How can I remove element from mutable vector?
EDIT:
The collection doesn't really have to be vector. I found solution for set, but will wait if someone will suggest solution for vector.
your mistake is that you pass the "test" arg to remove function, while it really accepts a predicate. In your case this one wold work (unproperly though, making a list instead of a vector):
(dosync (alter my-vector #(remove #{"test"} %)))
to keep it a vector you would have to do this:
(dosync (alter my-vector #(vec (remove #{"test"} %))))
what makes me curious about your example:
why would you use this weird construction:
(defn create-vector []
(intern *ns* (symbol "my-vector" (ref []))))
instead of just (def my-vector (ref []))
why do you use ref at all? would you be using it inside some transaction? if not, i would propose you to move to an atom (since it is also thread safe)
user> (def my-atom (atom []))
#'user/my-atom
user> (swap! my-atom conj "hello")
["hello"]
user> (swap! my-atom #(remove #{"hello"} %))
()
As specified in my edit, collection doesn't have to be really vector. #leetwinski posted answer how to do it for vector, but if I would decide to use set I found other solution. So if we create set the same way as in question replacing [] to #{}:
(defn create-vector []
(intern *ns* (symbol "my-set" (ref #{}))
=>my-namespace/my-vector
and add element:
(dosync (alter my-set conj "test"))
=>#{"test"}
, we can remove it
(dosync (alter my-set disj item))
=>#{}

Recur not at tail position

How can I use something similiar to recurnot at tail position?
Take a look at my code:
(defn -main [& args]
(println "Hi! Type a file name...")
(defn readFile[])
(let [fileName(read-line)]
(let [rdr (reader fileName)]
(if-not (.exists rdr)
((println "Sorry, this file doesn't exists. Type a valid file name...")
(recur)))
(defn list '())
(doseq [line (line-seq rdr)]
(if-not (= "" line)
(concat list '(line)))
(list))))
(defn fileLinesList (readFile))
...
...)
I know I can't use recur here... But I neither know how can I make it in clojure.
I'm a newbie in Clojure and I'm coming from a OOP context. So...
Is there a way to use recursion in this case?
What would be an alternative?
First of all you should not nest your functions definitions in another defn (-main in this case). defn or def always defines symbol bindings at the top level of namespace and they don't nest. If you want to define a locally scoped function you need to use let and fn, e.g.
(let [my-fn (fn [a b] (+ a b))]
(my-fn 1 2))
In your particular case I think it would be easier to split your code into multiple functions. This way it will be more readable.
Prompting for a file name is one piece of your logic.
(defn get-existing-filename []
(let [filename (read-line)]
(if (.exists (java.io.File. filename))
filename
(do
(println "Sorry, this file doesn't exists. Type a valid file name...")
(recur)))))
Then you can use it to read a file removing empty lines:
(with-open [input (clojure.java.io/reader (get-existing-filename))]
(->> (line-seq input)
(remove empty?)
(doall)))
For a file with following content:
AAA
BBB
CCC
DDD
it will return
("AAA" "BBB" "CCC" "DDD")
If you really want it as a single function, the following will work:
(defn read-file []
(let [filename (read-line)]
(if (.exists (java.io.File. filename))
(with-open [input (clojure.java.io/reader (get-existing-filename))]
(->> (line-seq input)
(remove empty?)
(doall)))
(do
(println "Sorry, this file doesn't exists. Type a valid file name...")
(recur)))))
Finally, this function can be called from -main.
I have also noticed another issue in your sample code:
((println "Sorry, this file doesn't exists. Type a valid file name...")
(recur))
if and if-not require a single expression for their then and else branches. If you want to have multiple expressions you need to nest them in do:
(do
(println "Sorry, this file doesn't exists. Type a valid file name...")
(recur))
If you need if or if-not without the else branch then you can use when or when-not macros. Then you don't need to wrap multiple expressions because when/when-not will wrap them for your inside of do.
(when true
(println 1)
(println 2))
is equivalent to
(if true
(do
(println 1)
(println 2)))

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

Can a var be a key in a map?

(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.

Can you get the "code as data" of a loaded function in Clojure?

To put it another, way, "Okay, so code is data..."
That thread addresses how to read from a source file, but I'm wondering how to get the s-expression of an already-loaded function into a data structure that I can read and manipulate.
In other words, if I say,
(defn example [a b] (+ a b))
can't I get that list at runtime? Isn't this the whole point of "code as data"?
This is really a general Lisp question, but I'm looking for an answer in Clojure.
You can use the clojure.repl/source macro to get the source of a symbol:
user> (source max)
(defn max
"Returns the greatest of the nums."
{:added "1.0"
:inline-arities >1?
:inline (nary-inline 'max)}
([x] x)
([x y] (. clojure.lang.Numbers (max x y)))
([x y & more]
(reduce1 max (max x y) more)))
nil
But this is only part of the answer. AFAICT source looks up the source filename and line number that define the given symbol, and then prints the source code from the file. Therefore, source will not work on symbols that you do not have the source for, i.e. AOT-compiled clojure code.
Coming back to your original question, you can think of source as reading the meta data associated with the given symbol and simply printing that. I.e. it's cheating. It's not in any way returning "code as data" to you, where with code I mean a compiled clojure function.
In my mind "code as data" refers to the feature of lisps where source code is effectively a lisp data structure, and therefore it can be read by the lisp reader. That is, I can create a data structure that is valid lisp code, and eval that.
For example:
user=> (eval '(+ 1 1))
2
Here '(+ 1 1) is a literal list which gets read by the clojure reader and then evaluated as clojure code.
Update: Yehonathan Sharvit was asking in one of the comments if it's possible to modify the code for a function. The following snippet reads in the source for a function, modifies the resulting data structure, and finally evaluates the data structure resulting in a new function, my-nth, being defined:
(eval
(let [src (read-string (str (source-fn 'clojure.core/nth) "\n"))]
`(~(first src) my-nth ~#(nnext src))))
The syntax-quote line replaces nth with my-nth in the defn form.
You can get the source in recent versions of clojure with the source function.
user=> (source nth)
(defn nth
"Returns the value at the index. get returns nil if index out of
bounds, nth throws an exception unless not-found is supplied. nth
also works for strings, Java arrays, regex Matchers and Lists, and,
in O(n) time, for sequences."
{:inline (fn [c i & nf] `(. clojure.lang.RT (nth ~c ~i ~#nf)))
:inline-arities #{2 3}
:added "1.0"}
([coll index] (. clojure.lang.RT (nth coll index)))
([coll index not-found] (. clojure.lang.RT (nth coll index not-found))))
nil
to get the string as a value you can wrap this in with-out-str:
user=> (with-out-str (source nth))
"(defn nth\n \"Returns the value at the index. get returns nil if index out of\n bounds, nth throws an exception unless not-found is supplied. nth\n also works for strings, Java arrays, regex Matchers and Lists, and,\n in O(n) time, for sequences.\"\n {:inline (fn [c i & nf] `(. clojure.lang.RT (nth ~c ~i ~#nf)))\n :inline-arities #{2 3}\n :added \"1.0\"}\n ([coll index] (. clojure.lang.RT (nth coll index)))\n ([coll index not-found] (. clojure.lang.RT (nth coll index not-found))))\n"
user=>
That was my message; nice to meet you ;-) BTW, the references given in that thread for answers were excellent reading; so if you're interested, you might want to take the time to read them. Back to your question though source seems to work for code that was loaded through a file, but it doesn't work in all cases. I think, specifically, it doesn't work for functions defined in the repl.
user=> (def foo (fn [] (+ 2 2)))
#'user/foo
user=> (source foo)
Source not found
nil
user=> (defn foo2 [] (+ 2 2))
#'user/foo2
user=> (source foo2)
Source not found
nil
Digging a little bit...
user=> (source source)
(defmacro source
"Prints the source code for the given symbol, if it can find it.
This requires that the symbol resolve to a Var defined in a
namespace for which the .clj is in the classpath.
Example: (source filter)"
[n]
`(println (or (source-fn '~n) (str "Source not found"))))
nil
user=> (source clojure.repl/source-fn)
(defn source-fn
"Returns a string of the source code for the given symbol, if it can
find it. This requires that the symbol resolve to a Var defined in
a namespace for which the .clj is in the classpath. Returns nil if
it can't find the source. For most REPL usage, 'source' is more
convenient.
Example: (source-fn 'filter)"
[x]
(when-let [v (resolve x)]
(when-let [filepath (:file (meta v))]
(when-let [strm (.getResourceAsStream (RT/baseLoader) filepath)]
(with-open [rdr (LineNumberReader. (InputStreamReader. strm))]
(dotimes [_ (dec (:line (meta v)))] (.readLine rdr))
(let [text (StringBuilder.)
pbr (proxy [PushbackReader] [rdr]
(read [] (let [i (proxy-super read)]
(.append text (char i))
i)))]
(read (PushbackReader. pbr))
(str text)))))))
nil
So yeah, it looks like it tries to load the source file off the classpath to try to spit it out for you. One thing I've learned when working with Clojure is that 9 times out of 10 it is useful to look at the source.

Resources