In my continual effort to replicate imperative programming in Clojure, I seem to still nest for functions.
For example, if I wanted to start with:
[[{:a 1 :b 2 :c 3} {:a 1 :b 2 :c 3}] [{:a 2 :b 2 :c 3} {:a 2 :b 2 :c 3}]]
and get:
[[:table [:tr [:td {:class "a-1" "b-2" "c-3"}] [:td {:class "a-2" "b-2" "c-3"}]]
how would I do that without the classic nested for statements I'm familiar with from other languages?
I realize I should paste my attempt here but it's too awful.
UPDATE: see the point below that I used incorrect hiccup in the :class values.
My recommendation would be to see the structure of the data (which you have already) and try to decompose the problem in terms of the data you are given.
You have a structure which resembles rows and cells, so that's one approach. You format rows, which are the result of formatting individual cells, then each cell needs to have CSS classes which are passed as a hash-map.
With that in mind:
(defn cell-classes [cell]
(->> (map (fn [[k v]] (str (name k) "-" v)) cell)
(interpose " ")
(reduce str)))
(defn format-cell [cell]
(let [classes (cell-classes cell)]
[:td {:class classes}]))
(defn format-row [cells]
(->> (map format-cell cells)
(into [:tr])))
(defn format-rows [rows]
(->> (map format-row rows)
(into [:table])))
If we test it with your sample data:
(clojure.pprint/pprint (format-rows [[{:a 1 :b 2 :c 3} {:a 1 :b 2 :c 3}] [{:a 2 :b 2 :c 3} {:a 2 :b 2 :c 3}]]))
it prints
[:table
[:tr [:td {:class "a-1 b-2 c-3"}] [:td {:class "a-1 b-2 c-3"}]]
[:tr [:td {:class "a-2 b-2 c-3"}] [:td {:class "a-2 b-2 c-3"}]]]
PS: there's a minor issue in your desired output, which is that {:class "a-1" "b-2" "c-3"} would not be a desirable output for hiccup, that's why I joined the class names with space on cell-classes.
Related
Clojure's (read-string) is really useful.
eg.
(read-string "{:a 1 :b 2} {:c 3 :d 4} [1 2 3]")
will give me the first object, the {:a 1 :b 2}
But how can I get the rest of string ie. "{:c 3 :d 4} [1 2 3]"
What's the reader equivalent of rest or drop ?
You can wrap the string in a StringReader, then wrap that in a PushbackReader, then read from that reader multiple times.
NB. the example below uses clojure.edn/read, as that is an edn-only reader meant for dealing with pure data; clojure.core/read is primarily meant for reading code and should never be used with untrusted inputs.
(require '[clojure.edn :as edn])
(def s "{:a 1 :b 2} {:c 3 :d 4} [1 2 3]")
;; Normally one would want to use with-open to close the reader,
;; but here we don't really care and we don't want to accidentally
;; close it before consuming the result:
(let [rdr (java.io.PushbackReader. (java.io.StringReader. s))
sentinel (Object.)] ; ← or just use ::eof as sentinel
(take-while #(not= sentinel %)
(repeatedly #(edn/read {:eof sentinel} rdr))))
;= ({:a 1, :b 2} {:c 3, :d 4} [1 2 3])
ClojureScript version of what should be the accepted answer by https://stackoverflow.com/users/232707/michał-marczyk
(require '[cljs.reader :as rdr])
(require '[cljs.tools.reader.reader-types :as reader-types])
(def s "{:a 1 :b 2} {:c 3 :d 4} [1 2 3]")
(let [pbr (reader-types/string-push-back-reader s)
sentinel ::eof]
(take-while #(not= sentinel %)
(repeatedly #(rdr/read {:eof sentinel} pbr))))
Probably not very idiomatic but straightforward
(->> (str "(" "{:a 1 :b 2} {:c 3 :d 4} [1 2 3]" ")")
(read-string))
then access to individual elements (you can also use brackets)
If you have a list within the string, you can preserve it via options given to read-string-
(def str-list "({:a 1 :b 2} {:c 3 :d 4} [1 2 3])")
(read-string {:read-cond :preserve} str-list)
;;=> ({:a 1 :b 2} {:c 3 :d 4} [1 2 3])
The source for the available options can be found in the doc string the of read function, i.e. (source read)from the REPL.
Let's say we a list of maps. Maps all have the same keywords, but we don't know the keywords beforehand.
[{:a 1 :b 2} {:a 3 :b 4}]
And what would be the idiomatic way of merging this list into such a map:
{:a [1 3]
:b [2 4]}
Doesn't seem hard, however as I start to implement the function, it gets super ugly and repetitive. I have a feeling that there are much cleaner ways of achieving this.
Thank you
You can actually get a pretty elegant solution by using several functions from the standard library:
(defn consolidate [& ms]
(apply merge-with conj (zipmap (mapcat keys ms) (repeat [])) ms))
Example:
(consolidate {:a 1 :b 2} {:a 3 :b 4})
;=> {:a [1 3], :b [2 4]}
One cool thing about this solution is that it works even if the maps have different key sets.
i would rather use double reduction to "merge" them with update:
(defn merge-maps-with-vec [maps]
(reduce (partial reduce-kv #(update %1 %2 (fnil conj []) %3))
{} maps))
user> (merge-maps-with-vec [{:a 1 :b 2} {:a 3 :b 4 :c 10}])
{:a [1 3], :b [2 4], :c [10]}
It is not as expressive as #Sam Estep's answer, but on the other hand it doesn't generate any intermediate sequences (like every-key-to-empty-vector map which also needs one extra pass through every entry of every map). Of course, premature optimizations are bad in general, but it won't hurt here i guess. Though the reduce based solution looks a bit more obscure, but being put into a library with proper docs it would not look as obscure to the end user (or to yourself a year after)
While many solutions are possible, here is one that uses some of the convenience functions in the Tupelo library:
(ns clj.core
(:use tupelo.core)
(:require [tupelo.schema :as ts]
[schema.core :as s] ))
(s/defn gather-keys
[list-of-maps :- [ts/KeyMap]]
(newline)
(let [keys-vec (keys (first list-of-maps))]
(s/validate [s/Keyword] keys-vec) ; verify it is a vector of keywords
(apply glue
(for [curr-key keys-vec]
{curr-key (forv [curr-map list-of-maps]
(get curr-map curr-key))} ))))
(deftest t-maps
(spyx
(gather-keys [{:a 1 :b 2}
{:a 3 :b 4} ] )))
(gather-keys [{:a 1, :b 2} {:a 3, :b 4}]) ;=> {:a [1 3], :b [2 4]}
Note that this solution assumes that each input map has an identical set of keys. Normally I'd want to enforce that assumption with a sanity check in the code as well.
Looking at the answer from Sam, I would rewrite it with some temporary variables to help document the sub-steps:
(defn consolidate-keys [list-of-maps]
(let [keys-set (set (mapcat keys list-of-maps))
base-result (zipmap keys-set (repeat [] )) ]
(apply merge-with conj base-result list-of-maps)))
(consolidate-keys [ {:a 1 :b 2}
{:a 3 :z 9} ] )
;=> {:z [9], :b [2], :a [1 3]}
I am struggling on how to construct a macro that lets me pass patterns and results to core.match/match in the form of a vector. I would like to be able to do this:
(let [x {:a 1}
patterns [[{:a 2}] :high
[{:a 1}] :low]]
(my-match x patterns))
> :low
I have tried the below and several other approaches which do not work, unless I pass patterns as a literal.
(defmacro my-match [e ems]
`(m/match [~e] ~#ems))
(let [x {:a 1}
patterns [[{:a 2}] :high
[{:a 1}] :low]]
(my-match x patterns))
=> CompilerException java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Symbol, compiling:(*cider-repl kontrakt*:106:10)
(let [x {:a 1}]
(my-match x [[{:a 2}] :high
[{:a 1}] :low]))
=> :low
Macros are expanded at compile time, so you cannot rely on runtime information (the value of a parameter) during expansion. The root problem is that you can't apply a macro in the same way you can apply a function.
In clojure, how to apply a macro to a list?
So you have to either resort to using eval:
(defmacro functionize [macro]
`(fn [& args#] (eval (cons '~macro args#))))
(defmacro my-match [e ems]
`(apply (functionize m/match) [~e] ~ems))
Or approach the problem in a different way (do runtime pattern matching instead of compile time pattern matching).
The simplest way to solve your problem is with a plain old map:
(ns clj.core
(:use tupelo.core))
(def x {:a 1} )
(def patterns { {:a 2} :high
{:a 1} :low } )
(spyx (get patterns x))
;=> (get patterns x) => :low
Since you have no "wildcard values", you don't need core.match at all. If you would like to match on wild-card values, please see the function wild-match? in the Tupelo library. Samples:
(wild-match? {:a :* :b 2}
{:a 1 :b 2}) ;=> true
(wild-match? [1 :* 3]
[1 2 3]
[1 9 3] )) ;=> true
(wild-match? {:a :* :b 2}
{:a [1 2 3] :b 2}) ;=> true
Input: [{:a "ID1" :b 2} {:a "ID2" :b 4}]
I want to only add up all the keys :b and produce the following:
Result: 6
I thought about doing a filter? to pull all the numbers into vector and add it all up but this seems like doing work twice. I can't use merge-with + here since the :a has a string in it. Do I use a reduce here with a function that will pull the appropriate key?
(reduce (fn [x] (+ (x :b))) 0 list-of-maps)
It would be even nicer if I could retain the map structure with updated value ({:a "ID1" :b 6}) but since I don't really need the other keys, just the total sum is fine.
I want to only add up all the keys :b and produce the following:
Result: 6
I believe workable code is:
(def m1 {:a 1, :b 2})
(def m2 {:a 11, :b 12})
(def m3 {:a 21, :b 22})
(def ms [m1 m2 m3])
(->> ms
(map :b)
(reduce +))
I feel use of ->> here can help readability in your situation.
This says to take action on ms, which is defined to be a vector of maps, threading incremental results through the remaining forms.
The first thing is to transform each entry of maps using the keyword :b as a function on each, extracing the value corresponding to that key, resulting in the sequence:
(2 12 22)
You can then apply reduce exactly as you intuit across that seq to get the result:
user=> (def m1 {:a 1, :b 2})
#'user/m1
user=> (def m2 {:a 11, :b 12})
#'user/m2
user=> (def m3 {:a 21, :b 22})
#'user/m3
user=> (def ms [m1 m2 m3])
#'user/ms
user=> (->> ms
#_=> (map :b)
#_=> (reduce +))
36
I'm a tad confused by what you intend by this part of the question:
It would be even nicer if I could retain the map structure with updated value ({:a "ID1" :b 6})
Do you want to have each value for :b across all maps contain the sum of them all in a result, or something else?
(reduce + (map :b list-of-maps))
This its simple but it works!
user=> (+ (your-map :b) (your-map :b))
or
user=> (def x [{:a "ID1" :b 2} {:a "ID2" :b 4}])
#'user/x
user=> (+ ((first x) :b) ((second x) :b))
6
user=>
or user=> (+ ((nth x 0) :b) ((nth x 1) :b))
6
I would like to create a lazy-seq containing another lazy-seq using clojure.
The data structure that I aready have is a lazy-seq of map and it looks like this:
({:a 1 :b 1})
Now I would like to put that lazy-seq into another one so that the result would be a lazy-seq of a lazy-seq of map:
(({:a 1 :b 1}))
Does anyone know how to do this? Any help would be appreciated
Regards,
Here is an example of creating a list containing a list of maps:
=> (list (list {:a 1 :b 1}))
(({:a 1, :b 1}))
It's not lazy, but you can make both lists lazy with lazy-seq macro:
=> (lazy-seq (list (lazy-seq (list {:a 1 :b 1}))))
or the same code with -> macro:
=> (-> {:a 1 :b 1} list lazy-seq list lazy-seq)
Actually, if you'll replace lists here with vectors you'll get the same result:
=> (lazy-seq [(lazy-seq [{:a 1 :b 1}])])
(({:a 1, :b 1}))
I'm not sure what you're trying to do and why do you want both lists to be lazy. So, provide better explanation if you want further help.
generally, there's nothing special about having a lazy-seq containing many lazy-seq's, so i dont understand exactly what it is you are really after.
you could always do
(map list '({:a 1 :b 1})) ;; gives (({:a 1, :b 1}))
we can even verify that it maintains laziness:
(def a
(concat
(take 5 (repeat {:a 1 :b 2}))
(lazy-seq
(throw (Exception. "too eager")))))
(println (take 5 (map list a))) ;; works fine
(println (take 6 (map list a))) ;; throws an exception