Construct a List of Maps - clojure

First of all I'm a Clojure beginner and sry for my bad English.
Lets say you have a function that is suppossed to return a List of Maps containing various Information (in this example Systeminformation).
I've come up with the following example but it just seems wrong and overcomplicated to me.
My Problem is that 'for [disk (File/listRoots)' already returns a list and I have to merge the 2 Lists in order to get the desired Output, for which must exist a better Solution.
I hope that somebody of you can enlighten me of how to do that in a more 'Clojure way'.
(import
[java.lang Runtime System]
[java.io File])
(defn get-sysinfo []
(let [basic-info (list
{:name "Processor Count:", :value (. (Runtime/getRuntime) availableProcessors)}
{:name "OS Name:", :value (System/getProperty "os.name")}
{:name "OS Arch:", :value (System/getProperty "os.arch")}
{:name "User Name:", :value (System/getProperty "user.name")}
{:name "Java Version:", :value (System/getProperty "java.version")})]
(concat basic-info (for [disk (File/listRoots)]
{:name (str "Disk " (. disk getAbsolutePath)), :value (str "Free Space " (float (/ (. disk getFreeSpace) (* 1024 1024 1024))) " GB")}))))
Thanks in advance
Markus

You can also use the little list* helper. It creates a list from the given elements. The last of which is taken as a tail list. So the "merging" happens implicitly.
(defn get-sysinfo
[]
(list*
{:name "Processor Count:" :value (.availableProcessors (Runtime/getRuntime))}
{:name "OS Name:", :value (System/getProperty "os.name")}
{:name "OS Arch:", :value (System/getProperty "os.arch")}
{:name "User Name:", :value (System/getProperty "user.name")}
{:name "Java Version:", :value (System/getProperty "java.version")}
(for [disk (File/listRoots)]
{:name (str "Disk " (.getAbsolutePath disk))
:value (str "Free Space " (float (/ (.getFreeSpace disk)
(* 1024 1024 1024)))
" GB")})))
However, maybe you really want to return a map?
(defn get-sysinfo-map
[]
(into {"Processor Count" (.availableProcessors (Runtime/getRuntime))
"OS Name" (System/getProperty "os.name")
"OS Arch" (System/getProperty "os.arch")
"User Name" (System/getProperty "user.name")
"Java Version" (System/getProperty "java.version")}
(for [disk (File/listRoots)]
[(str "Disk " (.getAbsolutePath disk))
(float (/ (.getFreeSpace disk) (* 1024 1024 1024)))])))

Since you're using (for ...) to transform each element, map seems a more logical choice. You can't really avoid merging two lists since one is "fixed" and the other is generated later from the file listings.
It's also more idiomatic to use (.methodName object) instead of (. object methodName) - though that's purely a style issue.
I'd do it something like:
(defn get-sysinfo []
(concat (list
{:name "Processor Count:", :value (. (Runtime/getRuntime) availableProcessors)}
{:name "OS Name:", :value (System/getProperty "os.name")}
{:name "OS Arch:", :value (System/getProperty "os.arch")}
{:name "User Name:", :value (System/getProperty "user.name")}
{:name "Java Version:", :value (System/getProperty "java.version")})
(map #(hash-map :name (str "Disk " (.getAbsolutePath %)),
:value (str "Free Space "
(float (/ (.getFreeSpace %) (* 1024 1024 1024)))
" GB"))
(File/listRoots))))

Related

ClojureScript map destructure with defaults filled in?

