I've played around a bit with clojure before doing much more studying of lisp and I want to get back into clojure...
So I have this thing in common lisp:
(mapcar #'(lambda (x)
(incf (third (car (member x count-mx
:test #'equal
:key #'(lambda (x) (list (car x) (cadr x))))))))
transitions)
That for me in clojure translated to (not sure if this is good clojure):
(map (fn [x] (swap! (third (some #(if (= x (vec (first %) (second %))) %)
count-mx))
inc))
transitions)
where count-mx, if I used atoms to make the zeroes:
[[a a 0] [a b 0] [a c 0] [a d 0] [b a 0] [b b 0] [b c 0] [b d 0] [c a 0] [c b 0] [c c 0] [c d 0] [d a 0] [d b 0] [d c 0] [d d 0]]
and transitions:
[[a a] [a b] [b c] [c d] [d a] [a b] [b c]]
The objective being to increment [a a 0] to [a a 1] after mapping over transitions and seeing [a a]
Although the clojure line does work I cannot use it within a function or let because of immutability, how do I break out of my common lisp thinking? I quite like mapping and modifying values within something, but I'm not sure how to do this efficiently and/or correctly in clojure.
I didn't know: Maps accept also vectors as keys.
In this case, we can directly take the pairs as keys and use update
in combination with inc
to update the values (by enhancing the value by 1). I use sorted-map
because it looks nicer for presentation (sorted) - more performant would be to use (hash-map)
or {}
in the into
expression.
(def m (into (sorted-map) ;; or: `{}` btw `(hash-map)`
(for [x '[a b c d] ;; nesting for-loop
y '[a b c d]] ;; for sunccinct creation
{[x y] 0}))) ;; of all combinations
m
;; => {[a a] 0, [a b] 0, [a c] 0, [a d] 0,
;; [b a] 0, [b b] 0, [b c] 0, [b d] 0,
;; [c a] 0, [c b] 0, [c c] 0, [c d] 0,
;; [d a] 0, [d b] 0, [d c] 0, [d d] 0}
(def transitions '[[a a] [a b] [b c] [c d]
[d a] [a b] [b c]])
(def m' (reduce (fn [a-map a-transition]
(update a-map a-transition inc))
m
transitions))
m'
;; => {[a a] 1, [a b] 2, [a c] 0, [a d] 0,
;; [b a] 0, [b b] 0, [b c] 2, [b d] 0,
;; [c a] 0, [c b] 0, [c c] 0, [c d] 1,
;; [d a] 1, [d b] 0, [d c] 0, [d d] 0}
(reduce (fn [mp key] (update mp key inc)) m '[a b c])
expands to:
(update (update (update m 'a inc) 'b inc) 'c inc)
therefore (in an accumulating manner) sequentially updating the values of each key given in transitions
.
We convert the sorted map into the nested vector form by:
(def m'' (into [] (map (comp vec flatten) (seq m'))))
m''
;; => [[a a 1] [a b 2] [a c 0] [a d 0]
;; [b a 0] [b b 0] [b c 2] [b d 0]
;; [c a 0] [c b 0] [c c 0] [c d 1]
;; [d a 1] [d b 0] [d c 0] [d d 0]]
We can generalize them to functions:
(defn update-map-by-transitions
"Update the map `m` successively by transitions"
[m transitions & {:keys [func] :or {func inc}}]
(reduce (fn [m' tr] (update m' tr func))
m
transitions))
(defn map-to-vecs
"Transform transition map to flatten vectors"
[m & {:keys [vec-func] :or {vec-func flatten}}]
(into [] (map (comp vec vec-func) (seq m))))
(def m' (update-map-by-transitions m transitions))
(def m'' (map-to-vecs m' :vec-func flatten))
m''
;; [[a a 1] [a b 2] [a c 0] [a d 0]
;; [b a 0] [b b 0] [b c 2] [b d 0]
;; [c a 0] [c b 0] [c c 0] [c d 1]
;; [d a 1] [d b 0] [d c 0] [d d 0]]
;; or do:
(def m''' (map-to-vecs m' :vec-func identity))
m'''
;; [[[a a] 1] [[a b] 2] [[a c] 0] [[a d] 0]
;; [[b a] 0] [[b b] 0] [[b c] 2] [[b d] 0]
;; [[c a] 0] [[c b] 0] [[c c] 0] [[c d] 1]
;; [[d a] 1] [[d b] 0] [[d c] 0] [[d d] 0]]
I am also coming from Common Lisp. By the way, you abbreviate it CL or "Lisp". But CLISP actually means one of the many implementations of CL (next to sbcl, abcl, ECL, etc.). So don't call CL clisp ... (to avoid others to be annoyed about it).
I think using nested vectors for this task is tedious and - when scaling up later - less performant.
Instead, prefer maps. They have the function update
and in case of nested maps update-in
to manipulate and "mutate" the values of a map which will prove very handy for this task.
To take a vec [a b c d]
and generate {a 0 b 0 c 0 d 0}
, I would define:
(defn mapper [vec default-val]
(into {} (for [x vec] {x default-val})))
(mapper '[a b] 0) ;; => {a 0 b 0}
Now, we want two keys for storing a count value for them: Nest the maps:
(def m (mapper '[a b c d] (mapper '[a b c d] 0)))
m
;; =>{a {a 0, b 0, c 0, d 0},
;; b {a 0, b 0, c 0, d 0},
;; c {a 0, b 0, c 0, d 0},
;; d {a 0, b 0, c 0, d 0}}
We can anytime convert them back to your favorized nested vector form (I admit, it is better human readable):
(defn nested-maps-to-vecs [nested-map]
(vector (for [[k v] nested-map
[ik iv] v]
[k ik iv])))
(nested-maps-to-vecs m)
;;=> [[a a 0] [a b 0] [a c 0] [a d 0]
;; [b a 0] [b b 0] [b c 0] [b d 0]
;; [c a 0] [c b 0] [c c 0] [c d 0]
;; [d a 0] [d b 0] [d c 0] [d d 0]]
Now define transitions
:
(def transitions '[[a a] [a b] [b c] [c d] [d a] [a b] [b c]])
Using inc
function for update and the handy update-in
we can
"mutate" the values of the map:
(def m' (reduce (fn [a-map a-transition] (update-in a-map a-transition inc)) m transitions))
m'
;; => {a {a 1, b 2, c 0, d 0},
;; b {a 0, b 0, c 2, d 0},
;; c {a 0, b 0, c 0, d 1},
;; d {a 1, b 0, c 0, d 0}}
(nested-maps-to-vecs m')
;; => [[a a 1] [a b 2] [a c 0] [a d 0]
;; [b a 0] [b b 0] [b c 2] [b d 0]
;; [c a 0] [c b 0] [c c 0] [c d 1]
;; [d a 1] [d b 0] [d c 0] [d d 0]]
Voila!
(update-in a-map a-transition inc)
gets e.g. [a b]
and accesses in the nested map with the key sequence a
and then b
and takes the value which is in that "place" and applies inc
to it and "stores" the result into that "place".
Well, it doesn't store it actually, but it returns a new map with the updated value.
This new map is the new a-map
in the reduce
function and taking the next a-transition
this updated map will be further updated.
So reduce
is the trick to "accumulate" the updates by constant update and capture of the updated map while walking through the transitions
sequence.
Clojure has a very efficient way to "update" its immutable structures. It saves only the changes while referring to the unchanged rest. Therefore this "generation of a new updated map" by update
sounds worse than it actually is: It is actually highly efficient performance- as well as memory footprint-wise. This special storing strategy cannot be pursued by Common Lisp and other Lisps or languages, because of mutability. Only pure functional languages with ensured immutability of data like Clojure or Haskell can use this kind of strategy for storing and updating their immutable data.
Clojure has however also mutability in atom
s which has to be specifically declared to be an atom
. In this case, to use atom
s as values in the map or the nested vectors would be not very the Clojure way.
I am also still trying to wrap my head around this all.
Have fun in your journey!