I was trying to run this script in repl,
(clojure.set/rename-keys {:id "faeb2d4a-2415-423e-bf65-9266ae4c3326"}
{:id :crux.db/id})
it returns
#:crux.db{:id "faeb2d4a-2415-423e-bf65-9266ae4c3326"}
instead of {:crux.db/id "faeb2d4a-2415-423e-bf65-9266ae4c3326"}
I thought there is something wrong, but the data is actually correct
(:crux.db/id #:crux.db{:id "faeb2d4a-2415-423e-bf65-9266ae4c3326"})
=> "faeb2d4a-2415-423e-bf65-9266ae4c3326"
(= #:crux.db{:id "faeb2d4a-2415-423e-bf65-9266ae4c3326"} {:crux.db/id "faeb2d4a-2415-423e-bf65-9266ae4c3326"})
=> true
Why?
See https://clojure.org/reference/reader#_maps which explains the "Map namespace syntax".
#:foo{:a 1 :b 2 :c 3} ; shorthand for {:foo/a 1 :foo/b 2 :foo/c 3}
It is a shorthand, to save space if many keys in the map have the same namespace.
(let [some-map {:crux.db/name "Joe Smith"
:crux.db/address "123 Foo Bar Lane"
:crux.db/phone "666.555.1234"
:crux.db/age 64}]
then some-map looks like:
#:crux.db{:name "Joe Smith",
:address "123 Foo Bar Lane",
:phone "666.555.1234",
:age 69}
Related
I'm sorry if this has been answered elsewhere, but I can't seem to find an example that matches the pattern of what I'm looking for. I also may not yet understand recursive specter paths fully.
If I have the data (explicitly with the nested vector):
{:a "1" :b "2" :c [ {:a "3" :b "4"} {:a "5" :b "6"} ]}
And I'd like to apply the keyword function to all values with the key :a to result in:
{:a :1 :b "2" :c [ {:a :3 :b "4"} {:a :5 :b "6"} ]}
Finally, I'd like it to be recursive to an arbitrary depth, and handle the vector case as well.
I've read https://github.com/nathanmarz/specter/wiki/Using-Specter-Recursively , but I must be missing something critical.
Thanks to anyone pointing me in the right direction!
(use '[com.rpl.specter])
(let [input {:a "1" :b "2" :c [{:a "3" :b "4"} {:a "5" :b "6"}]}
desired-output {:a :1 :b "2" :c [{:a :3 :b "4"} {:a :5 :b "6"}]}
FIND-KEYS (recursive-path [] p (cond-path map? (continue-then-stay [MAP-VALS p])
vector? [ALL p]
STAY))]
(clojure.test/is
(= (transform [FIND-KEYS (must :a)] keyword input)
desired-output)))
Not a Specter solution, but it is easily done via clojure.walk/postwalk:
(ns demo.core
(:require
[clojure.walk :as walk] ))
(def data {:a "1" :b "2" :c [{:a "3" :b "4"} {:a #{7 8 9} :b "6"}]})
(def desired {:a :1 :b "2" :c [{:a :3 :b "4"} {:a #{7 8 9} :b "6"}]})
(defn transform
[form]
(if (map-entry? form)
(let [[key val] form]
(if (and
(= :a key)
(string? val))
[key (keyword val)] ; can return either a 2-vector
{key val})) ; or a map here
form))
(walk/postwalk transform data) =>
{:a :1, :b "2", :c [{:a :3, :b "4"} {:a #{7 9 8}, :b "6"}]}
I even put in a non-string for one of the :a values to make it trickier.
My problem is next, i have list of maps, for example:
({:id 1 :request-count 10 ..<another key-value pair>..}
{:id 2 :request-count 15 ..<another key-value pair>..}
...)
Need create map with records in which, key is value of 'id' and value is value of 'request-count', for each map from prev example, like:
{1 10
2 15
...}
I know how to do this. My question is - standard library have function for achieve this? Or maybe i can achiev this with combination few function, without 'reduce'?
Use the juxt function to generate a sequence of pairs, and then toss them into a map:
(into {} (map (juxt :id :request-count) data))
Example:
user=> (def data [{:id 1 :request-count 10 :abc 1}
#_=> {:id 2 :request-count 15 :def 2}
#_=> {:id 3 :request-count 20 :ghi 3}])
#'user/data
user=> (into {} (map (juxt :id :request-count) data))
{1 10, 2 15, 3 20}
Be aware that if there is more than one map in data with the same :id, then the last one encountered by map will be the one that survives in the output map.
I would do it like so:
(def data
[{:id 1 :request-count 10}
{:id 2 :request-count 15}] )
(defn make-id-req-map [map-seq]
(vec (for [curr-map map-seq]
(let [{:keys [id request-count]} curr-map]
{id request-count}))))
With result:
(make-id-req-map data) => [{1 10} {2 15}]
Note: while you could combine the map destructuring into the for statement, I usually like to label the intermediate values as described in Martin Fowler's refactoring "Introduce Explaining Variable".
normally when you print a map, the values are unquoted.
(print {:abc "0" :def "1"}) results in {:abc 0 :def 1}. I would like the output to look like {:abc "0" :def "1"}
I attempted to use the map function to get at every key-value pair and that did not work.
This was my attempt:
(defn print-map [m]
(print "{")
(map #((print (first %) "\"" (second %) "\",")) m)
(print "}\n")
)
nothing from the map gets printed
just use pr/prn instead of print/println, since they generate the string that could be read back by reader, meaning the strings would be quoted:
user=> (prn {:a "10" :b 20 :c "21"})
{:a "10", :b 20, :c "21"}
nil
(print (str {:a "82834"}))
;{:a "82834"}
=> nil
I am writing a piece of code that needs to read in a text file that has data. The text file is in the format:
name 1 4
name 2 4 5
name 3 1 9
I am trying to create a vector of a map in the form [:name Sarah :weight 1 cost :4].
When I try reading the file in with the line-seq reader, it reads each line as an item so the partition is not correct. See repl below:
(let [file-text (line-seq (reader "C://Drugs/myproject/src/myproject/data.txt"))
new-test-items (vec (map #(apply struct item %) (partition 3 file-text)))]
(println file-text)
(println new-test-items))
(sarah 1 1 jason 4 5 nila 3 2 jonas 5 6 judy 8 15 denny 9 14 lis 2 2 )
[{:name sarah 1 1, :weight jason 4 5, :value nila 3 2 } {:name jonas 5 6, :weight judy 8 15, :value denny 9 14}]
I then tried to just take 1 partition, but still the structure is not right.
=> (let [file-text (line-seq (reader "C://Drugs/myproject/src/myproject/data.txt"))
new-test-items (vec (map #(apply struct item %) (partition 1 file-text)))]
(println file-text)
(println new-test-items))
(sarah 1 1 jason 4 5 nila 3 2 jonas 5 6 judy 8 15 denny 9 14 lis 2 2 )
[{:name sarah 1 1, :weight nil, :value nil} {:name jason 4 5, :weight nil, :value nil} {:name nila 3 2 , :weight nil, :value nil} {:name jonas 5 6, :weight nil, :value nil} {:name judy 8 15, :weight nil, :value nil} {:name denny 9 14, :weight nil, :value nil} {:name lis 2 2, :weight nil, :value nil} {:name , :weight nil, :value nil}]
nil
Next I tried to slurp the file, but that is worse:
=> (let [slurp-input (slurp "C://Drugs/myproject/src/myproject/data.txt")
part-items (partition 3 slurp-input)
mapping (vec (map #(apply struct item %) part-items))]
(println slurp-input)
(println part-items)
(println mapping))
sarah 1 1
jason 4 5
nila 3 2
jonas 5 6
judy 8 15
denny 9 14
lis 2 2
((s a r) (a h ) (1 1) (
Please help! This seems like such an easy thing to do in Java, but is killing me in Clojure.
split it into a sequence of lines:
(line-seq (reader "/tmp/data"))
split each of them into a sequence of words
(map #(split % #" ") data)
make a function that takes a vector of one data and turns it into a map with the correct keys
(fn [[name weight cost]]
(hash-map :name name
:weight (Integer/parseInt weight)
:cost (Integer/parseInt cost)))
then nest them back together
(map (fn [[name weight cost]]
(hash-map :name name
:weight (Integer/parseInt weight)
:cost (Integer/parseInt cost)))
(map #(split % #" ") (line-seq (reader "/tmp/data"))))
({:weight 1, :name "name", :cost 4}
{:weight 2, :name "name", :cost 4}
{:weight 3, :name "name", :cost 1})
you can also make this more compact by using zip-map
You are trying to do everything in one place without testing intermediate results. Instead Clojure recommends to decompose task into a number of subtasks - this makes code much more flexible and testable. Here's the code for your task (I assume records in file describe people):
(defn read-lines [filename]
(with-open [rdr (clojure.java.io/reader filename)]
(doall (line-seq rdr))))
(defn make-person [s]
(reduce conj (map hash-map [:name :weight :value] (.split s " "))))
(map make-person (read-lines "/path/to/file"))
Given:
(def my-vec [{:id 0 :a "foo" :b "bar"} {:id 1 :a "baz" :b "spam"}
{:id 2 :a "qux" :b "fred"}])
How can I idiomatically update * the item in my-vec with :id=1 to have values :a="baz2" and :b="spam2"?
*: I recognize that I wouldn't actually be updating my-vec, but really returning a new vector that is identical to my-vec except for the replacement values.
Do you know ahead of time that the map with id == 1 is the second map in your vector? If so:
user> (-> my-vec
(assoc-in [1 :a] "baz2")
(assoc-in [1 :b] "spam2"))
[{:id 0, :a "foo", :b "bar"} {:id 1, :a "baz2", :b "spam2"} {:id 2, :a "qux", :b "fred"}]
If you need to access your data by id a lot, another idea is to replace your vector of hash-maps with a hash-map of hash-maps keyed on :id. Then you can more easily assoc-in no matter the order of things.
user> (def new-my-vec (zipmap (map :id my-vec) my-vec))
#'user/new-my-vec
user> new-my-vec
{2 {:id 2, :a "qux", :b "fred"}, 1 {:id 1, :a "baz", :b "spam"}, 0 {:id 0, :a "foo", :b "bar"}}
user> (-> new-my-vec
(assoc-in [1 :a] "baz2")
(assoc-in [1 :b] "spam2"))
{2 {:id 2, :a "qux", :b "fred"}, 1 {:id 1, :a "baz2", :b "spam2"}, 0 {:id 0, :a "foo", :b "bar"}}
map a function over the vector of maps that either creates a modified map if the key matches or uses the original if the keys don't match then turn the result back into a vector
(vec (map #(if (= (:id %) 1)
(assoc % :a "baz2" :b "spam2")
%)))
It is possible to do this more succinctly though this one really shows where the structural sharing occurs.
Might want to take a look at array-map which creates a map backed by an array and keyed by the index instead of using :id?