Adding new objects to list and showing them - clojure

i started learning clojure and i got stuck with this thing,i have a knowledge of java and maybe my approach is too java-like but my real question is-i made a function that lets the user type a few strings,now i want when user enters those strings to make a new object and then insert it into a list so i can show the elements of that list later,how can it be done?i tried...
(defn unesi []
(println "Unesite ime i prezime studenta: ")
(let [imePrezime (read-line)]
(println "Unesite index studenta: ")
(let [index (read-line)]
(println "Unesite email studenta: ")
(let [email (read-line)]
(def s1 {:ime imePrezime :index index :email email})
(println "Uspjesno ste unijeli studenta!")
)
)
)
)
i have managed to make a new object but it is always the same one,how can i always add a new one?and how to add it to a list to show elements later?

I'm not sure you're starting with clojure with the right example. Clojure favors immutability, and you're trying just to mutate something.
None the less, addressing your question.
I'm using an atom students for the list state between function calls.
I defined a local function read-input which prints the input message then reads the line for a little reuse.
Each new student gets conjed into the existent students list, and the list gets swaped with the new one.
Code:
(def students (atom []))
(defn unesi []
(letfn [(read-input [message]
(println message)
(read-line))]
(let [imePrezime (read-input "Unesite ime i prezime studenta: ")
index (read-input "Unesite index studenta: ")
email (read-input "Unesite email studenta: ")]
(swap! students #(conj % {:ime imePrezime :index index :email email}))
(println "Uspjesno ste unijeli studenta!"))))
You can then access the list using the deref operator #
user> #students
=> [{:ime "guille", :index "3", :email "guille#email"}
{:ime "cacho", :index "pedro", :email "lala"}]

Related

CLOJURE: How to find date-diff in days and also optimized way of doing nested loop in Clojure

I am a newbie to Clojure and trying to do some simple validation to verify courses completed by a students are having gap in days, if so then how many days.
Wanted to iterate over the courses and find such courses where the difference between course1 and course2 are more than 20days, this will be done by finding the difference between (start-date of course2 - end-date of course1)
here is my test-data-set, where there is a gap of more than 20days between
advanced-science-102 and datascience-science-101 courses.
[{:course-type "basic-science-101"
:start-date "2020-01-01"
:end-date "2020-05-01"}
{:course-type "advanced-science-102"
:start-date "2020-05-15"
:end-date "2020-10-01"}
{:course-type "datascience-science-101"
:start-date "2020-12-01"
:end-date "2021-03-20"}
]
I have this code so far where I am iterating through the courses and trying to find the difference between start-date and end-date of two courses to decide if there are any gaps between.
(ns student-course-ruleset.courses-test
(:require [java-time :as jt]))
(def mycourses [{:course-type "basic-science-101"
:start-date "2020-01-01"
:end-date "2020-05-01"}
{:course-type "advanced-science-102"
:start-date "2020-05-20"
:end-date "2020-10-01"}
{:course-type "datascience-science-101"
:start-date "2020-11-15"
:end-date "2021-01-20"}])
(defn select-values [map ks]
(remove nil? (reduce #(conj %1 (map %2)) [] ks)))
(defn find-gap-in-course [mycourses]
(loop [[course1 & mycourses] mycourses]
(loop [[course2 & mycourses] mycourses]
(when (and (not-empty course1) (not-empty course2))
(println "Finding gap of courses : " course1 course2)
(println "Current courses in comparison are : " (select-values course1 [:course-type])
(select-values course2 [:course-type]))
(println "Finding Gap in courses: ")
;; (jt/gap (select-values course2 [:start-date]) (select-values course1 [:end-date]))
)
)
(if course1 (recur mycourses))
))
(find-gap-in-course mycourses)
You can test with (t/interval) from clj-time
https://github.com/clj-time/clj-time

Function for applying to a user with a various number of data fields

The question was born when I was practicing an Observer topic in a tutorial
I am trying to apply the function to the user but cannot use user's data fields like name, surname.
Let's say that the user may have various number of data fields so we must use & args argument. My code that does not work:
(ns observer.core)
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(def user2 {:name "Jane" :surname "Smith"})
(apply
(fn [& args] (println (str "I am " (:name args) " " (:surname args) ".")))
user)
(apply
(fn [& args] (println (str "My sister is " (:name args) " " (:surname args) ".")))
user2)
The output:
I am .
My sister is .
observer.core>
How to fix it regarding that the apply function must be used?
apply converts a map to a seq, i.e.
{:name "Alan" :surname "Smith" :alias "Mike"} becomes ([:name "Alan"] [:surname "Smith"] [:alias "Mike"])
You could put it back into a map, if that is what you need:
(let [user {:name "Alan" :surname "Smith" :alias "Mike"}]
(apply
(fn [& args]
(let [args (into {} args)]
(println (str "I am " (:name args) " " (:surname args) "."))))
user))
but this looks a bit of a stretch to me. I believe the solution could have been better if I knew how this function is supposed to be used.
Usually there are two types of functions: (fn :text "some" :row 25) and (fn {:text "some" :row 25}).
In the spirit of learning:
Check out Clojure - Cheatsheet.
10 years with Clojure, and I still use it daily.
(apply some-func (list x y z)) becomes (some-func x y z), because apply assumes that the second argument is a list (which it then unpacks).
And what you are currently doing is collecting all the arguments back into a list called args
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(apply
(fn [& args]
(prn 'ARGS args) ;; lets see what is actually in args
(println (str "I am " (:name args) " " (:surname args) ".")))
user)
;; -> ARGS ([:name "Alan"] [:surname "Smith"] [:alias "Mike"])
;; -> I am .
And the outut is as #akond says.
You could, of course, put 'user' in a vector (or list), but then don't use '&' to collect everything back into a list, (which you would then have to pick stuff out of again):
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(apply
(fn [args]
(prn 'ARGS args)
(println (str "I am " (:name args) " " (:surname args) ".")))
[user])
That would give you the output you expected. But this is a bit strange, perhaps, but certainly viable if you must use apply and you can control the "list" part of the argument.
So, #akond's solution is simple and clean.
And augmenting it with Clojure "destructing":
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(apply
(fn [& args]
(let [{:keys [name surname alias]} (into {} args)]
(println (str "I am " name " " surname "." (when alias (str " But call me " alias "!"))))))
user)
I believe you intended to do something like this:
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(def user2 {:name "Jane" :surname "Smith"})
(defn fn-1
[item]
(println (str "I am " (:name item) " " (:surname item) ".")) )
(defn fn-2
[item]
(println (str "My sister is " (:name item) " " (:surname item) ".")))
(fn-1 user)
(fn-2 user2)
with result:
I am Alan Smith.
My sister is Jane Smith.
One has to wrap a user object or the map by a list.
(ns observer.core)
(defrecord Person [name balance])
(def user (Person. "Alan" 150.34))
(def user2 {:name "Adam" :balance 629.74})
(def observers (atom #{}))
(swap! observers conj (fn [l] (println (str "2. " (:name l)))))
(swap! observers conj (fn [l] (println (str "1. " (:balance l)))))
(println "user")
(vec (map #(apply % (list user)) #observers))
(println "\nuser2")
(vec (map #(apply % (list user2)) #observers))
Output
user
1. 150.34
2. Alan
user2
1. 629.74
2. Adam
observer.core>

Clojure csv to map like {:key1 #{a, b, c}, :key2 #{n, m, k}}

After loading :
(csv/read-csv "Fetch, get, bring \n Take, receive, accept")
I get :
(["Fetch" " get" " bring "] [" Take" " receive" " accept"])
Now, I want to turn it into a map with unique keys and sets as values like:
[:key1 #{Fetch, get, bring}, :key2 #{Take, receive, accept}]
My goal is to be able to look up a word, say get, and return "Fetch, bring"
My goal is to be able to look up a word, say get, and return "Fetch, bring"
Not completely sure about your goal, but that did not stop me from implementing a function that gets all siblings of a word. I don't think you need a map with random keys, or do you? Note that a set is implemented as a hash-map where the values are the same as the keys (e.g., #{:a :b} is a wrapping around {:a :a, :b :b}).
Now, first parse the data to a list of words-sets:
(def word-sets
(map (comp set #(map string/trim %))
(csv/read-csv "Fetch, get, bring \n Take, receive, accept")))
;; => (#{"bring" "Fetch" "get"} #{"accept" "Take" "receive"})
Then the function to get the siblings of a word:
(defn siblings [word]
(mapcat #(when (contains? % word) (disj % word))
word-sets))
Using the set operator contains? we check every word-set if it contains the word, if so we return that set with that word disjoined. Because of the when, a word-set that does not contain the word becomes nil and mapcat removes the nil entries and concats the rest to one flat list.
E.g.,
(siblings "get")
;; => ("bring" "fetch")
(siblings "Take")
;; => ("accept" "receive")
(siblings "non existing")
;; => '()
I was able to get something similar, does the trick for me.
inp being (["Fetch" " get" " bring "] [" Take" " receive" " accept"]) .
(def x (map #(into #{} %) inp))
-> [#{Fetch, get, bring} #{Take, receive, accept}]
(map #(hash-map (gensym ":key") %) x)
-> ({:key6393 #{" bring " " get" "Fetch"}} {:key6394 #{" Take" " receive" " accept"}})

Clojure say-hi with varargs

Input: "Michael" "Julia" "Joe" "Sam"
Output: Hi, Michael, Julia, Joe, and Sam. (pay attention to the commas and the word "and")
Input: nil
Output: Hi, world.
Here is my first attempt:
(defn say-hi [& name]
(print "Hi," name))
user> (say-hi "Michael")
Hi, (Michael)
nil
user> (say-hi "Michael" "Julia")
Hi, (Michael Julia)
nil
Question:
How to implement default: (no input, say "Hi World!")
How to get rid of the parents around names in output?
How to implement the commas separation and add the conjunction word "and"?
First off, Clojure supports multi-arity functions, so you could do something like this to achieve default behaviour:
(defn say-hi
([] (say-hi "World"))
([& names] ...))
Then, what you want is to take a seq and join all the strings it contains together, using ", " in between. The clojure.string namespaces contains lots of string manipulation functions, one of them being clojure.string/join:
(require '[clojure.string :as string])
(string/join ", " ["Michael", "Julia"])
;; => "Michael, Julia"
But the last element of the seq should be concatenated using " and " as a separator, so you'll end up with something like this:
(require '[clojure.string :as string])
(defn say-hi
([] (say-hi "World"))
([& names]
(if (next names)
(format "Hi, %s, and %s!"
(string/join ", " (butlast names))
(last names))
(format "Hi, %s!" (first names)))))
Note that you have to differentiate between the single- and multi-name cases and (next names) basically checks whether the seq contains more than one element. (You could achieve the same by adding another arity to the function.)
(say-hi)
;; => "Hi, World!"
(say-hi "Michael")
;; => "Hi, Michael!"
(say-hi "Michael" "Julia" "Joe" "Sam")
;; => "Hi, Michael, Julia, Joe, and Sam!"
You can use clojure.string/join:
(use '[clojure.string :only [join]])
(defn sentencify [& elems]
(->>
[(join ", " (butlast elems)) (last elems)]
(remove empty?)
(join " and ")))
(defn say-hi [& name]
(print "Hi," (if name
(sentencify name)
"World!")))
A concise solution:
(defn say-hi [& names]
(let [names (case (count names)
0 ["world"]
1 names
(concat (butlast names) (list (str "and " (last names)))))]
(->> names, (cons "Hi"), (interpose ", "), (apply str))))
(say-hi)
;"Hi, world"
(say-hi "Michael")
;"Hi, Michael"
(say-hi "Michael" "Julia" "Joe" "Sam")
;"Hi, Michael, Julia, Joe, and Sam"
For long lists of names, you would want to eschew count, last, and butlast, maybe by pouring names into a vector first.
To print (as the question does) rather than return the formatted string, append print to the final form:
(->> names, (cons "Hi"), (interpose ", "), (apply str), print)

How to remove item from list in Clojure

I want to delete room by number. As you can see, the rooms is atom list and contains atoms. I got an exception: IllegalArgumentException Don't know how to create ISeq from: core.main$delete_room_by_id$fn__7541 clojure.lang.RT.seqFrom (RT.java:487)
I have this code:
(comment ------------- * DATA * ----------------- )
(def rooms "atomic list of atoms - rooms" (atom '()))
(comment ------------- * UTILS * ----------------- )
(defn enter-value [message]
(do (println message) (read-line)))
(comment ------------- * ADD ROOM * ----------------- )
(defn save-room "The function that will save provided room."
[number num-of-beds price]
(swap! rooms conj (atom {:number number
:num-of-beds num-of-beds
:price price
:is-ocupated false})))
(defn enter-room "This function will create room based on user input." []
(let [number (enter-value "Number...")
num-of-beds (enter-value "Number of beds...")
price (enter-value "Price...")]
(save-room number num-of-beds price)))
(comment ------------- * DELETE ROOM * ----------------- )
(defn delete-room-by-number "Delete room by number."
[number]
(swap! rooms remove #(not (= (:number #%) number))))
I think that swap! function don't put parameters for remove function as I want. I think that final command is: (remove rooms #(not (= (:number #%) number))). This is not good because I must to deref rooms like #rooms and pass it as second parameter of remove function.
Thanks for reading this.
There is a mistake in two functions. The value in save-room should not be a map in an atom, but just a map, because else you get atoms saved in an atom.
Also delete-by-room-number contained a mistake, the anonymous function wasn't written correctly.
(defn save-room "The function that will save provided room."
[number num-of-beds price]
(swap! rooms conj {:number number :num-of-beds num-of-beds :price price :is-ocupated false}))
(defn delete-room-by-number [num]
(swap! rooms #(remove (fn [room] (= (:number room) num)) %)))
Update:
It is more common practice to store an immutable, possibly nested, datatructure in an atom/ref,etc. A better option might be to not to go for a list but a vector or map, like this:
(def room-map {1 {:nums-of-beds 2 :price 30}, 2 {:nums-of-beds 4 :price 60}})
This way you use the room number as the key. This way you can never have a duplicate room number, because map keys must be unique.
You can update the map with assoc-in, update-in etc:
(def new-room-map (assoc-in room-map [2 :nums-of-beds] 40))
Value of new-room-map: {1 {:nums-of-beds 2, :price 30}, 2 {:nums-of-beds 40, :price 60}}
If you are going for the map representation of your rooms, use the function assoc-in in combination with swap! and an updated version of your room-map will be stored in the atom. If you have trouble understanding this, I suggest you read more on these functions:
http://clojuredocs.org/clojure_core/clojure.core/swap!
http://clojuredocs.org/clojure_core/clojure.core/assoc-in