How would I go about adding values from a tab delimited string to a plist?
(dolist (x *lines*)
(cl-ppcre:split "\t" x))
*lines* is a list of tab delimited strings loaded from a file, and I want to make a plist of the form
(:a value1 :b value2 :c value 3)
Thanks!
(let ((line '("foo" "bar" "baz")))
(loop for item in line and key in '(:a :b :c) collect key collect item))
=> (:A "foo" :B "bar" :C "baz")
(mapcan 'list '(:a :b :c) '("foo" "bar" "baz"))
=> (:A "foo" :B "bar" :C "baz")
You should read the lines from the file, CL-PPCRE:SPLIT them to get the list, and step through this list:
(loop
for (key value) on (cl-ppcre:split " " "a value1 b value2 c value3") by #'cddr
appending (list (intern (string-upcase key) (find-package :keyword))
value))
Related
I've got a nested plist structure, for example:
(:title "A title"
:repeat (:row #(:a :b :c)
:column #(:c :a :b))
:spec (:data my-data
:late t))
and I need to set :data to a different value. The challange is that this key may appear anywhere in the tree, possibly even deeper in the tree than this example. It will only appear once. I know about the access library, but can't use it. I can find the key easy enough using a recursive search:
(defun find-in-tree (item tree &key (test #'eql))
(labels ((find-in-tree-aux (tree)
(cond ((funcall test item tree)
(return-from find-in-tree tree))
((consp tree)
(find-in-tree-aux (car tree))
(find-in-tree-aux (cdr tree))))))
(find-in-tree-aux tree)))
But I can't quite work out if there's any way to get the place when it's nested in the tree. Ideally something like:
(setf (find-place-in-tree :data tree) 'foo)
is what I'm after.
Any ideas?
I could not work out your recursive searcher so I wrote a simpler one, which also solves the 'item is present but value is nil' in the usual way:
(defun find-in-tree (item tree &key (test #'eql))
;; really just use iterate here
(labels ((fit-loop (tail)
(cond
((null tail)
;; not there
(return-from find-in-tree (values nil nil)))
((null (rest tail))
;; not a plist
(error "botched plist"))
(t
(destructuring-bind (this val . more) tail
(cond
((funcall test this item)
;; gotit
(return-from find-in-tree (values val t)))
((consp val)
;; Search in the value if it's a list
(fit-loop val)
(fit-loop more))
(t
;; just keep down this list
(fit-loop more))))))))
(fit-loop tree)))
Given that the setf function is essentially trivial if you don't want it to add entries (which it can not always do anyway):
(defun (setf find-in-tree) (new item tree &key (test #'eql))
;; really just use iterate here
(labels ((fit-loop (tail)
(cond
((null tail)
(error "not in tree"))
((null (rest tail))
(error "botched plist"))
(t
(destructuring-bind (this val . more) tail
(cond
((funcall test this item)
(return-from find-in-tree
(car (setf (cdr tail) (cons new more)))))
((consp val)
(fit-loop val)
(fit-loop more))
(t
(fit-loop more))))))))
(fit-loop tree)))
This is not exactly a setf-able tree.
But the construction of a setf-like in-place mutation macro for nested plists - even for the case that the key of a nested plist occurs in more than one places.
The plist-setf constructs paths to the desired key within the nested plist. And replaces the current value to the new-value.
Within a path, a symbol should not occur twice. Otherwise there will be severe errors.
(defun plistp (l)
"Is `l` a plist?"
(loop for (k v) on l by #'cddr
always (symbolp k)))
(defun get-plist-paths (plist key &optional (acc '()))
"Which paths are in a nested plist for reaching key?"
(loop for (k v) on plist by #'cddr
nconcing (if (eq key k)
(list (reverse (cons key acc)))
(if (plistp v)
(get-plist-paths v key (cons k acc))
nil))))
(defun staple (plist plist-path)
"Given a plist-path, generate code to getf to this path."
(let ((res (list 'getf plist (car plist-path))))
(loop for s in (cdr plist-path)
do (setf res (cons 'getf (cons res (list s))))
finally (return res))))
(defun construct-call (plist plist-path new-value)
"Add to the generated code a `(setf ... new-value)."
`(setf ,(staple plist plist-path) ,new-value))
(defun construct-entire-call (plist-symbol plist key new-value)
"Generate the entire code for the macro."
(let ((plist-paths (get-plist-paths plist key)))
(cons 'progn
(loop for pp in plist-paths
collect (construct-call plist-symbol pp new-value)))))
(defmacro %plist-setf (plist key new-value)
"A macro to make the input of construct-entire-call more uniform."
`(construct-entire-call ',plist ,plist ,key ,new-value))
(defmacro plist-setf (plist key new-value)
"Automated setf of a key in a nested plist to set the location to the new-value."
(eval `(%plist-setf ,plist ,key ,new-value)))
;; the `eval` is needed to have an extra evaluation step here.
;; I am happy if someone can suggest a better alternative.
;; Or if someone can falsify its correctness here.
Some of the functions can be "explained" by some examples:
(defparameter *pl* (list :points 5 :a (list :b 1 :c (list :d 0 :e 1) :f 2)))
(defparameter *pl1* (list :points 5 :a (list :b 1 :c (list :d 0 :e 1) :f 2 :e 3 :g (list :h 1 :e 1))))
(get-plist-path *pl1* :e)
;; => ((:A :C :E) (:A :E) (:A :G :E))
(construct-entire-call '*pl1* *pl1* :e 3)
;; (PROGN
;; (SETF (GETF (GETF (GETF *PL1* :A) :C) :E) 3)
;; (SETF (GETF (GETF *PL1* :A) :E) 3)
;; (SETF (GETF (GETF (GETF *PL1* :A) :G) :E) 3))
(%plist-setf *pl1* :e 3)
;; (PROGN
;; (SETF (GETF (GETF (GETF *PL1* :A) :C) :E) 3)
;; (SETF (GETF (GETF *PL1* :A) :E) 3)
;; (SETF (GETF (GETF (GETF *PL1* :A) :G) :E) 3))
Usage:
(defparameter *pl1* (list :points 5 :a (list :b 1 :c (list :d 0 :e 1) :f 2 :e 3 :g (list :h 1 :e 1))))
(macroexpand-1 '(plist-setf *pl1* :e 3))
;; (PROGN
;; (SETF (GETF (GETF (GETF *PL1* :A) :C) :E) 3)
;; (SETF (GETF (GETF *PL1* :A) :E) 3)
;; (SETF (GETF (GETF (GETF *PL1* :A) :G) :E) 3)) ;
;; T
*pl1*
;; => (:POINTS 5 :A (:B 1 :C (:D 0 :E 1) :F 2 :E 3 :G (:H 1 :E 1)))
;; after
(plist-setf *pl1* :e 3)
*pl1*
;; => (:POINTS 5 :A (:B 1 :C (:D 0 :E 3) :F 2 :E 3 :G (:H 1 :E 3)))
Or also:
(defparameter *pl* (list :points 5 :a (list :b 1 :c (list :d 0 :e 1) :f 2)))
(macroexpand-1 '(plist-setf *pl* :e 3))
;; (PROGN (SETF (GETF (GETF (GETF *PL* :A) :C) :E) 3)) ;
;; T
*pl*
;; => (:POINTS 5 :A (:B 1 :C (:D 0 :E 1) :F 2))
(plist-setf *pl* :e 3)
*pl*
;; => (:POINTS 5 :A (:B 1 :C (:D 0 :E 3) :F 2))
I have a vector of hash-maps, like this:
(def my-maps [{:a 1} {:b 2}])
I want to loop over each hash-map, give the key and value a more meaningful name within the loop, then process each hash-map differently depending on its key.
Without further ado, here is my best attempt:
(for [m my-maps]
(let [my-key-name (key m) my-val-name (val m)]
(case my-key-name
:a (println "Found key :a with value " my-val-name)
:b (println "Found key :b with value " my-val-name))))
This approach, however, produces a rather cryptic error:
; Error printing return value (ClassCastException) at clojure.core/key (core.clj:1569).
; class clojure.lang.PersistentArrayMap cannot be cast to class java.util.Map$Entry (clojure.lang.PersistentArrayMap is in unnamed module of loader 'app'; java.util.Map$Entry is in module java.base of loader 'bootstrap')
What am I doing wrong?
You can destructure inside for (or use doseq):
(for [[[k v] & _] [{:a 1} {:b 2}]]
(println "Found key" k "with value" v))
Found key :a with value 1
Found key :b with value 2
=> (nil nil)
For the sake of clarity, here is a more general answer broken down into individual steps:
(let [my-maps [{:a 1} {:b 2 :c 3}]]
(doseq [curr-map my-maps]
(newline)
(println "curr-map=" curr-map)
(let [map-entries (seq curr-map)]
(println "map-entries=" map-entries)
(doseq [curr-me map-entries]
(let [[k v] curr-me]
(println " curr-me=" curr-me " k=" k " v=" v))))))
With result
curr-map= {:a 1}
map-entries= ([:a 1])
curr-me= [:a 1] k= :a v= 1
curr-map= {:b 2, :c 3}
map-entries= ([:b 2] [:c 3])
curr-me= [:b 2] k= :b v= 2
curr-me= [:c 3] k= :c v= 3
A MapEntry object in Clojure can be treated as either a 2-element vector (accessed via first & second) or as a MapEntry accessed via the key and val functions. The destructuring form:
(let [[k v] curr-me]
treats the MapEntry object curr-me as a sequence and pulls out the first 2 elements into k and v. Even though it prints like a vector (eg [:a 1]), it does have the type clojure.lang.MapEntry.
The destructuring syntax & _ in the for expression of the original answer is a "rest args" destructuring. It causes the sequence of all MapEntry objects after the first one to be assigned to the variable _, which is then ignored in the rest of the code.
How does one create a map from a var in Clojure?
For example, if one has a var called 'var', as follows:
(def var "I am a var")
And then, one wants to convert it to the following:
{:var "I am a var"}
How does one achieve that?
By the way, just to be clear:
I want the f from (f var) where 'f' converts var to {:var "I am a var"}.
If you want to create a map from the var name to its value you can create a macro:
(defmacro to-map [sym]
`{(keyword '~sym) ~sym})
(to-map var)
=> {:var "I am a var"}
FYI, this macro can take optional args to construct a map.
(defmacro to-map [& vs]
"foo = 1, bar = 2. (to-map foo bar) ==> {:foo 1 :bar 2}"
`(let [ks# (map keyword '~vs)]
(zipmap ks# [~#vs])))
maybe you can do if using the var's metadata:
(defn process-var [param]
{(-> param meta :name keyword)
#param})
in repl:
user> (def x "fourty two")
#'user/x
user> (process-var #'x)
{:x "fourty two"}
user> (def y 1001)
#'user/y
user> (map (comp process-var (ns-interns *ns*)) '(x y))
({:x "fourty two"} {:y 1001})
user> (map (comp process-var (ns-interns *ns*)) '(x y process-var))
({:x "fourty two"} {:y 1001} {:process-var #function[user/process-var]})
also you can take a var straight from namespace's symbols table:
(defn by-name
([name-symbol] (by-name name-symbol *ns*))
([name-symbol ns]
{(keyword name-symbol)
(deref (ns-resolve ns name-symbol))}))
user> (by-name 'x)
{:x "fourty two"}
user> (by-name 'map 'clojure.core)
{:map #function[clojure.core/map]}
user> (by-name '*clojure-version* 'clojure.core)
{:*clojure-version* {:major 1, :minor 8, :incremental 0, :qualifier nil}}
I am looking for a function similar to those in clojure.walk that have an inner function that takes as argument :
not a key and a value, as is the case with the clojure.walk/walk function
but the vector of keys necessary to access a value from the top-level data structure.
recursively traverses all data
Example :
;; not good since it takes `[k v]` as argument instead of `[path v]`, and is not recursive.
user=> (clojure.walk/walk (fn [[k v]] [k (* 10 v)]) identity {:a 1 :b {:c 2}})
;; {:a 10, :c 30, :b 20}
;; it should receive as arguments instead :
[[:a] 1]
[[:b :c] 2]
Note:
It should work with arrays too, using the keys 0, 1, 2... (just like in get-in).
I don't really care about the outer parameter, if that allows to simplify the code.
Currently learning clojure, I tried this as an exercise.
I however found it quite tricky to implement it directly as a walk down the tree that applies the inner function as it goes.
To achieve the result you are looking for, I split the task in 2:
First transform the nested structure into a dictionary with the path as key, and the value,
Then map the inner function over, or reduce with the outer function.
My implementation:
;; Helper function to have vector's indexes work like for get-in
(defn- to-indexed-seqs [coll]
(if (map? coll)
coll
(map vector (range) coll)))
;; Flattening the tree to a dict of (path, value) pairs that I can map over
;; user> (flatten-path [] {:a {:k1 1 :k2 2} :b [1 2 3]})
;; {[:a :k1] 1, [:a :k2] 2, [:b 0] 1, [:b 1] 2, [:b 2] 3}
(defn- flatten-path [path step]
(if (coll? step)
(->> step
to-indexed-seqs
(map (fn [[k v]] (flatten-path (conj path k) v)))
(into {}))
[path step]))
;; Some final glue
(defn path-walk [f coll]
(->> coll
(flatten-path [])
(map #(apply f %))))
;; user> (println (clojure.string/join "\n" (path-walk #(str %1 " - " %2) {:a {:k1 1 :k2 2} :b [1 2 3]})))
;; [:a :k1] - 1
;; [:a :k2] - 2
;; [:b 0] - 1
;; [:b 1] - 2
;; [:b 2] - 3
It turns out that Stuart Halloway published a gist that could be of some use (it uses a protocol, which makes it extensible as well) :
(ns user)
(def app
"Intenal Helper"
(fnil conj []))
(defprotocol PathSeq
(path-seq* [form path] "Helper for path-seq"))
(extend-protocol PathSeq
java.util.List
(path-seq*
[form path]
(->> (map-indexed
(fn [idx item]
(path-seq* item (app path idx)))
form)
(mapcat identity)))
java.util.Map
(path-seq*
[form path]
(->> (map
(fn [[k v]]
(path-seq* v (app path k)))
form)
(mapcat identity)))
java.util.Set
(path-seq*
[form path]
(->> (map
(fn [v]
(path-seq* v (app path v)))
form)
(mapcat identity)))
java.lang.Object
(path-seq* [form path] [[form path]])
nil
(path-seq* [_ path] [[nil path]]))
(defn path-seq
"Returns a sequence of paths into a form, and the elements found at
those paths. Each item in the sequence is a map with :path
and :form keys. Paths are built based on collection type: lists
by position, maps by key, and sets by value, e.g.
(path-seq [:a [:b :c] {:d :e} #{:f}])
({:path [0], :form :a}
{:path [1 0], :form :b}
{:path [1 1], :form :c}
{:path [2 :d], :form :e}
{:path [3 :f], :form :f})
"
[form]
(map
#(let [[form path] %]
{:path path :form form})
(path-seq* form nil)))
(comment
(path-seq [:a [:b :c] {:d :e} #{:f}])
;; finding nils hiding in data structures:
(->> (path-seq [:a [:b nil] {:d :e} #{:f}])
(filter (comp nil? :form)))
;; finding a nil hiding in a Datomic transaction
(->> (path-seq {:db/id 100
:friends [{:firstName "John"}
{:firstName nil}]})
(filter (comp nil? :form)))
)
Note : in my case I could also have used Specter, so if you are reading this, you may want to check it out as well.
There is also https://github.com/levand/contextual/
(def node (:b (first (root :a))))
(= node {:c 1}) ;; => true
(c/context node) ;; => [:a 0 :b]
I'm trying to apply a function to all elements in a map that match a certain key.
(def mymap {:a "a" :b "b" :c "c"})
(update-in mymap [:a :b] #(str "X-" %))
I'm expecting
{:a "X-a", :c "c", :b "X-b"}
But I get
ClassCastException java.lang.String cannot be cast to clojure.lang.Associative clojure.lang.RT.assoc (RT.java:702)
Anyone can help me with this?
update-in is to update a single key in the map (at a particular nesting level, [:a :b] means update key :b inside the map value of key :a.
What you want can be done using reduce:
(reduce #(assoc %1 %2 (str "X-" (%1 %2)))
mymap
[:a :b])
Here's a generalized function:
(defn update-each
"Updates each keyword listed in ks on associative structure m using fn."
[m ks fn]
(reduce #(update-in %1 [%2] fn) m ks))
(update-each mymap [:a :b] #(str "X-" %))
In the solution below, the haspmap if first filtered, then it is mapped to the str function, and then merged with the original hashmap -
(def m {:a "a" :b "b" :c "c"})
(def keys #{:a :b})
(->> m
(filter (fn [[k v]] (k keys)))
(map (fn [[k v]] [k (str "X-" v)]))
(into {})
(merge m))