Załóżmy, że masz listę list, np. '(("abc" "def" "ghi") ("012" "345" "678") ("jkl" "mno" "pqr")) lub '(("ab" "cd" "ef") ("01" "23" "45")).

Jaki byłby kanoniczny sposób na zamek na zamek na listę na danej liście? To znaczy. Jak będzie zdefiniować func

(func '(("ab" "cd" "ef") ("01" "23" "45")) :sep "|" :combiner #'concat) 
  ;; => ("ab|01" "cd|23" "ef|45")

(func '(("abc" "def" "ghi") ("012" "345" "678") ("jkl" "mno" "pqr")) ...)
  ;; => ("abc|012|jkl" "def|345|mno" "ghi|678|pqr")

Gdzie concat := (lambda (args) ...) jest funkcją łączącą głowy odpowiednich list.

Przypuszczalnie, ten typ operacji jest znany jako rotacja lub Zipmany (zgodnie z Odpowiedzi W przypadku powiązanych pytań dla różnych języków).

Mam coś takiego (podwójne - apply)

(apply #'mapcar #'(lambda (&rest args) (apply #'concatenate 'string args)) lst)

Gdzie lst '(("ab" "cd" "ef") ("01" "23" "45"))} na przykład. combiner byłby concatenate. Zauważ, że w tej przykładowej implementacji nie podano separatora.

Ale wydaje się to strasznie zawiły.

Więc Jaka byłaby implementacja kanoniczna dla tego rodzaju operacji ?

1
Aroob 4 czerwiec 2018, 00:59

5 odpowiedzi

Najlepsza odpowiedź

Dla dowolnej liczby list należy pozbyć się apply #'mapcar, ponieważ w przeciwnym razie liczba list byłaby ograniczona przez call-arguments-limit.

Jednym typowym sposobem będzie użycie reduce, łącząc dwie listy na raz:

(defun zip (list-of-lists &key (combiner #'concat))
  (reduce (lambda (list-a list-b)
            (mapcar combiner list-a list-b))
          list-of-lists))

Jeśli nie podoba Ci się wyraźny formularz {x0}}, możesz lubić curry, e. sol. z alexandria:

(defun zip (list-of-lists &key (combiner #'concat))
  (reduce (curry #'mapcar combiner)
          list-of-lists))

Inne konstrukty pętli to loop, do, dolist i istnieją również kilka pętli bibliotek, np. sol. iterate, for.

5
Svante 3 czerwiec 2018, 23:14

Dołącz

(defun %d (stream &rest args)
  "internal function, writing the dynamic value of the variable
DELIM to the output STREAM. To be called from inside JOIN."
  (declare (ignore args)
           (special delim))
  (princ delim stream))

(defun join (list delim)
  "creates a string, with the elements of list printed and each
element separated by DELIM"
  (declare (special delim))
  (format nil "~{~a~^~/%d/~:*~}" list))

Cel

Nie używaj &rest args lub {x1}} - który ograniczyłby nasze długości listy.

rekurencyjne mapcars

(defun mapcars (f l)
  (if (null (car l))
      '()
    (cons (funcall f (mapcar #'car l))
          (mapcars f (mapcar #'cdr l)))))

iteracyjne mapcars

(defun mapcars (f l)
  (loop for l1 = l then (mapcar #'cdr l1)
        while (car l1)
        collect (funcall f (mapcar #'car l1))))

Użycie

CL-USER 10 > (mapcars (lambda (l) (join l "|")) l)
("abc|012|jkl" "def|345|mno" "ghi|678|pqr")
4
Rainer Joswig 4 czerwiec 2018, 12:41

Co powiesz na:

(defun unzip (lists &key (combiner #'list))
  (apply #'mapcar combiner lists))

Wtedy powinieneś zrobić join:

(defun join (l &key (sep ", "))
  (format nil (format nil "~a~a~a" "~{~a~^" sep "~}") l))

Następnie wykonaj opakowanie lub lambda do konkretnego użycia:

(unzip '(("abc" "def" "ghi") ("012" "345" "678") ("jkl" "mno" "pqr"))
       :combiner (lambda (&rest l) (join l :sep "|")))
; ==> ("abc|012|jkl" "def|345|mno" "ghi|678|pqr")
2
Sylwester 3 czerwiec 2018, 23:29

Pierwotnie, próbowałem używać backquote i splice ,@ i definiowanie makra, aby pozbyć się apply s. Ale jak wskazał @Coredump, który niesie, to inne problemy.

W każdym razie potrzebujesz funkcji Pythonic join() jako kombinarna, jeśli chcesz dodać separatory między elementami dołączonych. concatenate sprawi, że rzeczy bardziej skomplikowane, jeśli funkcja będzie zachowywać w sposób, w jaki chcesz.

Odkąd używam bardzo eleganckiego join() join(), moja odpowiedź będzie bardzo podobna do jego. Może moja odpowiedź jest najbliższa twojej oryginalnej przykładzie.

(defun join (l &key (sep ", "))
  (format nil (format nil "~a~a~a" "~{~a~^" sep "~}") l))

Korzystając z tego, możemy zdefiniować swój func do:

(defun func (lists &key (sep "|") (combiner #'join))
  (apply #'mapcar
         #'(lambda (&rest args) (funcall combiner args :sep sep))
         lists))

Lub bez apply - Jak @Racter Joswig wskazuje - i wszystkich poprzednich odpowiadających, ponieważ ma ograniczenie w liczbie argumentów, które mogą wziąć (~ 50):

(defun func (lists &key (sep "|") (combiner #'join))
  (reduce #'(lambda (l1 l2)
              (mapcar #'(lambda (e1 e2) 
                          (funcall combiner (list e1 e2) :sep sep)) l1 l2))
          lists))

Lub nieco krótszy:

(defun func (lists &key (sep "|") (combiner #'join))
  (reduce #'(lambda (l1 l2)
              (mapcar #'(lambda (&rest l) (funcall combiner l :sep sep)) l1 l2))
          lists))

Testowanie:

(func '(("abc" "def" "ghi") ("012" "345" "678") ("jkl" "mno" "pqr"))) 
;; ("abc|012|jkl" "def|345|mno" "ghi|678|pqr")

Należy pamiętać, że :sep "" sprawia, że join odpowiednik funkcji concatenate.

(func '(("abc" "def" "ghi") ("012" "345" "678") ("jkl" "mno" "pqr")) :sep "")
;; ("abc012jkl" "def345mno" "ghi678pqr")

Dzięki @sylWester, @Coredump i @rainer Joswig!

2
Gwang-Jin Kim 4 czerwiec 2018, 19:22

Dlaczego nie używaj i odpocząć w params i powrócić do funkcji LAMBDA.

CL-USER> (mapcar (lambda (&rest l) l) '(1 2 3) '(a b c) '("cat" "duck" "fish"))
((1 A "cat") (2 B "duck") (3 C "fish")) 

Następnie możesz użyć w ten sposób:

(apply #'mapcar (lambda (&rest l) l) '(("ab" "cd" "ef") ("01" "23" "45"))) 
; => (("ab" "01") ("cd" "23") ("ef" "45"))

Dopiero tutaj jest to, co zrobił normalny zamek błyskawiczny, teraz chcesz dołączyć do tego suboblist do jednego sznurka, a niektóre niestandardowe separator.

Dla tej najlepszej odpowiedzi jest format użycia, a także tak jak ta Odpowiedź Z stackoverflow tutaj https://stackoverflow.com/a/41091118/1900722

(defun %d (stream &rest args)
  "internal function, writing the dynamic value of the variable
DELIM to the output STREAM. To be called from inside JOIN."
  (declare (ignore args)
           (special delim))
  (princ delim stream))

(defun join (list delim)
  "creates a string, with the elements of list printed and each
element separated by DELIM"
  (declare (special delim))
  (format nil "~{~a~^~/%d/~:*~}" list))

Następnie musisz ponownie użyć MapCar ponownie na tych funkcjach i listy wyników z ZIP

(mapcar (lambda (lst) (join lst "|")) '(("ab" "01") ("cd" "23") ("ef" "45")))
 ; => ("ab|01" "cd|23" "ef|45")
1
anquegi 4 czerwiec 2018, 12:16