In other languages it is pretty self-explanatory in how to create a struct. How would you do something like this in Clojure?
For example, I'd like to add a person's first and last name, their address, and telephone numbers (home and mobile).
I'm guessing I'd want to make a vector?
Thanks in advance!
In Clojure, you would usually use a map to represent that sort of data. You could use a flat map:
{:first-name "Jeremy"
:last-name "Martinson"
:street "455 Larkspur Dr."
:city "Baviera"
:state "California"
:zip 22611
:home-phone "(555) 555-5555"
:mobile-phone "(666) 666-6666"}
Or a nested one:
{:name {:first "Jeremy"
:last "Martinson"}
:address {:street "455 Larkspur Dr."
:city "Baviera"
:state "California"
:zip 22611}
:phones {:home "(555) 555-5555"
:mobile "(666) 666-6666"}}
If your data has a more-or-less static schema and you want to make use of Clojure's polymorphic features, you could use records:
(defrecord Name [first last])
(defrecord Address [street city state zip])
(defrecord Phones [home mobile])
(defrecord Person [name address phones])
(map->Person
{:name (map->Name
{:first "Jeremy"
:last "Martinson"})
:address (map->Address
{:street "455 Larkspur Dr."
:city "Baviera"
:state "California"
:zip 22611})
:phones (map->Phones
{:home "(555) 555-5555"
:mobile "(666) 666-6666"})})
In this case, however, you'd probably want to just go with a map. That Phones record, in particular, is quite ugly.
In clojure its called a "record", and is defined using defrecord:
from the docs:
(defrecord Person [fname lname address])
-> user.Person
(defrecord Address [street city state zip])
-> user.Address
(def stu (Person. "Stu" "Halloway"
(Address. "200 N Mangum"
"Durham"
"NC"
27701)))
-> #'user/stu
(:lname stu)
-> "Halloway"
(-> stu :address :city)
-> "Durham"
Related
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.
I am trying to implement a search in Clojurescript with reagent/re-frame and semantic-ui. Semantic-ui uses a renderer for the suggestions. This renderer defaults to image, price, title, description. As I want to have suggestions on geocoding I want to list addresses. This is the return data I am getting. I basically want to display name, city and postcode in the suggestions.
{:hits
[{:osm_type "W",
:name "Am Pfuhl",
:osm_value "residential",
:city "Berlin",
:postcode "12209",
:state "Berlin",
:osm_key "highway",
:extent [13.322584 52.4205878 13.3258975 52.419743],
:point {:lng 13.3241429, :lat 52.4201622},
:osm_id 103012039,
:country "Deutschland"}
:took 7}
The code I wrote does not show me any results. I tried a lot but I don't know how to look into the component to see if the state of it changes and if it stores results. The subscription does give me back results when I call it directly.
(def search (helper/component "Search"))
(def grid (helper/component "Grid"))
(def grid-row (helper/component "Grid" "Row"))
(defn on-search-change [event props]
(rf/dispatch [:get-geocode (:value (js->clj props :keywordize-keys true))]))
(defn on-result-select [event props]
(rf/dispatch [:geocode-selected]))
(defn get-geocode-results []
#(rf/subscribe [:geocode-results]))
(defn result-renderer [& meh]
(fn [meh]
[:div (str meh)]))
(defn geocode-component []
[:> grid
[:> grid-row
[:> search {:minCharacters 3
:loading (when (:geocode #(rf/subscribe [:loading])) )
:defaultValue "Berlin"
:selectFirstResult true
:onSearchChange on-search-change
:onResultSelect on-result-select
:resultRenderer result-renderer
:results get-geocode-results}]]])
I would very much appreciate some help on:
How do I find out if the component stores the results correctly?
How do I write a renderer that just renders all of the results for debugging?
Thanks and regards! Timo
Edit: solution on https://gist.github.com/TimoKramer/7e93758afb81dcad985fafccc613153a
From the docs it looks like :resultRenderer expects a React Component, and you're giving it a Clojure function. You can convert your hiccup-style components to React components with reagent.core/as-component.
I haven't tested this, but maybe it can be as simple as:
(defn result-renderer [& meh]
(reagent.core/as-component [:div (str meh)]))
I've used a similar strategy with tooltips:
(defn info-icon
([message]
(info-icon {} message))
([options message]
(let [popup (component "Popup")
icon (component "Icon")]
[:> popup
{:trigger (reagent/as-component [:> icon (merge {:name "info"} options)])}
" "
message])))
Where component probably matches your helper/component, and reagent is reagent.core
I am trying to save data into a collection of some sort, but the program that I have is saving everything into a separate map. I want to make it one map.
(defn readFile []
(map (fn [line] (clojure.string/split line #";"))
(with-open [rdr (reader "C:/Users/Rohil/Desktop/textfile.txt")]
(doseq [[idx line] (map-indexed vector(line-seq rdr))]
(if(.contains line "201609")
(if(not(.contains line "TBA"))
(println(assoc table :code(nth(clojure.string/split line #";")3) :instructor(nth(clojure.string/split line #";")19)))
)
)
)
)
)
)
)
Any help will be appreciated.
Looks like you are adapting to clojure :-) I went to the same process. Hang on, it will be worth it!
First: it is important to realize that map will save the result of the function into a new collection. Like cfrick mentions, println returns nil and assoc does not change a map.
I'm guessing a bit here what you are trying to do: You want to have a collection of dicts, where every dict has two keys, like so:
[
{ :code 1 :instructor "blah"}
{ :code 2 :instructor "boo" }
]
You need these values to come from a file, but you only want to save the lines where the line contains "201609" but not "TBA"
First some general remarks:
You probably want to split this function into smaller parts. One could be the check for lines (contains 201609 but not tba ), another could read the file...
I know it is the title of your question, but most likely there is a better way than to change a global variable. Maybe you could make the function readFile return the table?
try if you can pass in arguments to your function.
I'm not sure what you are trying to do with the line (doseq [[... Please give us more context there. I will ignore it
Here is a possible solution:
(ns test
(:require [clojure.string :as s]
[clojure.java.io :as io]))
(defn line-filter [include exclude line]
(and (not (s/includes? line exclude))
(s/includes? line include)))
(defn process-line [line]
(let [line-parts (s/split line #";")
code (nth line-parts 3)
instructor (nth line-parts 19)]
{:code code :instructor instructor}))
(defn read-file [file-name]
(s/split (slurp (io/resource file-name)) #"\n"))
(defn parse-lines [lines]
(map process-line lines))
(defn read-file-and-parse
"This function will read a file, process the lines, and output a collection of maps"
[filename search-for exclude]
(parse-lines
(filter #(line-filter search-for exclude %)
(read-file filename))))
you could now call this function like this: (read-file-and-parse "test.txt" "201609" "TBA")
If you want to add the result of this function into your table, you can use concat. But again, this will return a new version of your list (with new entries added) and not change the one you defined earlier.
Welcome to functional programming :-)))
Do you know a function that takes a Clojure string and converts it to a map. For example, if the function gets the string
:first "John" :last "Lukas" :city "London"
it returns a map with the previous key-value relations.
You can use the clojure.end/read-string function for this. It makes sure that nobody injects code to your system, but otherwise will parse clojure data structures. Prepend "{" and append "}" to make it a map, that can be parsed that way:
(def stringtoparse ":first \"John\" :last \"Lukas\" :city \"London\"")
(clojure.edn/read-string (str "{" stringtoparse "}"))
(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.