common-lispelisp

Iterate over plist and set value per key


How can I iterate over a plist, prompt for each key a value and fill in said value into the plist?

I have 'the skeleton' of a list of properties for my projects

(defconst project-properties
  '(:number nil
    :place nil
    :location nil))

I copy this list and fill in the values so I keep a skeleton list and have the filled in list to process it further.

I managed to do it for an alist, but am unable to change the functionality to let it work with plists.

(defun project-prompt-properties-alist ()
  "Prompt for project properties and return them"
  (let ((properties (copy-alist project-properties)))
    (cl-loop for (prop . val) in properties
             do (setf (alist-get prop properties) (read-string (format "Geef %s: " prop))))
    properties))

Solution

  • Both Common Lisp and Elisp allow you to loop over a list using on which iterates on successive tails of the list, instead of in which iterates on successive elements of the list. For example:

    CL-USER> (loop for tail on project-properties
                   do (format t "~A~%" tail))
    (NUMBER NIL PLACE NIL LOCATION NIL)
    (NIL PLACE NIL LOCATION NIL)
    (PLACE NIL LOCATION NIL)
    (NIL LOCATION NIL)
    (LOCATION NIL)
    (NIL)
    

    You can further use the by keyword to reduce the input list by two elements instead of one on each iteration:

    CL-USER> (loop for tail on project-properties by #'cddr
                   do (format t "~A~%" tail))
    (NUMBER NIL PLACE NIL LOCATION NIL)
    (PLACE NIL LOCATION NIL)
    (LOCATION NIL)
    

    You can use destructuring to take the first two elements of each tail:

    CL-USER> (loop for (prop val) on project-properties by #'cddr
                   do (format t "~A -> ~A~%" prop val))
    NUMBER -> NIL
    PLACE -> NIL
    LOCATION -> NIL
    

    These features work the same in Elisp. You should be able to write project-prompt-properties-plist for Elisp like this:

    (defun project-prompt-properties-plist ()
      "Prompt for project properties and return them"
      (let ((properties (copy-sequence project-properties)))
        (cl-loop for (prop val) on properties by #'cddr
                 do (plist-put properties prop (read-string (format "Geef %s: " prop))))
        properties))