I'm currently learning Clojure and I'm a total beginner and I would appreciate some help understanding. I went through some code today and found this.
(let [timepoints (merge-with (fn [mf swt] [mf swt]) timepoint-max timepoint-sum )])
where mf, swt, timepoint-max and timepoint-sum looks something like
{"Timepoint1": 3, "Timepoint2": 2}
So what does the code above do?
I understand that we set the variable timepoints to be some sort of union between the two maps(?). But I'm especially confused about the fn [mf swt] [mf swt] part.
The expression (fn [mf swt] [mf swt]) is an anonymous clojure function that constructs a vector from the two arguments passed into it, e.g
((fn [mf swt] [mf swt]) :a :b)
;; => [:a :b]
The expression (merge-with (fn [mf swt] [mf swt]) timepoint-max timepoint-sum ) is a call to the function merge-with with first argument (fn [mf swt] [mf swt]), second argument timepoint-max and third argument timepoint-sum. Here is an example where we bind timepoint-max and timepoint-sum to some example values:
(def timepoint-max {:x 0 :y 1})
(def timepoint-sum {:y 100 :z 200})
(merge-with (fn [mf swt] [mf swt]) timepoint-max timepoint-sum)
;; => {:x 0, :y [1 100], :z 200}
Read the docs of merge-with to understand what it does:
Returns a map that consists of the rest of the maps conj-ed onto the
first. If a key occurs in more than one map, the mapping(s) from the
latter (left-to-right) will be combined with the mapping in the result
by calling (f val-in-result val-in-latter).
In the above example, what we actually compute is the same as
{:x (:x timepoint-max)
:y ((fn [mf swt] [mf swt]) (:y timepoint-max) (:y timepoint-sum))
:z (:z timepoint-sum)}
;; => {:x 0, :y [1 100], :z 200}
where the function (fn [mf swt] [mf swt]) is used to combine the two values at the only overlapping key :y.
The full expression (let [timepoints (merge-with (fn [mf swt] [mf swt]) timepoint-max timepoint-sum )]) is a let form that binds values to symbols but it is not very useful because its *exprs part is empty, so it always evaluates to nil:
(let [timepoints (merge-with (fn [mf swt] [mf swt]) timepoint-max timepoint-sum )])
;; => nil
In order for it to evaluate to something else, e.g. timepoints, it would have to be modified to something like
(let [timepoints (merge-with (fn [mf swt] [mf swt]) timepoint-max timepoint-sum) ]
timepoints)
;; => {:x 0, :y [1 100], :z 200}
Related
I have a vector of vectors [[plate,'p1',0,1],[plate,'p2',0,2],[plate,'p3',1,1]] containing x,y positions of detected plates.
How do I retrieve the x position of plate p3?
It seems to be a simple task but I'm more familiar with python, so I'm not sure how to do this in clojure.
i would go with something like this:
(def data [[:plate "p1" 0 1] [:plate "p2" 0 2] [:plate "p3" 1 1]])
(some (fn [[_ v x y]] (when (= v "p3") [x y])) data)
;;=> [1 1]
(some (fn [[_ v x y]] (when (= v "p123") [x y])) data)
;;=> nil
(def p '[[plate,"p1",0,1],[plate,"p2",0,2],[plate,"p3",1,1]])
;; be aware, 'p1' you can use in Python, but because in Clojure `'` at beginning
;; of a symbol is parsed as `quote`, you can't use `''` instead of `""` in contrast to Python.
;; create a nested map out of the vec of vecs
(defn vecs-to-map [vecs]
(reduce (fn [m [_ id x y]] (assoc m id {:x x :y y}))
{}
vecs))
(def m (vecs-to-map p))
;;=> {"p1" {:x 0, :y 1}, "p2" {:x 0, :y 2}, "p3" {:x 1, :y 1}}
;; you can access such a nested list via `get-in` and giving the nested map
;; and the keys it should apply on it.
(get-in m ["p3" :x])
;;=> 1
Since the irregularity that one key is a string and the other a keyword is
not so nice, I would make out of them all keywords:
(defn vecs-to-map [vecs]
(reduce (fn [m [_ id x y]] (assoc m (keyword id) {:x x :y y}))
{}
vecs))
(def m (vecs-to-map p))
;; access it by:
(get-in m [:p3 :x])
;;=> 1
Additional Thoughts
We ignored the first element of the vec plate.
Let's say there exist also another vectors like
(def b '[[box "b1" 0 1] [box "b2" 0 2] [box "b3" 1 1]])
And if we want a nested map which contains :plate and :box in the
outer level as keys, we have to change the vecs-to-map function.
(defn vecs-to-map [vecs]
(reduce (fn [m [k id x y]] (assoc m (keyword k)
(assoc (get m (keyword k) {})
(keyword id) {:x x :y y})))
{}
vecs))
Then we can generate the map containing everything by:
(def m (vecs-to-map (concat p b)))
;; or alternatively in two steps:
;; (def m (vecs-to-map p))
;; (def m (merge m (vecs-to-map b)))
m
;; => {:plate {:p1 {:x 0, :y 1}, :p2 {:x 0, :y 2}, :p3 {:x 1, :y 1}}, :box {:b1 {:x 0, :y 1}, :b2 {:x 0, :y 2}, :b3 {:x 1, :y 1}}}
And we access the content by:
;; access through:
(get-in m [:plate :p3 :x])
;; => 1
(get-in m [:box :b2 :y])
;; => 2
You don't really provide much context on what you're trying to do but it feels like you want to filter the vector of tuples to those that have the symbol p3' in the second position and then return just the third and fourth elements of such a match?
If so, the following would work:
dev=> (def plate :plate)
#'dev/plate
dev=> (def data [[plate,'p1',0,1],[plate,'p2',0,2],[plate,'p3',1,1]])
#'dev/data
dev=> (let [[[_ _ x y]] (filter (comp #{'p3'} second) data)]
#_=> [x y])
[1 1]
This doesn't feel very idiomatic, so perhaps you could explain more of the context?
Note: 'p3' is a symbol whose name is p3' so I wonder if you mean "p3" for a string?
The vector of vector format doesn't seem very conducive to the sort of access you want to perform - perhaps changing it to a hash map, whose keys are the plate IDs (if that's what p1, p2, and p3 are?) would be better to work with?
Edit: in response to #leetwinkski's note about the result when there is no match, here's an alternative:
You could use when-first:
dev=> (when-first [[_ _ x y] (filter (comp #{'p123'} second) data)]
#_=> [x y])
nil
dev=> (when-first [[_ _ x y] (filter (comp #{'p3'} second) data)]
#_=> [x y])
[1 1]
Here is how I would do it, based on my favorite template project. Please also note that in Clojure strings always use double-quotes like "p1". Single quotes are totally different!
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(defn vec-has-label
"Returns true iff a vector has the indicated label"
[vec lbl]
(= lbl (nth vec 1)))
(defn vec->x
"Given a vector, return the x value"
[vec]
(nth vec 2))
(defn find-label
[matrix label]
(let [tgt-vecs (filterv #(vec-has-label % label) matrix) ; find all matching vectors
x-vals (mapv vec->x tgt-vecs)] ; extract the x values
x-vals))
The unit tests show the code in action
(dotest
(isnt (vec-has-label '[plate "p1" 0 1] "p0"))
(is (vec-has-label '[plate "p1" 0 1] "p1"))
(is= 9 (vec->x '[plate "p1" 9 1]))
(let [matrix (quote
[[plate "p1" 0 1]
[plate "p2" 0 2]
[plate "p3" 1 1]])]
(is= (find-label matrix "p3")
[1])
))
The unit test show the 2 ways of "quoting" a data structure that contains one or more symbols. This would be unneeded if the redundant plate symbol weren't present.
See also this list of documentation sources.
Apparently get-in doesn't work for '() lists since they're not an associative data structure. This makes sense for the API and from the perspective of performance of large lists. From my perspective as a user it'd be great to still use this function to explore some small test data in the repl. For example I want to be able to:
(-> '({:a ("zero" 0)} {:a ("one" 1)} {:a ("two" 2)})
(get-in [1 :a 0]))
=> "one"
Is there some other function that works this way? Is there some other way to achieve this behavior that doesn't involve converting all my lists to (say) vectors?
This does what you ask:
(defn get-nth-in [init ks]
(reduce
(fn [a k]
(if (associative? a)
(get a k)
(nth a k)))
init
ks))
For example,
(-> '({:a "zero"} {:a "one"} {:a "two"})
(get-nth-in [1 :a]))
;"one"
and
(-> '({:a ("zero" 0)} {:a ("one" 1)} {:a ("two" 2)})
(get-nth-in [1 :a 0]))
;"one"
The extra 's you have get expanded into (quote ...):
(-> '({:a '("zero" 0)} {:a '("one" 1)} {:a '("two" 2)})
(get-nth-in [1 :a 0]))
;quote
Not what you intended, I think.
A post just yesterday had a problem regarding lazy lists and lazy maps (from clojure/data.xml). One answer was to just replace the lazy bits with plain vectors & maps using this function:
(defn unlazy
[coll]
(let [unlazy-item (fn [item]
(cond
(sequential? item) (vec item)
(map? item) (into {} item)
:else item))
result (postwalk unlazy-item coll)
]
result ))
Since the resulting data structure uses only vectors & maps, it works for your example with get-in:
(let [l2 '({:a ("zero" 0)} {:a ("one" 1)} {:a ("two" 2)})
e2 (unlazy l2) ]
(is= l2 e2)
(is= "one" (get-in e2 [1 :a 0] l2))
)
You can find the unlazy function in the Tupelo library.
The first param for get-in should be a map.
You have to figure out the feature of your sequence, use last, first, filter or some e.g. to get the element first
for example you could use (:a (last data))
An idiomatic way to set default values in clojure is with merge:
;; `merge` can be used to support the setting of default values
(merge {:foo "foo-default" :bar "bar-default"}
{:foo "custom-value"})
;;=> {:foo "custom-value" :bar "bar-default"}
In reality however, often the default values are not simple constants but function calls. Obviously, I'd like to avoid calling the function if it's not going to be used.
So far I'm doing something like:
(defn ensure-uuid [msg]
(if (:uuid msg)
msg
(assoc msg :uuid (random-uuid))))
and apply my ensure-* functions like (-> msg ensure-uuid ensure-xyz).
What would be a more idiomatic way to do this? I'm thinking something like:
(merge-macro {:foo {:bar (expensive-func)} :xyz (other-fn)} my-map)
(associf my-map
[:foo :bar] (expensive-func)
:xyz (other-fn))
You can use delay combined with force.
You can then merge your defaults like
(merge {:foo "foo-default" :bar "bar-default" :uuid (delay (random-uuid))}
{:foo "custom-value" :uuid "abc"})
and access values using
(force (:foo ...))
or
(force (:uuid ...))
random-uuid will then only be called when you actually need the value (and only the first time).
You can wrap the call to force in a get-value function, or something like that.
I just adapted the condp macros and wrote the following:
(defmacro assoc-if-nil
"Takes a map as the first argument and a succession of key value pairs that
are used to set the key to value if the key of the map is nil. The value part
is only evaluated if the key is nil (thus different semantics to (merge)).
Example:
(assoc-if-nil {:a {:b :set}}
[:a :b] :non-def
[:a :c] :non-def
:d :non-def)
;; =>{:a {:b :set, :c :non-def}, :d :non-def}"
[m & clauses]
(assert (even? (count clauses)))
(let [g (gensym)
get-fn (fn[kork] (if (vector? kork) `get-in `get))
assoc-fn (fn[kork] (if (vector? kork) `assoc-in `assoc))
pstep (fn [[kork v]] `(if-not (~(get-fn kork) ~g ~kork)
(~(assoc-fn kork) ~g ~kork ~v)
~g))]
`(let [~g ~m ;; avoid double evaluation
~#(interleave (repeat g) (map pstep (partition 2 clauses)))]
~g)))
Which expands to:
(macroexpand-1 '
(assoc-if-nil m
[:a :b] :nested
:d :just-key))
(clojure.core/let
[G__15391 m
G__15391
(clojure.core/if-not
(clojure.core/get-in G__15391 [:a :b])
(clojure.core/assoc-in G__15391 [:a :b] :nested)
G__15391)
G__15391
(clojure.core/if-not
(clojure.core/get G__15391 :d)
(clojure.core/assoc G__15391 :d :just-key)
G__15391)]
G__15391)
What is the idiomatic way of conj-ing a list of values to a map value?
This is the result I want, but the anonymous function looks kind of ugly imo. Is there a better way?
> (update-in {:x #{}} [:x] #(apply conj % '(1 2)))
{:x #{1 2}}
The anonymous function is unnecessary
(update-in {:x #{}} [:x] conj 1 2)
;=> {:x #{1 2}}
(update-in {:x #{}} [:x] into [1 2])
;=> {:x #{1 2}}
You should not have to know whether the map contains? the key that you are conjing values to. Adapting your example ...
(update-in {} [:x] #(apply conj % '(1 2)))
;{:x (2 1)}
... not what you want.
The following
(defn assocs [m k coll]
(assoc m k (into (get m k #{}) coll)))
... supplies an empty-set value if no entry for the key exists.
(assocs {} :x [1 2])
;{:x #{1 2}}
(assocs {:x #{2}} :x [1 2])
;{:x #{1 2}}
You'll find similar code in clojure.algo.graph, for example here. (Warning: the graph type only functions in one of the algorithms and otherwise just gets in the way.)
I have two maps, of the form:
(def h1 {:p1 {:x {:port :p1 :instr :x :qty 10}}})
(def h2 {:p1 {:y {:port :p1 :instr :y :qty 11}}})
When I merge them using
(apply (partial merge-with merge) [h1 h2])
I get the correct result:
-> {:p1 {:y {:port :p1, :qty 11, :instr :y}, :x {:port :p1, :qty 10, :instr :x}}
However, If I try reducing over a sequence of maps:
(reduce (fn [r m] apply (partial merge-with merge) r m) {} [h1 h2])
I get only the first map as a result:
-> {:p1 {:y {:port :p1, :qty 11, :instr :y}}}
I would expect the same result. What am I doing wrong?
You forgot to apply the apply. In
(fn [r m] apply (partial merge-with merge) r m)
... the implicit do in the fn form performs a series of evaluations, returning the result of the last one. Ignoring side-effects (and there are none), the function is equivalent to
(fn [r m] m)
as you observed.
The reduce takes the sequence of maps apart, so you don't need the apply:
(reduce (fn [r m] (merge-with merge r m)) {} [h1 h2])
; {:p1
; {:y {:qty 11, :instr :y, :port :p1},
; :x {:qty 10, :instr :x, :port :p1}}}
If you are determined to use the same structure for the function, you have to do it this way:
(reduce (fn [r m] (apply (partial merge-with merge) [r m])) {} [h1 h2])
; {:p1
; {:y {:qty 11, :instr :y, :port :p1},
; :x {:qty 10, :instr :x, :port :p1}}}
apply expects a sequence as its last argument, which it flattens into the trailing arguments to its first (function) argument.