Updating one map in a vector of maps in clojure - vector

I have done a lot of searching on this and not found an answer to my specific problem. As background I am taking a coding bootcamp on Java and we are learning JavaScript and Clojure alongside Java. The course content is roughly Java: 50% / JavaScript 30% / Clojure 20%. Having advanced beyond my classmates I am tackling a challenge from my instructor.
I have built an app in Clojure to manage an animal shelter. I am using a vector of hashmaps as my central data store. Things that I have succeeded in doing:
Loading sample data from a hard coded array of hashmaps.
Creating a new animal and adding it's hashmap to the array.
Listing the entire "table" of animals (data from one hashmap per line)
Displaying a multiline detailed view of one animal's record.
What I am struggling with at the moment is my edit function. It is supposed to display the chosen animal's existing data and take any edits the user wants to make then update a working copy of the hashmap and finally update the working copy of the array.
(defn edit-animals [animals]
;; ask which animal to edit index is 1 based from the UI
(let [index (wait-for-int "Please enter the idex of the animal to edit. Enter 0 to display the list of animals" 0 (count animals))]
;; if the user typed a valid index
(if (> index 0)
;; then edit the animal
(let [index (- index 1) ;; index is 0 based for indexing the array
animals animals
animal (nth animals index)
;; prompt with existing data and ask for new data
name (wait-for-string (str "Name: " (:name animal) "\n") false)
species (wait-for-string (str "Species: " (:species animal "\n")) false)
breed (wait-for-string (str "Breed: " (:breed animal "\n")) false)
description (wait-for-string (str "Description: " (:description animal "\n")) false)
;; check for null returns from user
name2 (if (= name "") (:name animal) name)
species2 (if (= species "") (:species animal) species)
breed2 (if (= breed "") (:breed animal) breed)
description2 (if (= description "") (:description animal) description)
;; update local copy of animal
animal (assoc animal :name name2 :species species2 :breed breed2 :description description2)]
;; assoc fails to update animals
;; assoc-in crashes at runtime
animals (assoc animals index animal))
;;(wait-for-enter "\nPress enter to return to the menu")
;; else dispolay the list of animals
(display-animals animals)))
animals)
I have run this code in my debugger and verified that everything is working as expected up to the line:
animal (assoc animal :name name2 :species species2 :breed breed2 :description description2)
The next line fails in one of two ways as I have documented in the comments.
I am aware that atom may be a better way to do this but so far the vector of maps that is constantly passed around is working, so I would like to find a solution to my current problem that does not involve using atom. Once I get this problem solved I plan to switch the project to an atomic data structure. But is a project for another day.
If I have missed a relevant discussion here, please point me in the right direction!

The line:
animals (assoc animals index animal))
does not do what you think it does -- it is not inside the let binding vector.
First of all, good job, you asked the question the right way (with examples, etc.). Congratulations on your coding course. My recommendation would be to keep doing what you're doing, and learn to think in clojure in a very different way than you think in java. They are both worthwhile, just different in approach. In your code, you are doing numerous "temporary assignments" (such as name2 for example). The fact that your let binding vector has 11 pairs is a red flag that you're doing too much. The second item in let, animals animals is particularly strange.
Instead, try to think about the evaluation of expressions rather than the assignment of values. name2 = name1 + ... is a statement, and not an expression. It doesn't do anything. Instead, in a declarative language, almost everything is an expression. In the code below (which I just an extension of what you've done and not necessarily how I'd do it from scratch), note that no local bindings are re-bound (nothing is "assigned to" more than once). let allows us to lexically bind name to the result of an expression, and then we use name to achieve something else. not-empty allows us to do better than using name and name2, which is what you have done.
(def animals [{:name "John" :species "Dog" :breed "Pointer" :description "Best dog"}
{:name "Bob" :species "Cat" :breed "Siamese" :description "Exotic"}])
(defn edit-animals
[animals]
(if-let [index (dec (wait-for-int "Please enter the index of the animal to edit. Enter 0 to display the list of animals" 0 (count animals)))]
(let [animal (nth animals index)
name (or (not-empty (wait-for-string (str "Name: " (:name animal) "\n") false))
(:name animal))
species (or (not-empty (wait-for-string (str "Species: " (:species animal) "\n") false))
(:species animal))
breed (or (not-empty (wait-for-string (str "Breed: " (:breed animal) "\n") false))
(:breed animal))
description (or (not-empty (wait-for-string (str "Description: " (:description animal) "\n") false))
(:description animal))
animal {:name name :species species :breed breed :description description}]
(println "showing new animal: " animal)
(assoc animals index animal))
animals))
(def animals (edit-animals animals))
Note that this does not really achieve much other than restructuring your code. It really does too much, and is not a good example of how a function should do one thing well. But I think your goal for now should be to be a little more idiomatic and get away from the imperative mentality, when you write your clojure. After you do that, you can focus on the design part of it.
Keep up the good work and ask any more questions you have!

