Reputation: 517
I do the following code to retrieve only the keys of a plist:
(loop :for (key nil) :on config :by #'cddr
:collect key))
Running this produces:
CONFIG-TEST> (loop :for (key nil) :on '(:foo 1 :bar 2) :by #'cddr
:collect key)
(:FOO :BAR)
Is there a more 'functional' way to do this than using LOOP?
Upvotes: 3
Views: 1395
Reputation: 11
(defun plist-keys (plist)
"Get just the keys of a plist."
(loop for key in plist by #'cddr collecting key))
(defun plist-values (plist)
"Get just the values of a plist."
(loop for value in (rest plist) by #'cddr collecting value))
Upvotes: 1
Reputation: 18395
With the Serapeum library, which I consider as a second must-have just after Alexandria: use plist-keys
:)
(serapeum:plist-keys '(:a 1 :b 2))
;; (:A :B)
https://github.com/ruricolist/serapeum/blob/master/REFERENCE.md#plist-keys-plist
Here's its implementation:
(defun plist-keys (plist)
"Return the keys of a plist."
(collecting*
(doplist (k v plist)
(collect k))))
It also has plist-values
.
Upvotes: 3
Reputation: 9965
(ql:quickload :alexandria)
(mapcar #'car (alexandria:plist-alist '(:a 1 :b 2)))
;; => (:A :B)
To remove dependency of alexandria, define yourself plist-alist
:
(defun plist-alist (l &optional (acc '()))
(cond ((null l) (nreverse acc))
(t (plist-alist (cddr l) (cons (cons (car l) (cadr l)) acc)))))
However, dependency on :alexandria
should not be counted as dependency.
Actyally, one could change plist-alist
definition to obtain only the keys:
(defun plist-keys (l &optional (acc '()))
(cond ((null l) (nreverse acc))
(t (plist-keys (cddr l) (cons (car l) acc)))))
And likewise the values:
(defun plist-vals (l &optional (acc '()))
(cond ((null l) (nreverse acc))
(t (plist-vals (cddr l) (cons (cadr l) acc)))))
Upvotes: 1
Reputation: 38924
Using the SERIES package, scan-plist
returns two series, one for the keys, the other for values:
(scan-plist '(:a 3 :b 2))
=> #Z(:A :B)
#Z(3 2)
You can rely on this to collect the first series as a list:
(collect 'list (scan-plist '(:a 3 :b 2)))
More generally, you may want to process the values in some way, so you would use mapping
. For example, here is a plist-alist
made with SERIES:
(defun plist-alist (plist)
(collect 'list
(mapping (((k v) (scan-plist plist))) (cons k v))))
Upvotes: 5
Reputation: 9965
If you are sure that none of the values are of type symbol, you could filter for symbols:
(remove-if-not #'symbolp '(:a 1 :b 2)) ;;=> (:A :B)
Much less efficient, but universersal:
symbolp
and getf
-ability
(Only keys of a plist are getf
-able from the plist, thus this is the check whether it is a key or not. However, a check, whether an element in a plist is symbolp
is cheaper and removes most of the non-key values,
thus saving time and cost).(defun get-plist-keys (plist)
(remove-if-not #'(lambda (x) (and (symbolp x) (getf plist x))) plist))
(get-plist-keys '(:a 1 :b 2 :c :d))
;; => (:A :B :C)
Upvotes: 2
Reputation: 145
What stylistic direction would do
take us?
CL-USER> (do ((result (list) (cons (car plist) result))
(plist '(:foo 1 :bar 2) (cddr plist)))
((null plist) (reverse result)))
(:FOO :BAR)
By the way, I'd write the loop with less syntax, will this bite me?
CL-USER> (loop for key in '(:foo 1 :bar 2) by 'cddr
collecting key)
(:FOO :BAR)
Upvotes: 3
Reputation: 139381
Not really...
CL-USER 35 > (let ((? nil))
(mapcon (lambda (l)
(when (setf ? (not ?))
(list (first l))))
'(:foo 1 :bar 2)))
(:FOO :BAR)
or maybe:
(defun mapncar (fn list &key (start 0) (n 1))
(loop for l = (nthcdr start list) then (nthcdr n l)
while l
collect (funcall fn (first l))))
CL-USER 61 > (mapncar #'identity '(a 1 b 2 c 3) :n 2)
(A B C)
CL-USER 62 > (mapncar #'identity '(a 1 b 2 c 3) :start 1 :n 2)
(1 2 3)
Upvotes: 4