This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2016-08-13
Channels
- # aws (5)
- # bangalore-clj (2)
- # beginners (74)
- # boot (2)
- # cider (12)
- # cljs-dev (5)
- # cljsjs (2)
- # cljsrn (7)
- # clojure (41)
- # clojure-boston (4)
- # clojure-brasil (3)
- # clojure-russia (12)
- # clojurescript (73)
- # cloverage (35)
- # datomic (7)
- # euroclojure (1)
- # hoplon (34)
- # immutant (1)
- # luminus (9)
- # onyx (7)
- # overtone (3)
- # parinfer (1)
- # proton (2)
- # re-frame (3)
- # reagent (5)
- # spacemacs (5)
- # testing (2)
- # untangled (3)
- # yada (3)
what would be the syntax to destructure a map looking something like {:c #{:b :a}, :f #{:e :c :b :d :a} .....}
incoming as a parameter to a function so that inside that function I could distinguish between the key and a value of each map entry?
@senya22: you could use something like (for [[k v] my-map] ...)
@val_waeselynck: hm, and what if I want to use my-map
as a second arg of a reducing function which already processes each individual entry of a map?
@plexus: just wanted to walk over the elements in a map and compare its values to a static set and if its equal - to collect the key into the resulting collection. That comparison might need to be extended further to check not only for equality but for a subset as well...
(let [m {:x 1 :y 2 :z 3}
vset #{2 3}]
(map first (filter (comp vset last) m)))
(let [m {:x 1 :y 2 :z 3}
vset #{2 3}]
(reduce-kv (fn [coll k v]
(if (vset v)
(conj coll v)
coll)) [] m))
if you have filter-kv
availble (e.g. from Medley or Encore), you can do something like this, which I find reads a bit better
no, no, I actually want the keys - I check the values if it's equal or a subset and accumulate the key
yeah so instead of using a set literal you can define vset
to be whatever test you want to perform on the values
you mean in the
(let [m {:x 1 :y 2 :z 3}
vset #{2 3}]
(keys (filter-kv (fn [k v] (vset v)) m)))
?could you explain how the
(map first (filter (comp vset last) m)))
expression works please?sure, comp
means compose, it takes two functions and returns a new function by chaining the two functions together, (comp vset last)
is the same as #(last (vset %))
, which is a shorthand for (fn [x] (last (vset x)))
vset
is a set (`#{2 3}`), if you treat a set like a function, then it tests if whatever you give it is in the set
if you use a function like filter
, map
, reduce
over a map as we do here, then it treats the map as a sequence of "map entries", which you can think of as two-element vectors
so (filter (comp vset last) m)
with m
being {:x 1 :y 2 :z 3}
is the same as (filter #(vset (last %)) [[:x 1] [:y 2] [:z 3]])
(last [:x 1]) ;;=> 1
(vset (last [:x 1])) ;;=> false
(last [:y 2]) ;;=> 2
(vset (last [:y 2])) ;;=> true
the input of filter is a sequence of "map entries", so the output is also a sequence of map entries, just fewer
so the map is already treated as a sequence of its entries by default - there's no need to destructure it
@plexus: that's great, thank you so much! Let me now try to incorporate all this knowledge into my task
what would be the simplest way to flatten a map like this {:e #{:m :f}, :c #{:f}, :b #{:c :f}, :d #{:m :f}, :a #{:c :f}}
into the unique set of its values: #{:c :m :f}
?
Looks like an extra reduce needs to be added to something like this:
(reduce (fn[ flattened [key val]]
(conj flattened val))
#{}
{:e #{:m :f}, :c #{:f}, :b #{:c :f}, :d #{:m :f}, :a #{:c :f}})
=> #{#{:m :f} #{:c :f} #{:f}}
clojure.set/union
instead of conj
should do it?
why is it that the accumulator's type doesn' t really matter, whether I pass in a [], #{} or {}'
- the results is always a set?
(reduce (fn[ flattened [key val]]
(clojure.set/union flattened val))
{}
{:e #{:m :f}, :c #{:f}, :b #{:c :f}, :d #{:m :f}, :a #{:c :f}})
=> #{:m :c :f}
actually in this case it's kind of a coincidence... that is to say, the set functions like union
expect their inputs to be sets, but they don't check their inputs. In this case you're getting a set back out even though you're passing it a map and a set, but that behavior is not defined, you could have gotten a map back, or an error
(reduce (fn[ flattened [key val]]
(clojure.set/union flattened val))
{:x 1 :y 2 :z 3}
{:e #{:m :f}, :c #{:f}, :b #{:c :f}, :d #{:m :f}, :a #{:c :f}})
;;=> BOOM, this blows up
(reduce (fn[ flattened [key val]]
(clojure.set/union flattened val))
[1 2 3]
{:e #{:m :f}, :c #{:f}, :b #{:c :f}, :d #{:m :f}, :a #{:c :f}})
;; and now it's a vector ¯\_(ツ)_/¯
;;=> [1 2 3 :m :f :f :c :f :m :f :c :f]
this is called "garbage in - garbage out", you're expected to use these functions as they are intended to be used, if you don't all bets are off. This is (in this case) considered preferable to incurring the extra overhead of checking the input types
presumably once Clojure 1.9 is out there will be specs for these functions, and so you will be able to turn on instrumentation during development so you do get that type checking
weird, this still returns a set:
(reduce (fn[ flattened [key val]]
(clojure.set/union flattened val))
[1]
{:e #{:m :f}, :c #{:f}, :b #{:c :f}, :d #{:m :f}, :a #{:c :f}})
=> #{1 :m :c :f}
yeah 🙂 I had to look at how union
is implemented to come up with those examples 😉
(defn union
"Return a set that is the union of the input sets"
{:added "1.0"}
([] #{})
([s1] s1)
([s1 s2]
(if (< (count s1) (count s2))
(reduce conj s2 s1)
(reduce conj s1 s2)))
([s1 s2 & sets]
(let [bubbled-sets (bubble-max-key count (conj sets s2 s1))]
(reduce into (first bubbled-sets) (rest bubbled-sets)))))
in other words, it's taking whichever collecting is bigger, and conj'ing the elements of the other one onto it
I tried to implement this via some 2 functions but that didn't lead me far:
(defn flatten-dpdnts[dpdts-map]
(reduce apply-flatten #{} dpdts-map))
(defn apply-flatten [flattened dpdnts]
(let [[key val] dpdnts]
(reduce #(conj flattened %1) flattened val))
)
that reduce looks a bit funny, probably should be (reduce #(conj %1 %2) flattened val)
or just (conj flattened val)
... I think, hard to say without knowing the shape of your data
dpdts-map
is something like {:e #{:m :f}, :c #{:f}, :b #{:c :f}, :d #{:m :f}, :a #{:c :f}}
?
(defn apply-flatten [flattened dpdnts]
(let [[key val] dpdnts]
(conj flattened val)))
(defn flatten-dpdnts [dpdts-map]
(reduce apply-flatten #{} dpdts-map))
(flatten-dpdnts {:e #{:m :f}, :c #{:f}, :b #{:c :f}, :d #{:m :f}, :a #{:c :f}})
;;=> #{#{:m :f} #{:c :f} #{:f}}
(sorry, I'm just rambling at this point, always fun to help solve these little puzzles)
🙂 ah, I had it on this stage once, but then I wanted to do another iteration to have a single set
yeah you were really close actually, notice how your second reduce is very similar to the one in union