Equivalent of Clojure's “assoc-in” and “get-in” in Common lisp - clojure

In Clojure you can update a map (dict) with assoc-in and create key path automatically if it don't exist.
(assoc-in {:a 1 :b 3} [:c :d] 33)
; {:a 1, :c {:d 33}, :b 3}
Same for get-in: you can specify a path of keys (or list indices) and it will return the value specified by the path, nil if it does not exist.
(get-in {:a 1, :c {:d 33}, :b 3} [:c :d])
; 33
(get-in {:a 1, :c {:d 33}, :b 3} [:c :e])
; nil
I like to have the sugar in Common lisp,so I monkeyed a 'assoc-in' and I tested it on a trie I made out of list structure, I leave ':leaf' preserved, so the result of 'get-in' is always list:
test case:
(setf trie '(:A (:B (:leaf t) :C (:leaf t)) :D (:leaf t)))
get-in implementation and test:
(defmacro get-in (trie ks) `(reduce #'getf ,ks :initial-value ,trie))
(get-in trie '(:a :b)) ; (:leaf t)
(get-in trie '(:a :b :leaf)) ; t
assoc-in implementation and test:
(defmacro assoc-in (trie ks v)
`(setf (getf (get-in ,trie ,ks) :leaf) ,v))
(assoc-in trie '(:a :b) 99)
(get-in trie '(:a :b)) ; (:leaf 99)
(assoc-in trie '(:a :b :new-key) "new-key") ; (SETF REDUCE) is not fbound
I have trouble with 'assoc-in', I can update the trie but can't insert
Any advice is welcomed, doesn't have to be macro. I looked up Clojure implementation and tried to do it in Common lisp, also failed.

Here's how I'd do this. The docstrings and comments in the code explain what's happening. First a utility function:
(defun make-nested-alist (value items)
"Returns a nested association list that takes the first item
to an association list that takes the second item to an
association list, and so on, finally to the value. If items
is the empty list, then the value is returned by itself."
(reduce (lambda (item value)
(acons item value '()))
items :initial-value value
:from-end t))
(make-nested-alist 3 '())
;=> 3
(make-nested-alist 3 '(a b c))
;;=> ((a . ((b . ((c . e))))))
Now, a function that will retrieve values from a nested association list.
(defun assoc* (items alist)
"Finds the item in the nested association list keyed by the items.
It is an error if a prefix of the path leads to a value that cannot be
interpreted as an associate list."
(if (endp items) alist
(assoc* (rest items)
(cdr (assoc (first items) alist)))))
(defparameter *alist*
(copy-tree '((a . 1)
(b . 3)
(c . ((d . 33))))))
(assoc* '(a) *alist*) ;=> 1
(assoc* '(c d) *alist*) ;=> 33
(assoc* '(c e) *alist*) ;=> NIL
(assoc* '(a b) *alist*) ; ERROR (since the prefix (a) leads to 1)
Now, a function to "update" a nested association list. Note that this will update most association lists in place, but since you can't modify an empty list in place, you need to use the return value from this function.
(defun assoc*-update (value items alist)
"Returns a nested association list like the provided one, except
that the value at the path specified by items contains value. This
will modify the association list if the any prefix of the path is a
value in the association list. Because the result may be a new
list (e.g., when the original association list does not have a top
level entry for the initial item in the path), the result should be
used."
(if (endp items)
value
(let ((entry (assoc (first items) alist)))
(if (null entry)
;; if there is no entry at all, then we need to make a
;; nested association list out of the rest keys that
;; eventually comes back to the value, and tack it onto
;; the current alist. We can't modify alist, because alist
;; might be empty, and we can't modify the empty list.
(acons (first items) (make-nested-alist value (rest items)) alist)
;; Otherwise, there is an entry, and that takes care of the
;; first key, but we'll need to recurse into the value and
;; update it. If there are no keys left, then we should just
;; replace this entry, otherwise we need to recurse into it.
;; In both cases, we return alist.
(prog1 alist
(rplacd entry (assoc*-update value (rest items) (cdr entry))))))))
(let ((alist (copy-tree *alist*)))
(setf alist (assoc*-update 42 '(c e) alist))
(print alist)
(setf alist (assoc*-update 89 '(d) alist))
(print alist))
;=> ((A . 1) (B . 3) (C (E . 42) (D . 33)))
;=> ((D . 89) (A . 1) (B . 3) (C (E . 42) (D . 33)))

Since there are 95 times view, here I post my solution.
(defvar *list-trie* '(:A (:B (:leaf t) :C (:leaf t)) :D (:leaf t)))
(defun get-in-list (trie ks)
"input a key chain list, return children of common ancestors, return nil of not exist"
(reduce #'(lambda (node k) (and (listp node) (getf node k))) ks :initial-value trie))
(defmacro clojure-assoc-list (trie k v)
"return updated list"
`(progn (setf (getf ,trie ,k) ,v)
,trie))
(defun assoc-in-list (trie ks v)
(if (= 1 (length ks))
(clojure-assoc-list trie (car ks) v)
(clojure-assoc-list trie (car ks) (assoc-in-list (getf trie (car ks)) (cdr ks) v))))

Related

Elisp: how to find list duplicates

I am using this to find list duplicates:
(defun have-dups (x)
(let ((dups (copy-tree x)))
(if (eq (length (delete-dups dups)) (length x))
nil
t)))
(have-dups (list 1 2 3 3)) ;=> t
(have-dups (list 1 2 3)) ;=> nil
Considering the overhead of copy-tree and delete-dups, probably there is a better way.
Use a hash table, as soon as you find an element which already exists in the hash table, you know you have duplicates:
(defun has-dup (list)
(block nil
(let ((hash (make-hash-table :test 'eql)))
(map ()
(lambda (item)
(if (gethash item hash)
(return t)
(setf (gethash item hash) t)))
list))))
Here is a shorter version of your answer, which uses remove-duplicates instead of delete-dups, to avoid the destructive properties of the latter:
(defun has-dups-p (LIST) ""
(let ((unique1 (remove-duplicates LIST :test #'equal)))
(if (eq LIST unique1)
nil
t)))
(has-dups '(1 2 3 2 1)) ; t
(has-dups '("a" "b" "c")) ; nil
I find this reasonably easy to read and eq should be reasonably efficient as it goes straight to C, particularly where a duplicate occurs early in the list. Both remove-duplicates and delete-dups are passed to cl--delete-duplicates which is rather involved...
A longer solution follows, which returns the elements of LIST which have duplicates and the position of each duplicated element in LIST (remembering that seqs are zero-indexed in elisp). Note that this currently only applies where the elements of LIST are strings, although I'm sure it could be extended further to more general cases:
(defun list-duplicates (LIST) "
Returns `nil' when LIST has no duplicates.
Otherise, returns a `list' of `cons's.
In each list element:
- the `car' is the element of LIST which has duplicates.
- the `cdr' is a list of the positions where the duplicates are found."
(interactive)
;; res1 = result
;; unique1 = LIST with duplicates removed
(let ((unique1 (remove-duplicates LIST :test #'string-equal))
(res1 '()))
(if (eq LIST unique1)
nil
(progn
(dolist (x unique1)
;; i = incrementor
;; pos1 = list of postions of duplicates
(let (y (i 0) (pos1 '()))
(while (member x LIST)
(set 'y (seq-position LIST x))
(when (> i 0)
(push y pos1))
(set 'i (+ 1 i))
(set 'LIST
(substitute (concat x "1") x LIST :test #'string-equal :count 1)))
(push (cons x (nreverse pos1)) res1)))
(nreverse res1)))))
e.g.
(list-duplicates '("a" "b" "c")) ; nil
(list-duplicates '("a" "b" "b" "a" "b" "c" "c")) ; (("a" 3) ("b" 2 4) ("c" 6))

get-in for lists

Apparently get-in doesn't work for '() lists since they're not an associative data structure. This makes sense for the API and from the perspective of performance of large lists. From my perspective as a user it'd be great to still use this function to explore some small test data in the repl. For example I want to be able to:
(-> '({:a ("zero" 0)} {:a ("one" 1)} {:a ("two" 2)})
(get-in [1 :a 0]))
=> "one"
Is there some other function that works this way? Is there some other way to achieve this behavior that doesn't involve converting all my lists to (say) vectors?
This does what you ask:
(defn get-nth-in [init ks]
(reduce
(fn [a k]
(if (associative? a)
(get a k)
(nth a k)))
init
ks))
For example,
(-> '({:a "zero"} {:a "one"} {:a "two"})
(get-nth-in [1 :a]))
;"one"
and
(-> '({:a ("zero" 0)} {:a ("one" 1)} {:a ("two" 2)})
(get-nth-in [1 :a 0]))
;"one"
The extra 's you have get expanded into (quote ...):
(-> '({:a '("zero" 0)} {:a '("one" 1)} {:a '("two" 2)})
(get-nth-in [1 :a 0]))
;quote
Not what you intended, I think.
A post just yesterday had a problem regarding lazy lists and lazy maps (from clojure/data.xml). One answer was to just replace the lazy bits with plain vectors & maps using this function:
(defn unlazy
[coll]
(let [unlazy-item (fn [item]
(cond
(sequential? item) (vec item)
(map? item) (into {} item)
:else item))
result (postwalk unlazy-item coll)
]
result ))
Since the resulting data structure uses only vectors & maps, it works for your example with get-in:
(let [l2 '({:a ("zero" 0)} {:a ("one" 1)} {:a ("two" 2)})
e2 (unlazy l2) ]
(is= l2 e2)
(is= "one" (get-in e2 [1 :a 0] l2))
)
You can find the unlazy function in the Tupelo library.
The first param for get-in should be a map.
You have to figure out the feature of your sequence, use last, first, filter or some e.g. to get the element first
for example you could use (:a (last data))

Clojure - sort function

I am trying to write a recursive sort function that sorts a list from low to high (duh). I am currently getting output, just not the correct output. Here is my code:
(defn sort/predicate [pred loi]
(if (empty? loi)
()
(if (= (count loi) 1)
(cons (first loi) (sort pred (rest loi)))
(if (pred (first loi) (first (rest loi)))
(cons (first loi) (sort pred (rest loi)))
(if (pred (first (rest loi)) (first loi))
(cons (first (rest loi)) (sort pred (cons (first loi) (rest (rest loi)))))
(cons (first loi) (sort pred (rest loi))))))))
Basically, I compare the first two elements in the list and, if the first element is smaller I cons it with the result of comparing the next two elements of the list. If the second element of the list is smaller, I cons the second element with the result of sorting the first two elements of the cons of the first element and everything after the second element (sorry if that's hard to follow). Then, when there is only one element left in the list, I throw it on the end and return it. However, there is a bug along the way somewhere because I should get the following:
>(sort/predicate < '(8 2 5 2 3))
(2 2 3 5 8)
but instead, I get:
>(sort/predicate < '(8 2 5 2 3))
(2 5 2 3 8)
I'm pretty new to clojure, so any help is greatly appreciated. Also, I would like to keep my code roughly the same (I don't want to use a sorting function that already exists). Thanks
I don't think this is a very efficient way to sort, but I tried to stay true to your intention:
(defn my-sort [cmp-fn [x & xs]]
(cond
(nil? x) '()
(empty? xs) (list x)
:else (let [[y & ys :as s] (my-sort cmp-fn xs)]
(if (cmp-fn x y)
(cons x s)
(cons y (my-sort cmp-fn (cons x ys)))))))
;; merge sort implementation - recursive sort without stack consuming
(defn merge-sort
([v comp-fn]
(if (< (count v) 2) v
(let [[left right] (split-at (quot (count v) 2) v)]
(loop [result []
sorted-left (merge-sort left comp-fn)
sorted-right (merge-sort right comp-fn)]
(cond
(empty? sorted-left) (into result sorted-right)
(empty? sorted-right) (into result sorted-left)
:else (if (comp-fn 0 (compare (first sorted-left) (first sorted-right)))
(recur (conj result (first sorted-left)) (rest sorted-left) sorted-right)
(recur (conj result (first sorted-right)) sorted-left (rest sorted-right))))))))
([v] (merge-sort v >)))
clojure.core/sort implement by Java more general.
user=> (sort '(8 2 5 2 3))
(2 2 3 5 8)
user=> (sort > '(8 2 5 2 3))
(8 5 3 2 2)
user=> (source sort)
(defn sort
"Returns a sorted sequence of the items in coll. If no comparator is
supplied, uses compare. comparator must implement
java.util.Comparator. If coll is a Java array, it will be modified.
To avoid this, sort a copy of the array."
{:added "1.0"
:static true}
([coll]
(sort compare coll))
([^java.util.Comparator comp coll]
(if (seq coll)
(let [a (to-array coll)]
(. java.util.Arrays (sort a comp))
(seq a))
())))
nil
user=>

Clojure Multi Maps

Very simple + silly question:
Does clojure provide multi maps? I currently have something like this:
(defn wrap [func]
(fn [mp x]
(let [k (func x)]
(assoc mp k
(match (get mp k)
nil [x]
v (cons v x))))))
(defn create-mm [func lst]
(reduce (wrap func) {} lst))
Which ends up creating a map, where for each key, we have a vector of all elements with that key. However, it seems like multi maps is a very basic data structure, and I wonder if clojure has it built-in.
Thanks
I don't think this is really necessary as a distinct type, as Clojure's flexibility allow you to quickly make your own by just using maps and sets. See here:
http://paste.lisp.org/display/89840
Edit (I should have just pasted this in since it's so small)
Example Code (Courtesy Stuart Sierra)
(ns #^{:doc "A multimap is a map that permits multiple values for each
key. In Clojure we can represent a multimap as a map with sets as
values."}
multimap
(:use [clojure.set :only (union)]))
(defn add
"Adds key-value pairs the multimap."
([mm k v]
(assoc mm k (conj (get mm k #{}) v)))
([mm k v & kvs]
(apply add (add mm k v) kvs)))
(defn del
"Removes key-value pairs from the multimap."
([mm k v]
(let [mmv (disj (get mm k) v)]
(if (seq mmv)
(assoc mm k mmv)
(dissoc mm k))))
([mm k v & kvs]
(apply del (del mm k v) kvs)))
(defn mm-merge
"Merges the multimaps, taking the union of values."
[& mms]
(apply (partial merge-with union) mms))
(comment
(def mm (add {} :foo 1 :foo 2 :foo 3))
;; mm == {:foo #{1 2 3}}
(mm-merge mm (add {} :foo 4 :bar 2))
;;=> {:bar #{2}, :foo #{1 2 3 4}}
(del mm :foo 2)
;;=> {:foo #{1 3}}
)
Extra test for the case pointed out in the comments:
(comment
(-> {} (add :a 1) (del :a 1) (contains? :a))
;;=> false
)

What is idiomatic Clojure to "remove" a single instance from many in a list?

I have a list, which may contain elements that will compare as equal. I would like a similar list, but with one element removed. So from (:a :b :c :b :d) I would like to be able to "remove" just one :b to get (:a :c :b :d).
The context is a hand in a card game where two decks of standard cards are in play, so there may be duplicate cards but still played one at a time.
I have working code, see below. Are there more idiomatic ways to do this in Clojure?
(defn remove-one [c left right]
(if (= right ())
left
(if (= c (first right))
(concat (reverse left) (rest right))
(remove-one c (cons (first right) left) (rest right)))))
(defn remove-card [c cards]
(remove-one c () cards))
Here are the Scala answers I got a while ago: What is an idiomatic Scala way to "remove" one element from an immutable List?
How about:
(let [[n m] (split-with (partial not= :b) [:a :b :c :b :d])] (concat n (rest m)))
Which splits the list at :b and then removes the :b and concats the two lists.
I usually solve these problems with a higher-order function like split-with, but someone's already done that. Sometimes it's more readable or more efficient to work at a more primitive level, so here's a better version of your original looping code, using lazy sequences and generalized to take a predicate for removal instead of being constrained to equality checks:
(defn remove-once [pred coll]
((fn inner [coll]
(lazy-seq
(when-let [[x & xs] (seq coll)]
(if (pred x)
xs
(cons x (inner xs))))))
coll))
user> (remove-once #{:b} [:a :b :c :b :d])
(:a :c :b :d)
It is surprising there is not a high-level API to do something like this. Here is another version similar to #amalloy and #James that uses recur in order not to stack overflow.
(defn remove-once [x c]
(letfn [(rmv [x c1 c2 b]
(if-let [[v & c] (seq c1)]
(if (and (= x v) b)
(recur x c c2 false)
(recur x c (cons v c2) b))
c2))]
(lazy-seq (reverse (rmv x c '() true)))))
(remove-once :b [:a :b :c :b :d])
;; (:a :c :b :d)