in short, you have to return the animals bound in your innermost let (it index > 0), else display current animals and return it. So it would be like this:
(defn edit-animals []
(let [index ...]
(if (> index 0)
;; for an acceptable index you query for input, modify animals, and return modified collection
(let [...]
(assoc animals index animals))
;; otherwise display initial data and return it
(do (display animals)
animals))))
But i would restructure the code more, to make it more clojure-style. First of all i would extract the updating of the animal with input to standalone function, to remove name, name2, breed, breed2... bindings that make the let bindings messy. (upd-with-input), and replace assoc with update. Something like this:
(defn edit-animals [animals]
(letfn [(upd-with-input [animal field prompt]
(let [val (wait-for-string (str prompt (field animal) "\n") false)]
(if (clojure.string/blank? val)
animal
(assoc animal field val))))]
(let [index (dec (wait-for-int "enter index" 0 (count animals)))]
(if (contains? animals index)
(update animals index
(fn [animal]
(-> animal
(upd-with-input :name "Name: ")
(upd-with-input :species "Species: ")
(upd-with-input :breed "Breed: ")
(upd-with-input :description "Description: "))))
(do (when (== -1 index) (display animals))
animals)))))
then i would think of separating the whole part collecting user input from actually updating animals collection.

I think you want to delete animals from
animals (assoc animals index animal))
at the end of the if statement and instead return the result of the function
(assoc animals index animal)
or display them
(display-animals (assoc animals index animal))

Related

trying to use cl-lexer on a file containing "{" and "}"

