¿Mapear una función a través de una lista de propiedades?


17

P: ¿cuál es la forma idiomática de asignar una función a través de una lista de propiedades?

Las diversas funciones de mapeo ( mapcary familia) mapean una función sobre una secuencia como una lista. ¿Cómo se usan estas funciones cuando se trata de una lista de propiedades , es decir, cuando se trata de mapear a través de cada una de las propiedades contenidas en la lista (que sería cualquier otro elemento a partir del primero)? Me parece que la función de mapeo necesitaría acceder a la lista en pares de elementos en lugar de como elementos individuales.

Como ejemplo de juguete, ¿cómo se tomaría una lista de propiedades y se recopilarían todos los valores de las propiedades? Si fuera una lista de asociación, sería bastante simple:

(mapcar #'cadr '((:prop1 a) (:prop2 b) (:prop3 c))) ;=> (a b c)

Estoy seguro de que esto podría hacerse con un bucle, pero parece un poco laborioso y me pregunto si hay una forma más idiomática de hacerlo.


Por favor, aclare si desea asignar solo los valores de propiedad (que es lo que parece, y que es lo que hace su mapcarejemplo de lista) o si desea asignar sobre los pares de símbolo de propiedad y valor de propiedad. Este último es más general (más útil en general), supongo.
Dibujó el

@Drew: Estoy más interesado en el caso general; El ejemplo fue el más simple que se me ocurrió. Me gustaría saber cómo mapear sobre el par propiedad / valor . Si la respuesta es "un bucle", que así sea, pero me pregunto si hay una solución más elegante.
Dan

Lo que tienes en tu ejemplo no es una lista de propiedades. Las listas de propiedades tienen un número par de elementos, los elementos impares son nombres de propiedades, los elementos pares son valores de propiedades. Lo que tienes, probablemente, se llamará un árbol (una lista de listas).
wvxvw

Respuestas:


8

Es probable que obtenga varias looprespuestas e iteraciones. AFAIK, no hay forma idiomática de hacer esto. Ni siquiera creo que sea muy común querer acumular solo los valores de propiedad (no asociarlos con las propiedades).

Aquí hay una manera simple de hacerlo:

(defun prop-values (plist)
  "..."
  (let ((pl    (cdr plist))
        (vals  ()))
    (while pl
      (push (car pl) vals)
      (setq pl  (cddr pl)))
    (nreverse vals)))

Para el caso más general, donde desea asignar una función binaria sobre un plist:

(defun map-plist (fn plist)
  "..."
  (let ((pl    plist)
        (vals  ()))
    (while pl
      (push (funcall fn (car pl) (cadr pl)) vals)
      (setq pl (cddr pl)))
    (nreverse vals)))

(setq foo '(a 1 b 2 c 3 d 4 e 5))

(map-plist #'cons foo) ; => ((a . 1) (b . 2) (c . 3) (d . 4) (e . 5))

13

Esto probablemente dependerá de una situación. En general, si necesito vincular varios valores con varios nombres, usaría una tabla hash, pero si tengo que usar una lista de propiedades, usaría cl-loop. A continuación hay algunos ejemplos:

(cl-loop for (key value) on '(:prop1 a :prop2 b :prop3 c) by 'cddr
         collect value)
;;; (a b c)

Y si tiene una estructura de datos que muestra en su ejemplo:

(cl-loop for (key value) in '((:prop1 a) (:prop2 b) (:prop3 c))
         collect value)
;;; (a b c)

5

Para mí, la respuesta es convertir el plist en un alist, que es una estructura de datos equivalente, solo la mitad de profunda y más fácil de manipular.


3
Bueno, esa es una especie de respuesta (y un buen consejo), pero más un comentario.
Dibujó el

4

Con Emacs del Git HEAD actual que se convertirá en Emacs 25, puede hacer lo siguiente, es decir, convertir el plist en una lista fácilmente con la nueva seq-partitionfunción, y luego procesar las entradas de la lista usando el estándar mapcar.

(let* ((my-plist (list :a 1 :b 2 :c 3 :more (list 4 5 6)))
       (my-alist (seq-partition my-plist 2))
       (my-reverse-alist (mapcar (lambda (entry)
                                   (let ((prop (car entry))
                                         (val  (cadr entry)))
                                     (list val prop)))
                                 my-alist)))
  (message "my-plist: %s\nmy-alist: %s\nmy-reverse-alist: %s"
           my-plist my-alist my-reverse-alist))
;; my-plist: (:a 1 :b 2 :c 3 :more (4 5 6))
;; my-alist: ((:a 1) (:b 2) (:c 3) (:more (4 5 6)))
;; my-reverse-alist: ((1 :a) (2 :b) (3 :c) ((4 5 6) :more))

Echa un vistazo a los seq-partitiondocumentos usando C-h f seq-partition RET.


1

Una idea es utilizar -map-indexeddesde dashy aplicar la transformación sólo para valores impares de la lista:

(-non-nil (-map-indexed (lambda (index item) (when (oddp index) item))
  '(a x b y c z))) ; => (x y z)
(-non-nil (--map-indexed (when (oddp it-index) it) '(a x b y c z))) ; => (x y z)

Otra idea es convertir un plist en una tabla hash, usando ht<-plistfrom ht:

(ht-values (ht<-plist '(a x b y c z) 'equal)) ; (z y x)

Tenga en cuenta que la tabla hash no conserva el orden de los elementos.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.