Does Clojure/Script offer a way to build a destructured map out of the arguments plus filled-in defaults in case the keys weren't supplied in the call?
Consider this example (that doesn't quite do what the code implies by a quick glance). Does clojure provide a way to build the map prompt with these four keys and values either from the caller or the defaults. I hate to think I have to repeat these key names two more times to get what I am after.
(re-frame/reg-event-db
:open-prompt
(fn [db [_ {title :title
text :text
label-yes :label-yes
label-no :label-no
:or {title "Confirm"
text "Are you sure?"
label-yes "Ok"
label-no "Cancel"}
:as prompt}]]
(-> db
(update :state conj :modal-prompt)
(assoc :prompt prompt))))
After reviewing the official documentation page about destructuring, I don't think that Clojure proposes a more convient way of doing that.
But just by curiosity, I was wondering what is the code generated by destructuring, because I'm expecting it relies on macro stuff. Let consider this toy example:
(def my-map {:text "Some text"})
(let
[{title :title
:or {title "Confirm"}
:as prompt} my-map]
(str "I got " title " from " prompt))
;; => "I got Confirm from {:text \"Some text\"}"
(macroexpand '(let
[{title :title
:or {title "Confirm"}
:as prompt} my-map]
(str "I got " title " from " prompt)))
;; => (let*
;; [map__12555
;; my-map
;; map__12555
;; (if
;; (clojure.core/seq? map__12555)
;; (clojure.lang.PersistentHashMap/create
;; (clojure.core/seq map__12555))
;; map__12555)
;; prompt
;; map__12555
;; title
;; (clojure.core/get map__12555 :title "Confirm")]
;; (str "I got " title " from " prompt))
So as you can see, after a macro expansion, the :or mechanism which allows to specifies default value relies on clojure.core/get.
In this particular example, title is affected by (clojure.core/get map__12555 :title "Confirm") form. It's a way to avoid repeating the title variable, but does it worth it?
You can also check the source code of the destructuring macro to get full details about it, but personally I found it pretty difficult to handle ^^'.
it is doable, maybe not very practical though, but nice for self education:
let's begin with making up the function what would be special binding case.
let's say, we want to pass vectors of length 2 or 3, where vector of 2 will represent the simple binding map key-value pair like [:as abc] or [a :a], and the vector of size 3 would be k-v-default triple: [a :a "my default"]. The example of it's usage:
(bindings-preproc [['a 1 "asd"]
['b 2 "ddd"]
[:or {'x 10}]
[:as 'whole]])
resulting to
{a 1, b 2, :or {x 10, a "asd", b "ddd"}, :as whole}
this function could look like this:
(defn bindings-preproc [decls]
(let [defaults (into {} (keep (fn [decl]
(when (and (not (keyword? (first decl)))
(= 3 (count decl)))
(let [[nm _ default] decl]
[nm default])))
decls))
all-kvs (apply assoc {} (mapcat (partial take 2) decls))]
(update all-kvs :or merge defaults)))
(this one doesn't include error checks for the sake of illustrative simplicity)
The next thing is to employ it inside the binding macros. The idea to make bindings-preproc a macro fails, because binding forms are checked for validity before the inner macros are evaluated.
But still we have a feature, that would help, namely reader tags. They are used for example when you use #inst syntax. Since these reader tags are processed at read-time, before any macros are getting expanded, we can plug our preprocessor in.
(here i will use actual reference update, to demonstrate it from repl, but in real projects you would declare these tags in a special file)
user> (alter-var-root
#'default-data-readers
assoc 'my/reader #'user/bindings-preproc)
;;=> {uuid #'clojure.uuid/default-uuid-reader,
;; inst #'clojure.instant/read-instant-date,
;; my/reader #'user/bindings-preproc}
so, now we can try to make it work:
(defn f [#my/reader [[a :a 10]
[b :b 20]
[z :z]
[:keys [k1 k2 k3]]
[[c1 c2 & cs] :c]
[:or {z 101
k3 :wooo}]
[:as whole]]]
{:a a :b b :c1 c1 :c2 c2 :cs cs :z z :k1 k1 :k2 k2 :k3 k3 :whole whole})
user> (f {:a 1000 :c [:one]})
;;=> {:cs nil,
;; :c2 nil,
;; :z 101,
;; :c1 :one,
;; :k3 :wooo,
;; :b 20,
;; :whole {:a 1000, :c [:one]},
;; :k1 nil,
;; :k2 nil,
;; :a 1000}
user> (let [a 10
b 20
#my/reader [[x :x 1]
[y :y 2]
[z :z 100]] {:z 432}]
[a b x y z])
;;=> [10 20 1 2 432]
I like to make a map of all default values, then use into or similar to fuse the user-supplied values into the map of default values. For example:
(ns tst.demo.core
(:use tupelo.core tupelo.test) )
(def stuff-default {:a 1 :b 2})
(defn apply-defaults
[arg]
(let [stuff (glue stuff-default arg)] ; or use `into`. Last one wins, so put defaults first
(with-map-vals stuff [a b]
(newline)
(spyx a)
(spyx b))
stuff))
(dotest
(is= (apply-defaults {}) ; no inputs => all default values
{:a 1, :b 2})
(is= (apply-defaults {:a 100}) ; some inputs => partial defaults
{:a 100, :b 2})
(is= (apply-defaults {:a 100, :b 200}) ; all inputs => no defaults used
{:a 100, :b 200}))
Here glue is like into but with more error checking. We also use tupelo.core/with-map-vals to destruct the map, with less repetition than native Clojure destructuring (vals->map does the reverse).
The output is:
-------------------------------
Clojure 1.10.1 Java 14
-------------------------------
a => 1
b => 2
a => 100
b => 2
a => 100
b => 200
Ran 2 tests containing 3 assertions.
0 failures, 0 errors.

Clojure - Retrieving runtime values

I'm currently working on a pdf generating library built around pdfbox, a java library.
I don't have a problem per se, I'm just uncertain of what would be the clever way in clojure to do something.
I try to stick to a Hiccup style syntax for generating pdf.
With something like that (a very impractical example):
[:page {:title "hey"}
[:frame {:name "frame1" :top 130}]]
I would like to retrieve later in the document the values passed to page and frame (which are functions after parsing). For example, the next frame:
[:frame {:bottom (+ 10 (:top "frame1"))} (str "Titre:" (:title page))]
Every function passes its options map to the other so the first frame's options actually look like this:
{:title "hey", :name "frame1", :top 130}
But obviously the user can't access that map when the executing this kind of code.
For the page I think using a global Var that is updated with binding seems to be an okay solution (open to any suggestions). But as there might be any number of frames they can't be declared earlier. Therefore, my question is:
What kind of function, concept or way of doing things would be best to deal with that kind of problem? How could I give the user the ability to retrieve these data? (avoiding a global var for all options and a get-in if possible)
i've got an idea about that: why don't you use dynamically scoped value for context, that would contain all the data for your struct's call stack. And then you can analyze your struct, evaluating in this context.
I would go with something like this:
(def ^:dynamic *context* ())
(defn lookup-context [& kv-pairs]
(some #(when (every? (fn [[k v]] (= (k %) v)) kv-pairs) %)
*context*))
(defmacro with-context [data]
(let [items (tree-seq #(and (vector? %) (#{:frame :page} (first %)))
#(nthnext % 2)
data)
ctx-items (reverse (map second items))
let-bindings (zipmap ctx-items (repeatedly gensym))
data (clojure.walk/postwalk-replace let-bindings data)]
(reduce (fn [acc [itm sym]]
`(let [~sym ~itm]
(binding [*context* (cons ~sym *context*)] ~acc)))
data ;; here goes your data parsing
let-bindings)))
so this macro establishes cascading dynamic bindings, and all the calls to lookup-context inside it (even in the nested functions called from ";;here goes your data parsing" part)
for example with this structure:
(with-context [:page
{:name "page0" :val 1000}
[:frame
{:name "frame0" :val 10}
[:frame {:name "frame1" :val (+ (:val (lookup-context [:name "page0"]))
(:val (lookup-context [:name "frame0"])))}]]])
it is going to be expanded to this:
(let [G__8644 {:name "page0", :val 1000}]
(binding [*context* (cons G__8644 *context*)]
(let [G__8643 {:name "frame0", :val 10}]
(binding [*context* (cons G__8643 *context*)]
(let [G__8642 {:name "frame1",
:val
(+
(:val (lookup-context [:name "page0"]))
(:val (lookup-context [:name "frame0"])))}]
(binding [*context* (cons G__8642 *context*)]
[:page G__8644 [:frame G__8643 [:frame G__8642]]]))))))
giving you the result you need, i guess
UPDATE
as an answer to #amalloy's question about the reason for dynamically scoped var usage:
user> (defn item-factory []
[:frame {:name "frame2" :val (+ (:val (lookup-context [:name "frame1"]))
(:val (lookup-context [:name "page0"])))}])
#'user/item-factory
user>
(with-context [:page
{:name "page0" :val 1000}
[:frame
{:name "frame0" :val 10}
[:frame {:name "frame1" :val (+ (:val (lookup-context [:name "page0"]))
(:val (lookup-context [:name "frame0"])))}]
(item-factory)]])
;;=> [:page {:name "page0", :val 1000}
;; [:frame {:name "frame0", :val 10}
;; [:frame {:name "frame1", :val 1010}]
;; [:frame {:name "frame2", :val 2010}]]]
as you can see, the item-factory function, being called inside the data processing, is also context aware, meaning that the lib user can simply decompose the data generation, keeping the implicit dependency on the items defined upper on the definitions stack.

How best to update this tree?

I've got the following tree:
{:start_date "2014-12-07"
:data {
:people [
{:id 1
:projects [{:id 1} {:id 2}]}
{:id 2
:projects [{:id 1} {:id 3}]}
]
}
}
I want to update the people and projects subtrees by adding a :name key-value pair.
Assuming I have these maps to perform the lookup:
(def people {1 "Susan" 2 "John")
(def projects {1 "Foo" 2 "Bar" 3 "Qux")
How could I update the original tree so that I end up with the following?
{:start_date "2014-12-07"
:data {
:people [
{:id 1
:name "Susan"
:projects [{:id 1 :name "Foo"} {:id 2 :name "Bar"}]}
{:id 2
:name "John"
:projects [{:id 1 :name "Foo"} {:id 3 :name "Qux"}]}
]
}
}
I've tried multiple combinations of assoc-in, update-in, get-in and map calls, but haven't been able to figure this out.
I have used letfn to break down the update into easier to understand units.
user> (def tree {:start_date "2014-12-07"
:data {:people [{:id 1
:projects [{:id 1} {:id 2}]}
{:id 2
:projects [{:id 1} {:id 3}]}]}})
#'user/tree
user> (def people {1 "Susan" 2 "John"})
#'user/people
user> (def projects {1 "Foo" 2 "Bar" 3 "Qux"})
#'user/projects
user>
(defn integrate-tree
[tree people projects]
;; letfn is like let, but it creates fn, and allows forward references
(letfn [(update-person [person]
;; -> is the "thread first" macro, the result of each expression
;; becomes the first arg to the next
(-> person
(assoc :name (people (:id person)))
(update-in [:projects] update-projects)))
(update-projects [all-projects]
(mapv
#(assoc % :name (projects (:id %)))
all-projects))]
(update-in tree [:data :people] #(mapv update-person %))))
#'user/integrate-tree
user> (pprint (integrate-tree tree people projects))
{:start_date "2014-12-07",
:data
{:people
[{:projects [{:name "Foo", :id 1} {:name "Bar", :id 2}],
:name "Susan",
:id 1}
{:projects [{:name "Foo", :id 1} {:name "Qux", :id 3}],
:name "John",
:id 2}]}}
nil
Not sure if entirely the best approach:
(defn update-names
[tree people projects]
(reduce
(fn [t [id name]]
(let [person-idx (ffirst (filter #(= (:id (second %)) id)
(map-indexed vector (:people (:data t)))))
temp (assoc-in t [:data :people person-idx :name] name)]
(reduce
(fn [t [id name]]
(let [project-idx (ffirst (filter #(= (:id (second %)) id)
(map-indexed vector (get-in t [:data :people person-idx :projects]))))]
(if project-idx
(assoc-in t [:data :people person-idx :projects project-idx :name] name)
t)))
temp
projects)))
tree
people))
Just call it with your parameters:
(clojure.pprint/pprint (update-names tree people projects))
{:start_date "2014-12-07",
:data
{:people
[{:projects [{:name "Foo", :id 1} {:name "Bar", :id 2}],
:name "Susan",
:id 1}
{:projects [{:name "Foo", :id 1} {:name "Qux", :id 3}],
:name "John",
:id 2}]}}
With nested reduces
Reduce over the people to update corresponding names
For each people, reduce over projects to update corresponding names
The noisesmith solution looks better since doesn't need to find person index or project index for each step.
Naturally you tried to assoc-in or update-in but the problem lies in your tree structure, since the key path to update John name is [:data :people 1 :name], so your assoc-in code would look like:
(assoc-in tree [:data :people 1 :name] "John")
But you need to find John's index in the people vector before you can update it, same things happens with projects inside.

How can i read string from text file and convert into a map in clojure?

I am trying to use Clojure - Seesaw to read from a file and convert the string into a map (variables) so that I can use them to print to a GUI. Below is my current code:
(ns store.core
(:gen-class)
(:require [seesaw.core :as seesaw]))
(defn -main
[& args]
(seesaw/show!
(spit "amovies.txt" "")
;(spit "amovies.txt" (pr-str [{:id 1 :qty 4 :name "movie1" :price 3.50}
; {:id 2 :qty 5 :name "movie2" :price 3.00}]) :append true)
(spit "amovies.txt" "movie: Movie_Name\nprice: 5\nid: 1\nquantity: 2" :append true)
(print (read-string (slurp "amovies.txt")))
(with-open [rdr (reade "amovies.txt")]
(doseq [line (line-seq rdr)]
(println-str line)))
I am stuck figuring out how to read the string from amovies.txt line by line and then create a map with it. The desired output should be something like
movie: Movie_Name
price: 5
id: 1
quantity: 2
but in a way so that if I were to say, :movie, it would reference the name of the movie.
Can someone help? All help is appreciated!
1
(def movies-as-map
(let [lines (with-open [rdr (reade "amovies.txt")]
(line-seq rdr))]
(binding [*read-eval* false]
(map read-string lines))))
2
user> (def movie
(binding [*read-eval* false]
(read-string
"{:id 1 :qty 4 :name \"movie1\" :price 3.50}")))
#'user/movie
user> (keys movie)
(:id :qty :name :price)
Will work with this
(spit "amovies.txt"
(pr-str [{:id 1 :qty 4 :name "movie1" :price 3.50}
{:id 2 :qty 5 :name "movie2" :price 3.00}])
:append true)
But not with this
(spit "amovies.txt"
"movie: Movie_Name\nprice: 5\nid: 1\nquantity: 2"
:append true)
docs: https://clojure.github.io/clojure/branch-master/clojure.core-api.html#clojure.core/read-string
Wrong args being passed to show!
It looks like your passing a ton of arguments to seesaw/show!.
The documentation states
Usage: (show! targets)
Show a frame, dialog or widget.
If target is a modal dialog, the call will block and show! will
return the dialog's result. See (seesaw.core/return-from-dialog).
Returns its input.

Number format exception in compojure

As total clojure noob, I am trying to start one small tutorial app, in order to get familiar with compojure. It's a small application which lets user add two numbers, and after clicking on button displays their sum on the other page. I followed instruction from Mark McGranaghan blog. Everything seems ok, until I try to get sum of two numbers I have entered, instead of getting result, I am redirected to the same page (so basically I am stuck on first step of this tutorial). After checking the code, it seems that NumberFormatException is triggered when input parsing takes place (for some reason). In all my tests, I have tried to input all kinds of number format , but with no success. Here is the simplest code version , for which author said should work (I have tried the latest version from github site- same scenario: NFE):
(ns adder.core
(:use compojure.core)
(:use hiccup.core)
(:use hiccup.page-helpers))
(defn view-layout [& content]
(html
(doctype :xhtml-strict)
(xhtml-tag "en"
[:head
[:meta {:http-equiv "Content-type"
:content "text/html; charset=utf-8"}]
[:title "adder"]]
[:body content])))
(defn view-input []
(view-layout
[:h2 "add two numbers"]
[:form {:method "post" :action "/"}
[:input.math {:type "text" :name "a"}] [:span.math " + "]
[:input.math {:type "text" :name "b"}] [:br]
[:input.action {:type "submit" :value "add"}]]))
(defn view-output [a b sum]
(view-layout
[:h2 "two numbers added"]
[:p.math a " + " b " = " sum]
[:a.action {:href "/"} "add more numbers"]))
(defn parse-input [a b] ;; this is the place where problem occures
[(Integer/parseInt a) (Integer/parseInt b)])
(defroutes app
(GET "/" []
(view-input))
(POST "/" [a b]
(let [[a b] (parse-input a b)
sum (+ a b)]
(view-output a b sum)))
Can anyone tell me better way to pars the input values, in order to avoid this exception?I have tried couple of techniques , but nothing worked for me. I am using Leningen v1.7.1 with clojure 1.3 on win 7 machine.
Here is content of my project.clj file:
(defproject adder "0.0.1"
:description "Add two numbers."
:dependencies
[[org.clojure/clojure "1.3.0"]
[org.clojure/clojure-contrib "1.1.0"]
[ring/ring-core "1.0.2"]
[ring/ring-devel "1.0.2"]
[ring/ring-jetty-adapter "1.0.2"]
[compojure "1.0.1"]
[hiccup "0.3.8"]]
:dev-dependencies
[[lein-run "1.0.0"]])
and run.clj script:
(use 'ring.adapter.jetty)
(require 'adder.core)
(let [port (Integer/parseInt (get (System/getenv) "PORT" "8080"))]
(run-jetty #'adder.core/app {:port port}))
Thanks.
You are using compojure 1.0.1, the example in the blog you are following is using compojure 0.4.0.
As of version 0.6.0, Compojure no longer adds default middleware to routes. This means you must explicitly add the wrap-params and wrap-cookies middleware to your routes.
Source: https://github.com/weavejester/compojure
So you need to explicitly add the wrap-params middleware. So the following changes are required...
(ns adder.core
(:use ; change to idiomatic usage of :use
[compojure.core]
[hiccup.core]
[hiccup.page-helpers]
[ring.middleware.params :only [wrap-params]])) ; add middleware for params
(defn view-layout [& content]
(html
(doctype :xhtml-strict)
(xhtml-tag "en"
[:head
[:meta {:http-equiv "Content-type"
:content "text/html; charset=utf-8"}]
[:title "adder"]]
[:body content])))
(defn view-input []
(view-layout
[:h2 "add two numbers"]
[:form {:method "post" :action "/"}
[:input.math {:type "text" :name "a" :id "a"}] [:span.math " + "]
[:input.math {:type "text" :name "b" :id "a"}] [:br]
[:input.action {:type "submit" :value "add"}]]))
(defn view-output [a b sum]
(view-layout
[:h2 "two numbers added"]
[:p.math a " + " b " = " sum]
[:a.action {:href "/"} "add more numbers"]))
(defn parse-input [a b]
[(Integer/parseInt a) (Integer/parseInt b)])
(defroutes main-routes ; needs to be renamed
(GET "/" []
(view-input))
(POST "/" [a b]
(let [[a b] (parse-input a b)
sum (+ a b)]
(view-output a b sum))))
(def app (wrap-params main-routes)) ; wrap the params to allow destructuring to work