Using the file "test-lexer.lisp", I have very slightly modified lex to be
(defparameter *lex* (test-lexer "{ 1.0 12 fred 10.23e12"))
and increased the number of times test repeats to 6
(defun test ()
(loop repeat 6
collect (multiple-value-list (funcall *lex*))))
and tried modifying test-lexer in a number of ways to try to get it to recognize "{" as a token.
For example, adding [:punct:] in (deflexer test-lexer ...)
by changing
("[:alpha:][:alnum:]*"
(return (values 'name %0)))
to
("[:alpha:][:alnum:][:punct:]*"
(return (values 'name %0)))
and consistently get errors like
"""Lexer unable to recognize a token in "{ 1.0 12 fred 10.23e12", position 0 ("{ 1.0 12 fred 10.23e")
[Condition of type SIMPLE-ERROR]"""
How can i specify "{" as a character to be recognized? Or is my problem elsewhere?
The cl-lexer system is based on regular expressions, so you can put any literal character to stand for itself, like {. But it happens that the brace character has a special meaning in the regular expression language, so you need to quote it with a backslash. In order to write a backslash in Lisp strings, backslashes need to be escaped. Hence:
(deflexer test-lexer
("\\{" (return (values :grouping :open-brace))) ;; <-- Here
("[0-9]+([.][0-9]+([Ee][0-9]+)?)"
(return (values 'flt (num %0))))
("[0-9]+"
(return (values 'int (int %0))))
("[:alpha:][:alnum:]*"
(return (values 'name %0)))
("[:space:]+"))
I return the :open-brace value and the :grouping category, but you can choose to return something else if you want.
The test function then returns:
((:GROUPING :OPEN-BRACE) (FLT 1.0) (INT 12)
(NAME "fred") (FLT 1.023e13) (NIL NIL))

Clojure: Apply a nested vector format to a flattened vector

For example, I have the following nested vector:
[[[0.582198689235419 -0.34713183143727 0.4685311493624731]
[-0.38928013774079284 -0.5901700383677557 -0.37573234072157]
[0.6716356761877877 -0.19645167952721243 -0.5700686091940252]]
[[0.0027162308840597005 -0.4483592764429284 -0.4766278022217257 -0.2724018313051576]
[-0.2765881229144672 -0.8030656496255356 -0.16159395457031567 -0.27432324260043084]
[-0.6154630466545907 -0.60573539482247 0.4417814561800192 -0.5559788990464504]
[0.6194560094536031 -0.3663074359460578 -0.5704311251195602 0.7194827876969362]]]
And I have the following flattened vector:
(0.5366343712173423
-0.816449781850872
-0.16066485785704843
0.9816561233924161
-0.09646744313584676
-0.2619662625757997
-0.9946004265996822
-0.14096299956754854
0.579260850612288
-0.827601452607939
-0.24934665032374648
-0.42272393175707873
0.11239245249400165
-0.29878238708035043
-0.61522274672097
0.8298721730401472
0.5016214138116059
0.11633537727916243
-0.0631891708267196
-0.26569217599364303
0.20900664784109668
0.2005869506108401
-0.2658279978034501
0.3383997403318165
-0.09353513546647907)
I want the flattened vector to be converted to a nested vector that follows the same structure as the nested vector presented above. Is there a core function in Clojure or a library that does this? I have some ideas of how to solve this problem, but all of them seem very very inefficient, and this operation will be used with big vectors.
Thank you very much in advance.
This was a fun question to answer, because it is one of the very few times when I think using a zipper makes things easier instead of harder. The idea is to just make a vector-zip of the nested vector, to represent the desired structure, and call zip/next on it repeatedly; whenever we get to a node which is a leaf, we replace its value with the next one from the input sequence.
Note that this assumes there are exactly as many items in the nested structure as in the flattened list; if that is not the case, you will probably get an error of some kind, who knows.
(require '[clojure.zip :as z])
(defn replace-values [structure values]
(loop [z (z/vector-zip structure)
values (seq values)]
(cond (not values) (z/root z)
(z/branch? z) (recur (z/next z) values)
:else (recur (-> z
(z/replace (first values))
(z/next))
(next values)))))
user> (replace-values '[[[0.582198689235419 -0.34713183143727 0.4685311493624731]
[-0.38928013774079284 -0.5901700383677557 -0.37573234072157]
[0.6716356761877877 -0.19645167952721243 -0.5700686091940252]]
[[0.0027162308840597005 -0.4483592764429284 -0.4766278022217257 -0.2724018313051576]
[-0.2765881229144672 -0.8030656496255356 -0.16159395457031567 -0.27432324260043084]
[-0.6154630466545907 -0.60573539482247 0.4417814561800192 -0.5559788990464504]
[0.6194560094536031 -0.3663074359460578 -0.5704311251195602 0.7194827876969362]]]
'(0.5366343712173423
-0.816449781850872
-0.16066485785704843
0.9816561233924161
-0.09646744313584676
-0.2619662625757997
-0.9946004265996822
-0.14096299956754854
0.579260850612288
-0.827601452607939
-0.24934665032374648
-0.42272393175707873
0.11239245249400165
-0.29878238708035043
-0.61522274672097
0.8298721730401472
0.5016214138116059
0.11633537727916243
-0.0631891708267196
-0.26569217599364303
0.20900664784109668
0.2005869506108401
-0.2658279978034501
0.3383997403318165
-0.09353513546647907))
[[[0.5366343712173423 -0.816449781850872 -0.16066485785704843]
[0.9816561233924161 -0.09646744313584676 -0.2619662625757997]
[-0.9946004265996822 -0.14096299956754854 0.579260850612288]]
[[-0.827601452607939 -0.24934665032374648 -0.42272393175707873 0.11239245249400165]
[-0.29878238708035043 -0.61522274672097 0.8298721730401472 0.5016214138116059]
[0.11633537727916243 -0.0631891708267196 -0.26569217599364303 0.20900664784109668]
[0.2005869506108401 -0.2658279978034501 0.3383997403318165 -0.09353513546647907]]]

How to prevent close!-ing before put-ing in onto-chan

I'd like to run a code like
(->> input
(partition-all 5)
(map a-side-effect)
dorun)
asynchronously dividing input and output(a-side-effect).
Then I've written the code to experiment below.
;; using boot-clj
(set-env! :dependencies '[[org.clojure/core.async "0.2.374"]])
(require '[clojure.core.async :as async :refer [<! <!! >! >!!]])
(let [input (range 18)
c (async/chan 1 (comp (partition-all 5)
(map prn)))]
(async/onto-chan c input false)
(async/close! c))
explanation for this code:
Actually elements in input and its quantity is not defined before running and elements in input is able to be taken by some numbers from 0 to 10.
async/onto-chan is used to put a Seq of elements (a fragment of input) into the channel c and will be called many times thus the 3rd argument is false.
prn is a substitute for a-side-effect.
I expected the code above prints
[0 1 2 3 4]
[5 6 7 8 9]
[10 11 12 13 14]
[15 16 17]
in REPL however it prints no characters.
And then I add a time to wait, like this
(let [c (async/chan 1 (comp (partition-all 5)
(map prn)))]
(async/onto-chan c (range 18) false)
(Thread/sleep 1000) ;wait
(async/close! c))
This code gave my expected output above.
And then I inspect core.async/onto-chan.
And I think what happend:
the channel c was core.async/close!ed in my code.
each item of the argument of core.async/onto-chan was put(core.async/>!) in vain in the go-loop in onto-chan because the channel c was closed.
Are there sure ways to put items before close!ing?
write a synchronous version of onto-chan not using go-loop?
Or is my idea wrong?
Your second example with Thread.sleep only ‘works’ by mistake.
The reason it works is that every transformed result value that comes out of c’s transducer is nil, and since nils are not allowed in channels, an exception is thrown, and no value is put into c: this is what allows the producer onto-chan to continue putting into the channel and not block waiting. If you paste your second example into the REPL you’ll see four stack traces – one for each partition.
The nils are of course due to mapping over prn, which is a side-effecting function that returns nil for all inputs.
If I understand your design correctly, your goal is to do something like this:
(defn go-run! [ch proc]
(async/go-loop []
(when-let [value (<! ch)]
(proc value)
(recur))))
(let [input (range 18)
c (async/chan 1 (partition-all 5))]
(async/onto-chan c input)
(<!! (go-run! c prn)))
You really do need a producer and a consumer, else your program will block. I’ve introduced a go-loop consumer.
Very generally speaking, map and side-effects don’t go together well, so I’ve extracted the side-effecting prn into the consumer.
onto-chan cannot be called ‘many times’ (at least in the code shown) so it doesn’t need the false argument.
taking megakorre's idea:
(let [c (async/chan 1 (comp (partition-all 5)
(map prn)))
put-ch (async/onto-chan c (range 18) false)]
(async/alts!! [put-ch])
(async/close! c))

How do I map database columns to a struct in a list of structs in Racket?

I have a list called attendance-events, consisting of struct type attendance-event. The struct is defined as
(define-struct attendance-event (date flag))
The "date" is the date of the event and "flag" is a 2 character value that represents the type of event.
In my database, I am selecting "select date, code from attendance_table", and I want those two columns to be added to the list of attendance-events for each row.
So, how can this be done?
This performs the query and returns a list of attendance-event structures:
(for/list ([(date code)
(in-query conn "select date, code from attendance_table")])
(make-attendance-event date code))
I find it much easier to work with database rows after they've been converted to hash tables. The following function converts the rows-result type returned by the query function into a list of immutable hashes:
(require (planet jphelps/loop)) ;; Use the loop macro from Common Lisp.
(define (rows-result->hash results)
(let ((header (rows-result-headers results)))
(loop for row in (rows-result-rows results)
collect (loop for ((_ . name) (_ . decltype)) in header
for column across row
with-collection-type hash/immutable
collect (cons name column)))))
Then you can wrap the query function like this:
(define (simple-query query-text conn . params)
(rows-result->hash (apply query (list* conn query-text params))))
Finally, to create your struct:
(loop for row in (simple-query "select date,code from attendance_table;")
collect (make-attendance-event (hash-ref row 'date) (hash-ref row 'code)))

Lisp, why is this number not a float

Using Common Lisp I am trying loop through a list of students and if the GPA is greater than or equal to 3.0 I want to push a 1 onto another list called equal_names. The problem I am having is the interpreter keeps saying the GPA in the comparison list is "not of type (or rational float)". Why am I getting this error?
Yes, this is for homework. Also this is my first time asking on here, so if you need anything else please let me know.
Sample of the list I am getting the GPA from, where the GPA is 2.307...:
(SETQ students (LIST
(LIST (LIST 'Abbott 'Ashley 'J) '8697387888 'NONE 2.3073320999676614)))
The code I have written:
(setq gpa_count ())
(loop for x in students
if(>= 3.0 (cdr (cdr (cdr x))))
do(push '1 gpa_count))
Given a non-empty list cdr returns the tail of that list, i.e. the list that contains all the elements of the list but the first. The important thing to note is that it returns a list, not an element. That is (cdr (cdr (cdr x))) returns the list (2.30733...), not the float 2.30733.
The loop iterates the outer list. To understand the code in the loop you can look at the first element in students, which is:
'((Abbott Ashley J) 8697387888 NONE 2.3073320999676614)
Now we are going to orientate the list. Every time you pass an element add a d.
Every time you pick a value or go to a list in the list you add an a.
To find how to access the number 2.307.... You look at the first element element in the list:
(Abbott Ashley J) d
8697387888 d
NONE d
Now we are at the part that you are interested in, ie. (2.3073320999676614)), thus you add an a. Now order those in reverse and put a c in front and a r in the end.. It becomes cadddr In light of that your loop should be:
(setq students '(("Mary Larson" 333 NONE 1.1)
("Mina Morson" 333 NONE 2.5)
("Magnus Outsider" 333 NONE 4.1)))
(setq gpa_count ())
(loop for x in students
if (>= 3.0 (cadddr x))
do (push '1 gpa_count))
gpa_count ; ==> (1 1)
Another example:
(setq x (1 (2 3) (3 4 (5 6) 7))) ; ==> (1 (2 3) (3 4 (5 6) 7))
To get the 3*. We follow the parts. 1 == d, (2 3) == a, 2 ==d, 3* == a. In reverse: adad and add c and r to the ends ==> cadadr. thus:
(cadadr '(1 (2 3) (3 4 (5 6) 7))) ; ==> 3
To get the 5. we do the same 1 == d, (2 3) == d and then we have the list we want ==a.
Then 3 ==d, 4 ==d, (5 6) ==a. The 5 is the first element == a. In reverse aaddadd. Now CL guarantees 4 letters accessors so we need to split it up in 4s from the right. Thus it becomes:
(caadr (cdaddr '(1 (2 3) (3 4 (5 6) 7)))) ; ==> 5
Now, without describing you can pick any number or list. Eg. to get (5 6) ddadda, in reverse and split up becomes (cadr (cdaddr x))
Hope it helps.
If your data format is consistent then
(fourth x)
will return the GPA.
Going further,
(setf (symbol-function 'gpa)(function fourth))
would provide
(gpa x)
as "an accessor" for the gpa in the data structure.
My CLISP 2.49 gives this error message:
*** - >=: (2.307332) is not a real number
Let's look at that error message: >=: (2.307332) is not a real number.
The error happens at the call to >= and one argument is a list of a number, not a number.
Since you try to extract the number from a list, does that extract work?
We see that you call CDR. CDR of a list returns a list. So there is the error. You need to extract the number from the list.
Btw., CLISP has commands like help, where, backtrace, ... to further investigate the problem. Just type help and return, without anything else, and you see a list of commands.

Resources