How to unnest sequence spec? - clojure

with Clojure core.spec I can have the following:
(s/conform (s/cat :a even? :b (s/* odd?) :a2 even? :b2 (s/* odd?)) [2 3 5 12 13 15])
=> {:a 2, :b [3 5], :a2 12, :b2 [13 15]}
what I'd like to have is to remove redundancy by externalizing the sub spec:
(s/def ::even-followed-by-odds
(s/cat :a even? :b (s/* odd?)))
but
(s/conform (s/tuple ::even-followed-by-odds ::even-followed-by-odds) [2 3 5 12 13 15])
=> :clojure.spec/invalid
this one works:
(s/conform (s/tuple ::even-followed-by-odds ::even-followed-by-odds) [[2 3 5] [12 13 15]])
=> [{:a 2, :b [3 5]} {:a 12, :b [13 15]}]
So what I'm looking for is a function or macro (say unnest) which would it make work:
(s/conform (s/tuple (unnest ::even-followed-by-odds) (unnest ::even-followed-by-odds)) [2 3 5 12 13 15])
=> [{:a 2, :b [3 5]} {:a 12, :b [13 15]}]
how can I get that?

You need to stay in regex op land:
(s/conform (s/cat :x ::even-followed-by-odds :y ::even-followed-by-odds) [2 3 5 12 13 15])
{:x {:a 2, :b [3 5]}, :y {:a 12, :b [13 15]}}

Related

clojure: how can I merge these two maps?

I have one map that looks like
{:a {:b {:c {:d [[1 2 3]]}
:e "Hello"}}}
and another map that looks like {:a {:b {:c {:d [[4 5 6]]}}}}. How can I merge these two maps so that the result looks like this?
{:a {:b {:c {:d [[1 2 3] [4 5 6]]}
:e "Hello"}}}
For such a simple use-case, you might choose to stick with core Clojure functions:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(dotest
(let [x {:a {:b {:c {:d [[1 2 3]]}
:e "Hello"}}}
y {:a {:b {:c {:d [[4 5 6]]}}}}
yseq (get-in y [:a :b :c :d])
r1 (update-in x [:a :b :c :d] into yseq)
r2 (update-in x [:a :b :c :d] #(into % yseq)) ]
(is= r1 r2
{:a {:b {:c {:d [[1 2 3]
[4 5 6]]},
:e "Hello"}}})))
As shown for r2, I sometimes think it is clearer to use a self-contained closure function to explicitly show where the old value % is being used. I am often even more explicit, writing the r2 closure as:
(fn [d-val]
(into d-val yseq))
instead of using the #(...) reader macro.
You can use deep-merge-with from the deprecated clojure-contrib.map-utils:
(defn deep-merge-with [f & maps]
(apply
(fn m [& maps]
(if (every? map? maps)
(apply merge-with m maps)
(apply f maps)))
maps))
(def m1
{:a {:b {:c {:d [[1 2 3]]}
:e "Hello"}}})
(def m2
{:a {:b {:c {:d [[4 5 6]]}}}})
(deep-merge-with into m1 m2)
;; => {:a {:b {:c {:d [[1 2 3] [4 5 6]]}
;; :e "Hello"}}}

How to convert vector [1 2 3 :a :b :c :A :B :C] to [ {:index 1 :lower :a :upper :A} {:index 2 :lower :b :upper :B} {:index 3 :lower :c :upper :C} ]?

I want to know how to convert
vector [1 2 3 :a :b :c :A :B :C]
to
[ {:index 1 :lower :a :upper :A} {:index 2 :lower :b :upper :B} {:index 3 :lower :c :upper :C} ] ?
the vector may be [1 2 3 4 :a :b :c :d :A :B :C :D]
or if there is not an easy way,is there a way to convert
[{:index 1} {:index 2} {:index 3}] [{:lower :a} {:lower :b} {:lower :c}] [{:upper :A} {:upper :B} {:upper :C}]
to
[{:index 1 :lower :a :upper :A} {:index 2 :lower :b :upper :B} {:index 3 :lower :c :upper :C}]
Thanks!
So in general, when faced with a problem like this, I would go upstream and fix the input format. A vector that is a concatenation of arbitrary parts doesn't make any sense. For the sake of an answer, let us assume this isn't possible.
First we define a helper function to create the result maps:
(defn make-result [i l u]
{:index i :lower l :upper u})
Then we just need to map this function over the three subsequences:
(defn input->output [i]
(apply map make-result (partition (/ (count i) 3) i)))
We need to use apply as we generate a sequence of subsequences that we want to use as the parameters for map (recall that the function arity should match the number of sequences you pass to map - which conveniently our helper does).
This will work for both of the vectors given above.
(input->output [1 2 3 :a :b :c :A :B :C])
({:index 1, :lower :a, :upper :A} {:index 2, :lower :b, :upper :B} {:index 3, :lower :c, :upper :C})
(input->output [1 2 3 4 :a :b :c :d :A :B :C :D])
({:index 1, :lower :a, :upper :A} {:index 2, :lower :b, :upper :B} {:index 3, :lower :c, :upper :C} {:index 4, :lower :d, :upper :D})
Behaviour if the vector is in a different format may surprise or disappoint - perhaps some input validation is in order.
(let [ks [:index :lower :upper]
xs [1 2 3 :a :b :c :A :B :C]]
(->> xs
(partition (/ (count xs) (count ks)))
(apply map vector)
(mapv zipmap (repeat ks))))
How it works:
We first partition the vector by count:
(partition (/ (count xs) (count ks)) xs)=> ((1 2 3) (:a :b :c) (:A :B :C))
Then transpose the matrix:
(apply map vector *1)=> ([1 :a :A] [2 :b :B] [3 :c :C])
Finally zipmap with the provided keys for each row:
(mapv zipmap (repeat ks) *1)=> [{:index 1, :lower :a, :upper :A} {:index 2, :lower :b, :upper :B} {:index 3, :lower :c, :upper :C}]
If you can provide a list of the key-values, like the following (formatted to improve readability):
(def items [[{:index 1} {:index 2} {:index 3}]
[{:lower :a} {:lower :b} {:lower :c}]
[{:upper :A} {:upper :B} {:upper :C}]])
then you can use the following:
(apply map merge items)
;; returns ({:index 1, :lower :a, :upper :A} {:index 2, :lower :b, :upper :B} {:index 3, :lower :c, :upper :C})
This works by using the map function to merge the individual hash-maps in the 3 collections. First, the first elements of each collection are merged together, resulting in the element {:index 1, :lower :a, :upper :A}, then the second elements of each collection are merged, and so on.
Since the arguments to map merge are a collection, you need to use apply to provide the arguments to map.
I am not a clojure expert but maybe this helps:
; your data
(def x [1 2 3 :a :b :c :A :B :C])
; resolve symbols and numbers to strings
(def xa (map (fn [e] (if (keyword? e) (name e) (str e))) x))
; split into three sequences and zip this lists together
(let [xan (filter (fn [e] (not (empty? (re-matches #"[0-9]" e)))) xa)
xaa (filter (fn [e] (not (empty? (re-matches #"[a-z]" e)))) xa)
xaA (filter (fn [e] (not (empty? (re-matches #"[A-Z]" e)))) xa)]
(map-indexed (fn [i e] {:index e :lower (nth xaa i) :upper (nth xaA i)}) xan ))
You just build three sequences and iterate over any of them and use the index to access the corresponding elements from the other.

In Clojure, how do I make a nested map return a map with an inner map all set to 0?

So basically how do I make a function given the input {:A 1 :B 2 :C {:X 5 :Y 5 :Z 5} :D 1} and the key :C
return {:A 1 :B 2 :C {:X 0 :Y 0 :Z 0} :D 1}? It's the same mapping but with the nested map all set to 0. Given that we know that the key :C has the nested values.
I'm very new to clojure and I'm struggling with loops and iterations so any help would be appreciated.
Thanks.
(defn with-zero-vals-at-key
[m k]
(update m k (fn [m2] (zipmap (keys m2) (repeat 0)))))
(with-zero-vals-at-key {:A 1 :B 2 :C {:X 5 :Y 5 :Z 5} :D 1} :C)
;; => {:A 1, :B 2, :C {:X 0, :Y 0, :Z 0}, :D 1}
;; OR
(defn with-zero-vals
[m]
(zipmap (keys m) (repeat 0)))
(update {:A 1 :B 2 :C {:X 5 :Y 5 :Z 5} :D 1}
:C
with-zero-vals)
;; => {:A 1, :B 2, :C {:X 0, :Y 0, :Z 0}, :D 1}

How to generate the same value for two different paths in spec?

I'm trying to learn how to use overrides with s/gen.
I have a ::parent map which contains a ::child map. Both parent and child have keys in common. The requirement is that the keys have the same value between parent and child, e.g. {:a 1 :b 2 :child {:a 1 :b 2}. I know this seems redundant, but the problem domain requires it.
The code below generates examples, but the requirement above is not met.
Is there a way to use the same generated value in two locations?
(ns blah
(:require [clojure.spec.alpha :as s]
[clojure.spec.gen.alpha :as gen]))
(s/def ::a (s/int-in 1 5))
(s/def ::b (s/int-in 1 6))
(s/def ::child
(s/keys :req-un [::a ::b]))
(defn- parent-gen []
(let [a #(s/gen ::a)
b #(s/gen ::b)]
(s/gen ::parent-nogen
; overrides map follows
{::a a ::b b
::child #(s/gen ::child
; another overrides map
{::a a ::b b})))
(s/def ::parent-nogen
(s/keys :req-un [::a ::b ::child]))
(s/def ::parent
(s/with-gen ::parent-nogen parent-gen))
(gen/sample (s/gen ::parent))
You can do this with test.check's fmap:
(s/def ::a (s/int-in 1 5))
(s/def ::b (s/int-in 1 6))
(s/def ::child (s/keys :req-un [::a ::b]))
(s/def ::parent (s/keys :req-un [::a ::b ::child]))
(gen/sample
(s/gen ::parent
{::parent ;; override default gen with fmap'd version
#(gen/fmap
(fn [{:keys [a b child] :as p}]
(assoc p :child (assoc child :a a :b b)))
(s/gen ::parent))}))
=>
({:a 1, :b 2, :child {:a 1, :b 2}}
{:a 2, :b 2, :child {:a 2, :b 2}}
{:a 1, :b 1, :child {:a 1, :b 1}}
{:a 3, :b 2, :child {:a 3, :b 2}}
{:a 2, :b 4, :child {:a 2, :b 4}}
{:a 4, :b 4, :child {:a 4, :b 4}}
{:a 3, :b 3, :child {:a 3, :b 3}}
{:a 4, :b 4, :child {:a 4, :b 4}}
{:a 3, :b 4, :child {:a 3, :b 4}}
{:a 3, :b 4, :child {:a 3, :b 4}})
fmap takes a function f and a generator gen, and returns a new generator that applies f to every value generated from gen. Here we pass it the default generator for ::parent, and a function that takes those parent maps and copies the appropriate keys into the :child map.
If you want this spec to enforce that equality (besides just generation), you'll need to add an s/and to the ::parent spec with a predicate to check that:
(s/def ::parent
(s/and (s/keys :req-un [::a ::b ::child])
#(= (select-keys % [:a :b])
(select-keys (:child %) [:a :b]))))
Edit: here's another way to do the same thing with gen/let that allows for a more "natural" let-like syntax:
(gen/sample
(gen/let [{:keys [a b] :as parent} (s/gen ::parent)
child (s/gen ::child)]
(assoc parent :child (assoc child :a a :b b))))

Make specter return (part of) the selected path

In the following structure, I know how to iterate over the :x values of all children of :whatever:
=> (specter/select
[:whatever specter/MAP-VALS :x]
{:whatever {:a {:x 1} :b {:x 2}}})
[1 2]
What I'd like to get though is something like the following, that contains the wild-carded map key.
[[:a 1] [:b 2]]
How can this be done with specter?
(select
[:whatever ALL (collect-one FIRST) LAST :x]
{:whatever {:a {:x 1}
:b {:x 2}
:c {:x 55}}})
=> [[:a 1] [:b 2] [:c 55]]