Replace item in vector by id - clojure

(defonce channels (atom []))
(defn register-channel! [id tag channel]
(swap! channels conj [id tag channel]))
(register-channel! "456" :player {})
#channels ;; => [["456" :player {}]]
The id and tag are the indexes for the data, the id is unique, the tag is not.
I can conj new items on to channels. But calling register-channel! with an existing id will not conj a new item instead the third element, channel, of the existing item should be changed.
Here is what I have, which works:
(defn register-channel! [id tag channel]
(let [without-id (remove #(= id (first %)) #channels)
with-id (conj without-id [id tag channel])]
(reset! channels with-id)))
I'm pretty sure there is a more elegant way :)
Note that I am using a collection of vector because I'll only ever have 3 elements, I could use a map if there is no significant performance penalty. However I will need to write functions to find a channel by id or channels by tag afterwards, e.g.
(defn one-by-id [id] (first (filter #(= id (first %)) #channels)))
(defn many-by-tag [tag] (filter #(= tag (second %)) #channels))

If you really need channels to be a vector of pairs, you can add or replace new channels like this:
> (vec (assoc (into {} [[:a 1]]) :a 2))
[[:a 2]]
> (vec (assoc (into {} [[:a 1]]) :b 2))
[[:a 1] [:b 2]]
But that begs the question of whether you'd rather have channels itself be a map.
Define the atom as an empty map and job done!
(defonce channels (atom {}))
(defn register-channel! [id channel]
(swap! channels conj [id channel]))
> (register-channel! "456" {:a 1})
{"456" {:a 1}}
> (register-channel! "457" {:a 1})
{"456" {:a 1}, "457" {:a 1}}
> (register-channel! "457" {:a 2})
{"456" {:a 1}, "457" {:a 2}}

Related

accessing Clojure's thread-first macro arguments

I was wondering if there was a way to access the arguments value of a thread-first macro in Clojure while it is being executed on.
for example:
(def x {:a 1 :b 2})
(-> x
(assoc :a 20) ;; I want the value of x after this step
(assoc :b (:a x))) ;; {:a 20, :b 1}
It has come to my attention that this works:
(-> x
(assoc :a 20)
((fn [x] (assoc x :b (:a x))))) ;; {:a 20, :b 20}
But are there any other ways to do that?
You can use as->:
(let [x {:a 1 :b 2}]
(as-> x it
(assoc it :a 20)
(assoc it :b (:a it))))
In addition to akond's comment, note that using as-> can get quite confusing quickly. I recommend either extracting a top level function for these cases, or trying to use as-> in -> only:
(-> something
(process-something)
(as-> $ (do-something $ (very-complicated $)))
(finish-processing))

Prismatic schema coercion - renaming map key

I'm trying to coerce a map using prismatic-schema (1.0.4)
I'm trying to coerce
{:a 1}
to
{:b 1}
Using a custom matcher with the schema:
{:b s/Int}
But this code isn't working:
(require '[schema.core :as s])
(require '[schema.coerce :as coerce])
((coerce/coercer {:b s/Int}
(fn [s]
(when (= s s/Keyword)
(fn [x]
(if (= x :a)
:b
x)))))
{:a 1})
Output:
#schema.utils.ErrorContainer{:error {:b missing-required-key, :a disallowed-key}}
I tried debugging it by running the following code which matches everything in the schema and outputs the current value and schema being matched:
((coerce/coercer {:b s/Int}
(fn [s]
(when true
(fn [x]
(println s x)
x))))
{:a 1})
Output:
{:b Int} {:a 1}
=>
#schema.utils.ErrorContainer{:error {:b missing-required-key, :a disallowed-key}}
It looks as though the matcher bombs out as soon as it gets to the map?
Schema first breaks your map up into pieces that match up to the schema, then coerces each MapEntry to the corresponding MapEntry schema, and so on down. This breakdown fails in your case, so you never get to the key.
To accomplish what you want, you'll have to attach the coercion to the map schema and use e.g. clojure.set/rename-keys in your coercion function:
(def Foo {:b s/Int})
((coerce/coercer
Foo
(fn [s]
(when (= s Foo)
#(clojure.set/rename-keys % {:a :b}))))
{:a 1})

Update multiple elements of a Clojure atom within a single swap statement?

I have an atom that has two parts to it.
(def thing (atom {:queue '() :map {}}))
I want to update both :queue and :map in one atomic stroke, to prevent them from getting off-sync.
Queue individually:
(swap! thing update-in [:queue] (list 1))
(From this question: How to append to a nested list in a Clojure atom?)
Map individually:
(swap! thing assoc-in [:map 1] (:key :value))
(From this question: Using swap to MERGE (append to) a nested map in a Clojure atom?)
How can I do these both within a single swap statement? (assuming that would prevent them from getting off-sync?)
You have one change you want to make, right? And you could write that change as a pure function? All you need to do is write that function, and pass it as the argument to swap!.
(defn take-from-queue [{q :queue, m :map}]
{:queue (rest q), :map (assoc m :new-task (first q))})
(swap! thing take-from-queue)
Where of course I have no idea what you actually want the body of your function to do, so I've made up something that doesn't throw an exception.
Say you have a hash-map atom:
(def m1 (atom {:a "A" :b "B"}))
To change :a and :b at the same time, changing their values to values that are different, say the numbers 1 and 2, use this function:
(defn my-swap! [params]
(swap! m1 (fn [old new] new) params))
, like so:
(my-swap! {:a 1 :b 2}) ;=> {:a 1, :b 2}
And the same effect could be achieved with the following function and execution:
(defn my-multi-swap! [params1 params2]
(swap! m1 (fn [old new1 new2] new2) params1 params2))
(my-multi-swap! {} {:a 1 :b 2}) ;=> {:a 1, :b 2}
Normally reset! is used if you want to ignore the old value. Here we use it:
(defn my-merge-swap! [params]
(swap! m1 (fn [old new] (merge old new)) params))
(my-merge-swap! {:b 3}) ;=> {:a "A", :b 3}
The first parameter to the swap! function is the existing value of the atom, and you must pass in one or more extra parameters, which you can use to give the atom its new value.

how to traverse / walk an arbitrary embedded structure

I want a mechanism to traverse an arbitrarily nested data structure. Then apply a fn on every node, and then check if the fn returned true at each point.
Its easy to do this with a flat structure -
(walk (complement string?) #(every? true? %) [ 1 2 3 4])
However walk doesnt work with a nested one -
(walk (complement string?) #(every? true? %) [ 1 2 3 [ "a" ]])
Using only flatten also wont work, as I will have a map as one of the forms, and I want fn applied to each value in the map too. This is the structure I will have -
[ ["2012" [{:a 2} {:b 3}]] ["2013" [{:a 2} {:b 3}]] ]
I can easily write a fn to only traverse the above and apply the fn to each val. However is there a way to write a generic mechanism for traversing?
tree-seq might be what you want
(every? (complement string?)
(remove coll?
(tree-seq coll? #(if (map? %)
(vals %)
%)
[["2012" [{:a 2} {:b 3}]] ["2013" [{:a 2} {:b 3}]]])))
;; false
(every? (complement string?)
(remove coll?
(tree-seq coll? #(if (map? %)
(vals %)
%)
[[2012 [{:a 2} {:b 3}]] [2013 [{:a 2} {:b 3}]]])))
;; true

Pulling values from complex lists in Clojure

I'm trying to pull the values out of a complicated list structure.
Given something like this:
[{:a "whatever" :b [:c "foo"]} :e {:f "boo"} :g {:h [:i 62281]}]
I'd like to get:
["whatever" "foo" "boo" 62281]
So far I've only gotten to this point:
((62281) nil (boo) nil whatever foo)
Here's the code:
(defn get-values [params]
(apply conj
(map (fn [part]
(if (not (keyword? part))
(map (fn [v]
(if (vector? v)
(last v)
v))
(vals part))))
params)))
I can't seem to get rid of the nil's
I can't figure out why the values after a certain point are in lists.
I figure there's got to be a better way to do this.
Fix the data structure and everything will fall in place. As of now your data structure isn't consistent at all and that will make any function that touch this data way more complicated and error prone. You can model this data as a map:
(def data {:a "whatever"
:b nil
:c "foo"
:e nil
:f "boo"
:g nil
:h nil
:i 62281})
And then to get the desired result:
(->> (vals data)
(filter (comp not nil?))
(into []))
But for some strange reason you still want to parse the data structure you provided then:
(defn get-values [data]
(->> (map #(if (map? %) (into [] %) %) data)
flatten
(filter #(or (string? %) (number? %)))
(into [])))