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 [])))
Related
I have (for instance) a mix of data structures such as {:name "Peter" :children "Mark"} and {:name "Mark" :children ["Julia" "John"] i.e. :children value is either a single string or a collection of strings. Other functions in my code expect that the value of :children is always a collection of strings, so I need to adapt the data for them.
Of course I can use something like:
(defn data-adapter [m]
(let [children (:children m)]
(assoc m :children
(if (coll? children)
children
[children]))))
But is there a more idiomatic/laconic way?
I think you will have to take no for an answer.
(if (coll? x) x [x]) is about as terse and expressive as it gets. It’s what people usually use for this problem (sometimes with sequential? instead of coll?).
cond-> enthusiasts like me sometimes try to use it in place of a simple conditional, but here it is no improvement:
(cond-> x (not (coll? x)) vector)
In the context of your code, however, you can do a little better. A lookup and association is best expressed with update:
(defn data-adapter [m]
(update m :children #(if (coll? %) % [%])))
the only advice would be to abstract that logic to some function, to keep your actual business logic clean.
(defn data-adapter [m]
(let [children (:children m)]
(assoc m :children (ensure-coll children))))
or, more concise, with update:
(defn data-adapter [m]
(update m :children ensure-coll))
where ensure-coll could be something like this:
(defn iffun [check & {:keys [t f] :or {t identity f identity}}]
#((if (check %) t f) %))
(def ensure-coll (iffun coll? :f list))
(or whatever another implementation you like)
user> (data-adapter {:children 1})
;;=> {:children (1)}
user> (data-adapter {:children [1]})
;;=> {:children [1]}
Perhaps not idiomatic, but laconic:
(flatten [x])
https://clojuredocs.org/clojure.core/flatten
Given I have this action to perform
(def structure (atom [{:id "an-id"} {:id "another-id"}]))
(def job {:type "some-job"})
(reset! structure (map #(if (= "an-id" (:id %)) (update-in % [:performed-jobs] (fnil conj []) job) %) #structure))
next structure:
[{:id "an-id" :performed-jobs [{:type "some-job"}]} {:id "another-id"}]
How can I use swap! to change a single occurrence in my structure instead of resetting it all?
Replace reset! by swap! by giving it a function that takes the old value of the atom and returns a new value to store in the atom.
Replace dereferencing of the atom with the function's argument, the old value.
(swap! structure
(fn [old]
(map #(if (= "an-id" (:id %))
(update-in % [:performed-jobs]
(fnil conj []) job) %)
old)))
Given I have the following form:
(def data-points [[1483249740 "ONE"]
[1483249680 "TWO"]
[1483249620 "THREE"]
[1483249560 "FOUR"]])
How can I transform this data into this?
{:data [1483249740 1483249680 1483249620 1483249560]
:value ["ONE" "TWO" "THREE" "FOUR"]}
I would also like to know how to approach similar problems.
What is your way to break this down and what functions do I need to know to transform any data.
I'm new to clojure and haven't found a satisfying solution for this.
Thank you
i would use this:
(zipmap [:data :value] (apply map vector data-points))
;;=> {:data [1483249740 1483249680 1483249620 1483249560],
;; :value ["ONE" "TWO" "THREE" "FOUR"]}
it uses a single pass over the data collections, but more concise than the reduction, yet shouldn't differ in terms of performance
the snippet (apply map vector data) is quite an idiomatic way to transpose matrix in clojure (in your case it is what you need, since it turns columns into rows)
user> (apply map vector [[:a 1] [:b 2] [:c 3]])
;;=> ([:a :b :c] [1 2 3])
I'd probably write this as a reduction. This approach only requires a single pass over 'data-points' which may be preferable.
(reduce
(fn [m [data value]]
(-> m
(update :data conj data)
(update :values conj value)))
{:data [] :values []}
data-points)
Another representation which can be both efficient and easier to work with:
(def data-points-map
(into {} data-points))
Then you can do
(get data-points-map 1483249740)
to get "ONE". Otherwise you would need to
(aget (:value m) (.indexOf (:data m) 1483249740))
to achieve the same result.
Finally you can
{:data (keys data-points-map)
:value (values data-points-map)}
to get the "weird" representation in the original question.
Here's an obtusely functional way to do it
(->> data-points
(mapv (partial mapv vector))
(mapv (partial zipmap [:data :value]))
(reduce (partial merge-with into)))
As a function:
(def format-data-points
(comp (partial reduce (partial merge-with into))
(partial mapv (partial zipmap [:data :value]))
(partial mapv (partial mapv vector))))
(format-data-points data-points)
(I wouldn't recommend doing either of these actually, just presenting this for fun)
Is there a way to describe arbitrary lazy self-recursive data structures in Clojure?
Let's say for example I wanted to do something like this:
(def inf-seq (fn rec [] (lazy-seq (cons 42 (rec)))))
(take 3 (inf-seq))
but with a map:
(def inf-map (fn rec [] (??? {:a (rec) :b 42})))
(get-in (inf-map) [:a :a :a :b])
Sequence laziness does not apply to deferred function evaluation in Clojure, which you would obviously need for constructing infinitely nested maps.
Try using Delays:
user=> (def inf-map (fn rec [] {:a (delay (rec)) :b 42}))
#'user/inf-map
user=> (inf-map)
{:a #<Delay#4e9f9a19: :pending>, :b 42}
user=> #(:a (inf-map))
{:a #<Delay#5afd479c: :pending>, :b 42}
I have a list of maps where each key is associated with a list of strings.
I would like to convert each of these string list to sets instead.
(def list-of-maps-of-lists '({:a ["abc"]} {:a ["abc"]} {:a ["def"]} {:x ["xyz"]} {:x ["xx"]}))
This is my best attempt so far:
(flatten (map (fn [amap] (for [[k v] amap] {k (set v)})) list-of-maps-of-lists))
=> ({:a #{"abc"}} {:a #{"abc"}} {:a #{"def"}} {:x #{"xyz"}} {:x #{"xx"}})
What is the idiomatic solution to this problem?
This is very similar to your solution.
Using list comprehension:
(map
#(into {} (for [[k v] %] [k (set v)]))
list-of-maps-of-lists)
Alternative:
(map
#(zipmap (keys %) (map set (vals %)))
list-of-maps-of-lists)
I prefer solving such problems with fmap function from clojure.contrib:
(map (partial fmap set)
list-of-maps-of-lists)
Update: According to This Migration Guide, fmap has been moved to clojure.algo.generic.functor namespace of algo.generic library.