How would I best iterate over the below object in Clojure?
{
:item-set-1 ["a" "b" "c"]
:item-set-2 ["d" "e" "f"]
}
I want to try and identify all sub sets of the object and produce a result like this:
{
[:item-set-1 ["a"]]
[:item-set-1 ["a" "b"]]
[:item-set-1 ["a" "b" "c"]]
[:item-set-1 ["b"]]
[:item-set-1 ["b" "c"]]
[:item-set-1 ["c"]]
[:item-set-2 ["d"]]
[:item-set-2 ["d" "e"]]
[:item-set-2 ["d" "e" "f"]]
[:item-set-1 ["e"]]
[:item-set-1 ["e" "f"]]
[:item-set-1 ["f"]]
[:item-set-1 ["a"] [:item-set-2 ["d"]]]
[:item-set-1 ["b"] [:item-set-2 ["e"]]]
[:item-set-1 ["c"] [:item-set-2 ["f"]]]
[:item-set-1 ["a" "b"] [:item-set-2 ["d" "e"]]]
[:item-set-1 ["a" "b"] [:item-set-2 ["e" "f"]]]
[:item-set-1 ["a" "b"] [:item-set-2 ["d" "f"]]]
[:item-set-1 ["b" "c"] [:item-set-2 ["d" "e"]]]
[:item-set-1 ["b" "c"] [:item-set-2 ["e" "f"]]]
[:item-set-1 ["b" "c"] [:item-set-2 ["d" "f"]]]
[:item-set-1 ["a" "c"] [:item-set-2 ["d" "e"]]]
[:item-set-1 ["a" "c"] [:item-set-2 ["e" "f"]]]
[:item-set-1 ["a" "c"] [:item-set-2 ["d" "f"]]]
[:item-set-1 ["a" "b" "c"] [:item-set-2 ["d" "e" "f"]]]
}
I belive I can use clojure.math.combinatorics to identify the subsets in each key but not the whole object.
Update:
I attempted to produce the sub sets with this code:
(defn generate-freq-item-set []
(let [result [{:data (generate-string {:item-set-1 ["a" "b" "c"] :item-set-2 ["d" "e" "f"]})}]
items (as-> () items
(->> (for [row result]
(for [data (parse-string (:data row))]
(for [subset (combo/subsets (second data))]
(conj items {(first data) subset}))))))
frequencies (sort-by last >
(->> (apply concat (apply concat (apply concat items)))
(frequencies)))]
(prn frequencies)))
But this produces the following output which is not exactly what I'm after:
([{"item-set-1" ()} 1]
[{"item-set-2" ("d")} 1]
[{"item-set-1" ("a" "b" "c")} 1]
[{"item-set-2" ("d" "e")} 1]
[{"item-set-1" ("b" "c")} 1]
[{"item-set-2" ("d" "e" "f")} 1]
[{"item-set-2" ()} 1]
[{"item-set-1" ("a" "b")} 1]
[{"item-set-1" ("c")} 1]
[{"item-set-2" ("e")} 1]
[{"item-set-2" ("d" "f")} 1]
[{"item-set-2" ("f")} 1]
[{"item-set-2" ("e" "f")} 1]
[{"item-set-1" ("b")} 1]
[{"item-set-1" ("a")} 1]
[{"item-set-1" ("a" "c")} 1])
I'd approach this problem as follows.
At first, I'd splice initial map you have into a list, saving in the metadata the info about the set where the item belonged to.
Since it is not possible to attach metadata to raw strings, we need to create a wrapper type:
(defrecord ItemSetElement [x])
(defn make-item-set-element [x]
(->ItemSetElement x))
(defn unwrap-item-set-element [elem]
(:x elem))
Then go the functions that convert an initial map to sequence, saving needed info:
(defn wrap-element-and-save-owner [owner s]
(with-meta (make-item-set-element s) {::owner owner}))
(defn prepare-data [data]
(mapcat
(fn [[key ss]]
(map (partial wrap-element-and-save-owner key) ss))
data))
> (prepare-data {:item-set-1 ["a" "b"], :item-set-2 ["c"]})
({:x "a"} {:x "b"} {:x "c"})
As you see, the result of prepare-data is just a sequence, but every element of the sequence has information about the "owner" set in its meta, e.g.:
> (meta (first (prepare-data {:item-set-1 ["a" "b"], :item-set-2 ["c"]})))
{:user/owner :item-set-1}
Having a sequence, we can use clojure.math.combinatorics/subsets to generate all its subsets:
> (require '[clojure.math.combinatorics :as combo])
nil
> (combo/subsets (prepare-data {:item-set-1 ["a" "b"], :item-set-2 ["c"]}))
(()
({:x "a"})
({:x "b"})
({:x "c"})
({:x "a"} {:x "b"})
({:x "a"} {:x "c"})
({:x "b"} {:x "c"})
({:x "a"} {:x "b"} {:x "c"}))
Each element of the subset still has information about its "owner", so we can easily convert it to an initial-like structure. Here's a function for that:
(defn reconstruct-item-sets [subset]
(->> subset
(group-by #(::owner (meta %)))
(map (fn [[key elements]]
[key (map unwrap-item-set-element elements)]))
(into {})))
To sum up here's all the code including function, that glues everything together:
(require '[clojure.math.combinatorics :as combo])
(defrecord ItemSetElement [x])
(defn make-item-set-element [x]
(->ItemSetElement x))
(defn unwrap-item-set-element [elem]
(:x elem))
(defn wrap-element-and-save-owner [owner s]
(with-meta (make-item-set-element s) {::owner owner}))
(defn prepare-data [data]
(mapcat
(fn [[key ss]]
(map (partial wrap-element-and-save-owner key) ss))
data))
(defn reconstruct-item-sets [subset]
(->> subset
(group-by #(::owner (meta %)))
(map (fn [[key elements]]
[key (map unwrap-item-set-element elements)]))
(into {})))
(defn my-subsets [data]
(->> data
prepare-data
combo/subsets
(map reconstruct-item-sets)))
(def data {:item-set-1 ["a" "b"]
:item-set-2 ["c" "d" "e"]})
> (my-subsets data)
({}
{:item-set-1 ("a")}
{:item-set-1 ("b")}
{:item-set-2 ("c")}
{:item-set-2 ("d")}
{:item-set-2 ("e")}
{:item-set-1 ("a" "b")}
{:item-set-1 ("a"), :item-set-2 ("c")}
{:item-set-1 ("a"), :item-set-2 ("d")}
{:item-set-1 ("a"), :item-set-2 ("e")}
{:item-set-1 ("b"), :item-set-2 ("c")}
{:item-set-1 ("b"), :item-set-2 ("d")}
{:item-set-1 ("b"), :item-set-2 ("e")}
{:item-set-2 ("c" "d")}
{:item-set-2 ("c" "e")}
{:item-set-2 ("d" "e")}
{:item-set-1 ("a" "b"), :item-set-2 ("c")}
{:item-set-1 ("a" "b"), :item-set-2 ("d")}
{:item-set-1 ("a" "b"), :item-set-2 ("e")}
{:item-set-1 ("a"), :item-set-2 ("c" "d")}
{:item-set-1 ("a"), :item-set-2 ("c" "e")}
{:item-set-1 ("a"), :item-set-2 ("d" "e")}
{:item-set-1 ("b"), :item-set-2 ("c" "d")}
{:item-set-1 ("b"), :item-set-2 ("c" "e")}
{:item-set-1 ("b"), :item-set-2 ("d" "e")}
{:item-set-2 ("c" "d" "e")}
{:item-set-1 ("a" "b"), :item-set-2 ("c" "d")}
{:item-set-1 ("a" "b"), :item-set-2 ("c" "e")}
{:item-set-1 ("a" "b"), :item-set-2 ("d" "e")}
{:item-set-1 ("a"), :item-set-2 ("c" "d" "e")}
{:item-set-1 ("b"), :item-set-2 ("c" "d" "e")}
{:item-set-1 ("a" "b"), :item-set-2 ("c" "d" "e")})
I don't have clojure installed now, but in essense, you need to do it like this:
1) map subset function to each itemset. You will end up with two sets with all the subsets.
2) apply cartesian-product to these two sets of subsets. That's it. Cartesian product takes two sets and outputs all possible combinations.
I'll get back to you once I'm back from work and have clojure installed
EDIT
finally got home, here's the code:
(require '[clojure.math.combinatorics :as combo])
(def inputdata {:item-set-1 ["a" "b" "c"] :item-set-2 ["d" "e" "f"]})
(defn subsets-without-empty [set] (filter not-empty (combo/subsets set)))
(defn to-subset-maps [kv]
(map (fn [v] {(key kv) v})
(subsets-without-empty (val kv))))
(defn create-subsets [dictOfSets] (map to-subset-maps dictOfSets))
(apply combo/cartesian-product (create-subsets inputdata))
subsets-without-empty gets you all subsets excluding empty one, as you suggested
to-subset-maps converts {:a [1 2]} into [{:a [1]} {:a [2]} {:a [1 2]}], i.e. creates subsets and propagates the original key to each subset (required by your output format)
create-subsets just applies the to-subset-maps for each member of the input map.
Finally, we have the results in a collection. So we just need to unwrap it and pass to cartesian-product to get all the combinations, that's where the last line with apply comes in. Now, this solution works for any number of dimensions (or keys in the input map).
Related
I need to match two kinds of tuples and produce maps from them.
Both have a keyword and a string. One can have a third item (a language code).
[<key> <value>] ~> {:type <key> :value <value>}
[<key> <value> <lang>] ~> {:type <key> :value <value> :lang <lang>}
I only need to match those which keyword is either :foo or :bar and decided that I would use clojure.core.match:
(ns so.example
(:require
[clojure.core.match :refer [match]]))
(defn example-1 [ast]
(let [l10n-key #{:foo :bar}]
(match ast
[(k :guard l10n-key) v lang] {:type k :value v :lang lang}
[(k :guard l10n-key) v] {:type k :value v})))
(example-1 [:foo 10])
;=> {:type :foo, :value 10}
(example-1 [:bar 20 "en"])
;=> {:type :bar, :value 20, :lang "en"}
That works but I wanted to reuse the matching pattern :guard l10n-key in different clauses. So I thought I could use some syntax quoting and unquote splicing:
(defn example-2 [ast]
(let [l10n-key-match [:guard #{:foo :bar}]]
(match ast
[`(k ~#l10n-key-match) v lang] {:type k :value v :lang lang}
[`(k ~#l10n-key-match) v] {:type k :value v})))
However the defn expression crashes with:
Unexpected error (AssertionError) macroexpanding match at (form-init11111096422056977084.clj:3:5).
Invalid list syntax (clojure.core/concat (clojure.core/list (quote so.example/k)) l10n-key-match) in (clojure.core/seq (clojure.core/concat (clojure.core/list (quote so.example/k)) l10n-key-match)). Valid syntax: [[:default :guard] [:or :default] [:default :only] [:default :seq] [:default :when] [:default :as] [:default :<<] [:default :clojure.core.match/vector]]
What am I doing wrong?
Isn't this what spec, that already ships with Clojure, does? You would define your pattern like
(ns playground.catspec
(:require [clojure.spec.alpha :as spec]))
(spec/def ::type #{:foo :bar})
(spec/def ::value number?)
(spec/def ::lang #{"en" "sv" "fr"})
(spec/def ::key-value-lang (spec/cat :type ::type
:value ::value
:lang (spec/? ::lang)))
We use spec/def to define a spec, spec/cat to concatenate specs and spec/? for a spec that is optional.
Then we use conform to parse the tuple:
(spec/conform ::key-value-lang [:foo 10])
;; => {:type :foo, :value 10}
(spec/conform ::key-value-lang [:bar 20 "en"])
;; => {:type :bar, :value 20, :lang "en"}
(spec/conform ::key-value-lang [:bar 119 "fr"])
;; => {:type :bar, :value 119, :lang "fr"}
(spec/conform ::key-value-lang [119 :foo])
;; => :clojure.spec.alpha/invalid
(spec/conform ::key-value-lang [:bar 119 "uj"])
;; => :clojure.spec.alpha/invalid
(spec/conform ::key-value-lang [:bar])
;; => :clojure.spec.alpha/invalid
(spec/conform ::key-value-lang [:bar 119 "fr" :asdfasdf])
;; => :clojure.spec.alpha/invalid
(spec/conform ::key-value-lang {:a 1 :b 4})
;; => :clojure.spec.alpha/invalid
Doesn't solve the problem with clojure.core.match, but you don't really need it for something this simple:
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(dotest
(let [data [[:foo 10]
[:bar 20 "en"]
[:fizz 10]
[:buzz 20 "en"]]
keep-tags #{:foo :bar}
data-keep (filterv #(contains? keep-tags (first %)) data)
result (forv [tuple data-keep]
(zipmap [:type :value :lang] tuple))]
(is= result [{:type :foo, :value 10}
{:type :bar, :value 20, :lang "en"}])))
You may also be interested in these helper functions:
tupelo.core/matches? A nicer interface to clojure.core.match
tupelo.core/submatch?
tupelo.core/wild-match?
tupelo.core/wild-submatch?
I'm trying to create a stateful transducer, join-averages (gist here).
Running the let block shows me that I'm correctly joining values. But the result output still doesn't have the joined value.
...
c' {:tick-list {:uuid 1, :last-trade-price 11.1}, :ema-list {:uuid 1, :last-trade-price-exponential 10}, :sma-list {:uuid 1, :last-trade-price-average 10.1}}
record {:uuid 1, :last-trade-price 11.1}
c' {:tick-list {:uuid 2, :last-trade-price 11.2}, :ema-list {:uuid 2, :last-trade-price-exponential 11}, :sma-list {:uuid 2, :last-trade-price-average 10.2}}
record {:uuid 2, :last-trade-price 11.2}
...
c' {:tick-list {:uuid 3, :last-trade-price 11.3}, :ema-list {:uuid 3, :last-trade-price-exponential 12}, :sma-list {:uuid 3, :last-trade-price-average 10.3}}
record {:uuid 1, :last-trade-price-exponential 10}
record {:uuid 2, :last-trade-price-exponential 11}
record {:uuid 3, :last-trade-price 11.3}
record {:uuid 3, :last-trade-price-exponential 12}
Any ideas as to why the join-averages stateful transducer, not returning the correct result?
CODE
(:require [clojure.core.async :refer [chan sliding-buffer <! go-loop pipeline onto-chan] :as async]
[clojure.set :refer [subset?]]
[clojure.tools.logging :as log])
(defn has-all-lists? [averages-map]
(subset? #{:tick-list :sma-list :ema-list} (->> averages-map keys (into #{}))))
(defn join-averages []
(let [state (atom {})]
(fn [rf]
(fn
([] (rf))
([accumulator] (rf accumulator))
([accumulator input]
(let [uuid (:uuid input)
entry (cond
(:last-trade-price-exponential input) {:ema-list input}
(:last-trade-price-average input) {:sma-list input}
(:last-trade-price input) {:tick-list input})]
(if-let [current (get #state uuid)]
(let [_ (swap! state update-in [uuid] merge entry)
c' (get #state uuid)]
(log/info "c'" c')
(log/info "accumulator" accumulator)
(log/info "state" (with-out-str (clojure.pprint/pprint #state)))
(if (has-all-lists? c')
c'
(rf accumulator input)))
(do (swap! state merge {uuid entry})
(rf accumulator input)))))))))
(comment
(let [ema-list [{:uuid "1" :last-trade-price-exponential 10}
{:uuid "2" :last-trade-price-exponential 11}
{:uuid "3" :last-trade-price-exponential 12}]
sma-list [{:uuid "1" :last-trade-price-average 10.1}
{:uuid "2" :last-trade-price-average 10.2}
{:uuid "3" :last-trade-price-average 10.3}]
tick-list [{:uuid "1" :last-trade-price 11.1}
{:uuid "2" :last-trade-price 11.2}
{:uuid "3" :last-trade-price 11.3}]
ec (chan (sliding-buffer 100))
sc (chan (sliding-buffer 100))
tc (chan (sliding-buffer 100))
_ (onto-chan ec ema-list)
_ (onto-chan sc sma-list)
_ (onto-chan tc tick-list)
merged-ch (async/merge [tc sc ec])
output-ch (chan (sliding-buffer 100) (join-averages))]
(async/pipeline 1 output-ch (join-averages) merged-ch)
(go-loop [r (<! output-ch)]
(when-not (nil? r)
(log/info "record" r)
(recur (<! output-ch))))))
You didn't give us what your result should look like so I'll have to guess.
Also, to see how to implement a stateful transducer I usually just look at distinct. You should init your state after the transducer was initialized. This should work:
(defn has-all-lists? [averages-map]
(set/subset? #{:tick-list :sma-list :ema-list} (->> averages-map keys (into #{}))))
(defn join-averages []
(fn [rf]
(let [state (atom {})]
(fn
([] (rf))
([accumulator] (rf accumulator))
([accumulator input]
(let [uuid (:uuid input)
entry (condp #(%1 %2) input
:last-trade-price-exponential {:ema-list input}
:last-trade-price-average {:sma-list input}
:last-trade-price {:tick-list input})]
(let [nv (swap! state update-in [uuid] merge entry)
c' (get nv uuid)]
(if (has-all-lists? c')
(rf accumulator c')
accumulator))))))))
(let [ema-list [{:uuid "1" :last-trade-price-exponential 10}
{:uuid "2" :last-trade-price-exponential 11}
{:uuid "3" :last-trade-price-exponential 12}]
sma-list [{:uuid "1" :last-trade-price-average 10.1}
{:uuid "2" :last-trade-price-average 10.2}
{:uuid "3" :last-trade-price-average 10.3}]
tick-list [{:uuid "1" :last-trade-price 11.1}
{:uuid "2" :last-trade-price 11.2}
{:uuid "3" :last-trade-price 11.3}]]
(into []
(join-averages)
(concat ema-list sma-list tick-list)))
I have:
(def moo (my-func))
which returns:
[{:id 1 :name "Bob"}
{:id 2 :name "Jane"}
{:id 3 :name "Greg"}]
How do I now access moo to get the :name where :id=3? Thanks.
I would rather prefer using some (since it is more logical than using filter i guess, because it is designed to find exactly one value):
(def data
[{:id 1 :name "Bob"}
{:id 2 :name "Jane"}
{:id 3 :name "Greg"}])
(defn name-by-id [id data]
(some #(when (= (:id %) id) (:name %)) data))
user>
(name-by-id 3 data)
"Greg"
user>
(name-by-id 100 data)
nil
One way would be to use a filter
(def moos
[{:id 1 :name "Bob"}
{:id 2 :name "Jane"}
{:id 3 :name "Greg"}])
(defn name-for-id
[id]
(:name (first (filter #(= (:id %) id) moos))))
(name-for-id 3) ; => "Greg"
(def names
[{:id 1 :name "Bob"}
{:id 2 :name "Jane"}
{:id 3 :name "Greg"}])
;;get the :name where :id=3
(defn answer []
(:name (first (filter (fn [e] (= 3 (:id e))) names))))
In the above rather than names you could have moo.
I have data in the following format:
[["i1" "i2"]
['("A" "B" "C" "D") '("red" "blue" "green" "yellow")]]
I want to transform this to get a new data structure:
[["column" "value"]
["i1" "A"]
["i1" "B"]
["i1" "C"]
["i1" "D"]
["i2" "red"]
["i2" "blue"]
["i2" "green"]
["i2" "yellow"]]
Any help with this difficult problem would be great.
My attempts to far have involved using nested "for" statements, but I cannot get the resulting vectors in the same level as the header vector, despite many attempts to convert the results. I have also used "interleave" and "repeat" on the value for column, but that too creates lists at the wrong level.
(defn doit [[is vss]]
(vec (cons
["column" "value"]
(mapcat (fn [i vs] (mapv (fn [v] [i v]) vs)) is vss))))
(defn convert
[[header data]]
(->> (mapcat #(map vector (repeat %) %2) header data)
(cons ["column" "value"])))
(convert '[["i1" "i2"] [("A" "B" "C" "D") ("red" "blue" "green" "yellow")]])
;; => (["column" "value"]
;; ["i1" "A"] ["i1" "B"] ["i1" "C"] ["i1" "D"]
;; ["i2" "red"] ["i2" "blue"] ["i2" "green"] ["i2" "yellow"])
(defn conform
[[ks & rows]]
(mapcat
(fn [row]
(mapcat (fn [k val]
(map (partial vector k) val))
ks row))
rows))
Your example:
(conform [["i1" "i2"] ['("A" "B" "C" "D") '("red" "blue" "green" "yellow")]])
=> (["i1" "A"] ["i1" "B"] ["i1" "C"] ["i1" "D"]
["i2" "red"] ["i2" "blue"] ["i2" "green"] ["i2" "yellow"])
Bonus:
(conform [["i1" "i2"]
['("A" "B" "C" "D") '("red" "blue" "green" "yellow")]
['("E" "F" "G" "H") '("Mara" "Lara" "Clara" "Foxy")]])
=> (["i1" "A"] ["i1" "B"] ["i1" "C"] ["i1" "D"]
["i2" "red"] ["i2" "blue"] ["i2" "green"] ["i2" "yellow"]
["i1" "E"] ["i1" "F"] ["i1" "G"] ["i1" "H"]
["i2" "Mara"] ["i2" "Lara"] ["i2" "Clara"] ["i2" "Foxy"])
More bonus:
(conform [["i1" "i2" "i3"]
['("A" "B" "C" "D") '("red" "blue" "green" "yellow") ["Ufo"]]
['("E" "F" "G" "H") '("Mara" "Lara" "Clara" "Foxy") ["Orange" "Apple"]]])
=> (["i1" "A"] ["i1" "B"] ["i1" "C"] ["i1" "D"]
["i2" "red"] ["i2" "blue"] ["i2" "green"] ["i2" "yellow"]
["i3" "Ufo"]
["i1" "E"] ["i1" "F"] ["i1" "G"] ["i1" "H"]
["i2" "Mara"] ["i2" "Lara"] ["i2" "Clara"] ["i2" "Foxy"] ["i3" "Orange"] ["i3" "Apple"])
Creating a map from the result is easy:
(reduce (fn [acc [k v]]
(update-in acc [k] (fnil conj []) v)) {} *1 )
=> {"i3" ["Ufo" "Orange" "Apple"],
"i2" ["red" "blue" "green" "yellow" "Mara" "Lara" "Clara" "Foxy"],
"i1" ["A" "B" "C" "D" "E" "F" "G" "H"]}
There are some fun answers here. I will just add that conceptually, I think this is a good place for for. For each item in the first vector, you want to pair it with items from the corresponding lists in the second vector.
(defn convert [[cols vals]]
(vec (cons ["column" "value"] ;; turn the list into a vector.
(for [i (range (count cols)) ;; i <- index over columns
j (nth vals i)] ;; j <- items from the ith list
[(nth cols i) j])))) ;; [col val]
user=>(convert data)
This is easily modified to handle more vectors of values:
(defn convert [[cols & vals]]
(cons ["column" "value"]
(mapcat #(for [i (range (count cols))
j (nth % i)]
[(nth cols i) j])
vals)))
Not idiomatic, but did the trick.
((fn [coll]
(let [ks (first coll) vs (last coll)]
(cons
'("column" "value")
(partition 2 (concat
(interleave (repeat (first ks)) (first vs))
(interleave (repeat (last ks)) (last vs)))))))
[["i1" "i2"]
['("A" "B" "C" "D") '("red" "blue" "green" "yellow")]])
Please look at the following code:
(def data {:color ["R", "B", "G"] :name "Hello" :up "down"})
(defn collapse-vector-kvp [k v]
(map #(hash-map k %) v))
(defn collapse-map [m]
(map #(let
[x %]
(if (vector? (val x))
(collapse-vector-kvp (key x) (val x))
(hash-map (key x) (val x))
)) m))
(collapse-map data)
=> ({:name "Hello"} ({:color "R"} {:color "B"} {:color "G"}) {:up "down"})
What I would like to do is create a single list, rather than have the 'color' entries be in a list within the list. Is this easily achievable?
user=> (def data2 '({:name "Hello"} ({:color "R"} {:color "B"} {:color "G"}) {:up "down"}))
#'user/data2
user=> (flatten data2)
({:name "Hello"} {:color "R"} {:color "B"} {:color "G"} {:up "down"})
Another version of collapse-map:
(defn collapse-map [m]
(let [sep-m (group-by (comp vector? val) m)]
(concat (map (fn [[k v]] {k v})
(sep-m false))
(apply concat (map (fn [[k v]]
(collapse-vector-kvp k v))
(sep-m true))))))
(def test-data {:color ["R" "B" "G"]
:name "Hello"
:k ["v1" "v2" "v3"]
:up "down"})
(collapse-map test-data)
=> ({:name "Hello"}
{:up "down"}
{:color "R"}
{:color "B"}
{:color "G"}
{:k "v1"}
{:k "v2"}
{:k "v3"})