Mapper une fonction sur une liste de propriétés?

17

Q: quelle est la manière idiomatique de mapper une fonction sur une liste de propriétés?

Les diverses fonctions de mappage ( mapcaret famille) mappent une fonction sur une séquence telle qu'une liste. Comment utilise-t-on ces fonctions lorsqu'il s'agit d'une liste de propriétés , c'est-à-dire lorsque l'on essaie de mapper sur chacune des propriétés contenues dans la liste (qui serait tout autre élément à partir du premier)? Il me semble que la fonction de mappage aurait besoin d'accéder à la liste par paires d'éléments plutôt qu'en tant qu'éléments individuels.

À titre d'exemple de jouet, comment prendre une liste de propriétés et collecter toutes les valeurs des propriétés? S'il s'agissait plutôt d'une liste d'associations, ce serait assez simple:

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

Je suis sûr que cela pourrait être fait avec une boucle, mais cela semble un peu laborieux et je me demande s'il y a une façon plus idiomatique de le faire.

Dan
la source
Veuillez préciser si vous souhaitez mapper uniquement les valeurs de propriété (ce à quoi cela ressemble et ce que fait votre mapcarexemple d'alist) ou si vous souhaitez mapper les paires de symbole de propriété et de valeur de propriété. Ce dernier est plus général (plus généralement utile), je suppose.
Drew
@Drew: Je suis plus intéressé par le cas général; l'exemple était le plus simple auquel je pouvais penser. Je voudrais savoir comment mapper sur la paire propriété / valeur . Si la réponse est "une boucle", alors tant pis, mais je me demande s'il existe une solution plus élégante.
Dan
Ce que vous avez dans votre exemple n'est pas une liste de propriétés. Les listes de propriétés ont un nombre pair d'éléments, les éléments impairs sont des noms de propriété, les éléments pairs sont des valeurs de propriété. Ce que vous avez serait probablement appelé un arbre (une liste de listes).
wvxvw

Réponses:

8

Vous obtiendrez probablement des loopréponses diverses et itératives. AFAIK, il n'y a aucun moyen idiomatique de le faire. Je ne pense même pas qu'il soit très courant de vouloir accumuler uniquement les valeurs des propriétés (sans les associer aux propriétés).

Voici une façon simple de le faire:

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

Pour le cas plus général, où vous souhaitez mapper une fonction binaire sur 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))
A dessiné
la source
13

Cela dépendrait probablement d'une situation. En général, si j'ai besoin de lier un certain nombre de valeurs à un certain nombre de noms, j'utiliserais une table de hachage, mais si je dois utiliser une liste de propriétés, j'utiliserais cl-loop. Voici quelques exemples:

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

Et si vous avez une structure de données que vous montrez dans votre exemple:

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

Pour moi, la réponse est de transformer le plist en une liste, qui est une structure de données équivalente, juste deux fois moins profonde et plus facile à manipuler.

Stefan
la source
3
Eh bien, c'est une sorte de réponse (et de bons conseils), mais plutôt un commentaire.
Drew
4

Avec Emacs de l'actuel Git HEAD qui deviendra Emacs 25, vous pouvez effectuer les opérations suivantes, c'est-à-dire transformer le plist en liste facilement avec la nouvelle seq-partitionfonction, puis traiter les entrées de liste en utilisant standard 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))

Jetez un œil aux seq-partitiondocuments à l'aide C-h f seq-partition RET.

Tassilo Horn
la source
1

Une idée est d'utiliser -map-indexedde dashet appliquer la transformation que des valeurs impaires de la liste:

(-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)

Une autre idée est de simplement convertir un plist en une table de hachage, en utilisant ht<-plistde ht:

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

Notez que la table de hachage ne préserve pas l'ordre des éléments.

Mirzhan Irkegulov
la source