0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

mapcanを実装してみた

Posted at

若干手こずりました。

hyperspecを確認する

Syntax:
mapc function &rest lists+ => list-1
mapcar function &rest lists+ => result-list
mapcan function &rest lists+ => concatenated-results
mapl function &rest lists+ => list-1
maplist function &rest lists+ => result-list
mapcon function &rest lists+ => concatenated-results

Descriptionを抜粋します。

mapcanとmapconは、関数を適用した結果がlistではなくnconcを使ってリストにまとめられることを除けば、それぞれmapcarとmaplistと同じです。

mapcanの挙動を確認する

(defvar *list1* '(1 11 111))
(defvar *list2* '(2 22 222))
(defvar *list3* '(3 33 333))

(format t "~%~A" (mapcar (lambda (&rest lists)
                           (format t "~%*~A" lists))
                         *list1* *list2* *list3*))
;;; *(1 2 3)
;;; *(11 22 33)
;;; *(111 222 333)
;;; (NIL NIL NIL)

(format t "~%~A" (mapcan (lambda (&rest lists)
                           (format t "~%*~A" lists))
                         *list1* *list2* *list3*))
;;; *(1 2 3)
;;; *(11 22 33)
;;; *(111 222 333)
;;; NIL

mapcarとmapcanの比較です。高階関数の引数のリストは同じですが、最終的な戻り値が違います。

(append (append (append nil (list nil))
                (list nil))
        (list nil))
=> (NIL NIL NIL)


(cdr (nconc (nconc (nconc (list 'dummy) nil)
                   nil)
            nil))
=> NIL

こういうことのようです。dummyのリストにnconcし、最後にCDRを取っています。この例ではdummyは必要ないですが、次の例では必要です。

(format t "~%~A" (mapcar #'+ *list1* *list2* *list3*))
;;; (6 66 666)

(format t "~%~A" (mapcan #'+ *list1* *list2* *list3*))
;;; 666
(append (append (append nil '(6))
                '(66))
        '(666))
=> (6 66 666)

(cdr (nconc (nconc (nconc (list 'dummy) 6)
                   66)
            666))
=> 666

もっと良い解釈があるのかもしれません。

CARのリストを取り出してみる。

(defun aaa (lists acc)
  (cond ((null lists) acc)
        (t (aaa (cdr lists)
                (append acc `(,(car (car lists))))))))

(defun mapcan-aaa (fn &rest lists)
  (aaa lists nil))

(format t "~%~A" (mapcan-aaa nil *list1* *list2* *list3*))
;;; (1 2 3)

CDRのリストを取り出してみる。

(defun bbb (lists acc)
  (cond ((null lists) acc)
        (t (bbb (cdr lists)
                (append acc `(,(cdr (car lists))))))))

(defun mapcan-bbb (fn &rest lists)
  (bbb lists nil))

(format t "~%~A" (mapcan-bbb nil *list1* *list2* *list3*))
;;; ((11 111) (22 222) (33 333))

CARのリストとCDRのリストを取り出してみる。

(defun ccc (lists acc-car acc-cdr)
  (cond ((null lists) `(,acc-car . ,acc-cdr))
        (t (ccc (cdr lists)
                (append acc-car `(,(car (car lists))))
                (append acc-cdr `(,(cdr (car lists))))))))

(defun mapcan-ccc (fn &rest lists)
  (ccc lists nil nil))

(format t "~%~A" (mapcan-ccc nil *list1* *list2* *list3*))
;;; ((1 2 3) (11 111) (22 222) (33 333))

CARのリストとCDRのリストを再帰的に取り出してみる

(defun ddd (lists acc-car acc-cdr)
  (cond ((null lists) `(,acc-car . ,acc-cdr))
        (t (ddd (cdr lists)
                (append acc-car `(,(car (car lists))))
                (append acc-cdr `(,(cdr (car lists))))))))

(defun %mapcan (fn lists acc)
  (cond ((member nil lists) acc)
        (t (let ((tmp (ddd lists nil nil)))
             (%mapcan fn
                      (cdr tmp)
                      (append acc `(,(car tmp))))))))

(defun mapcan-ddd (fn &rest lists)
  (%mapcan fn lists nil))

(format t "~%~A" (mapcan-ddd nil *list1* *list2* *list3*))
;;; ((1 2 3) (11 22 33) (111 222 333))

高階関数を適用する

(defun eee (lists acc-car acc-cdr)
  (cond ((null lists) `(,acc-car . ,acc-cdr))
        (t (eee (cdr lists)
                (append acc-car `(,(car (car lists))))
                (append acc-cdr `(,(cdr (car lists))))))))

(defun %mapcan (fn lists acc)
  (cond ((member nil lists) acc)
        (t (let ((tmp (eee lists nil nil)))
             (%mapcan fn
                      (cdr tmp)
                      (append acc `(,(apply fn (car tmp)))))))))

(defun mapcan-eee (fn &rest lists)
  (%mapcan fn lists nil))

(format t "~%~A" (mapcan-eee #'+ *list1* *list2* *list3*))
;;; (6 66 666)

これはmapcarと同じです。ここからmapcanに変更していきます。

appendをnconcに変えてみる

(defun fff (lists acc-car acc-cdr)
  (cond ((null lists) `(,acc-car . ,acc-cdr))
        (t (fff (cdr lists)
                (append acc-car `(,(car (car lists))))
                (append acc-cdr `(,(cdr (car lists))))))))

(defun %mapcan (fn lists acc)
  (cond ((member nil lists) acc)
        (t (let ((tmp (fff lists nil nil)))
             (%mapcan fn
                      (cdr tmp)
                      (nconc acc `(,(apply fn (car tmp)))))))))

(defun mapcan-fff (fn &rest lists)
  (%mapcan fn lists nil))

(format t "~%~A" (mapcan-fff #'+ *list1* *list2* *list3*))
;;; (6 66 666)

正しくは 666 が返ってほしい。これではだめです。。。

局所変数にnconcする

(defun ggg (lists acc-car acc-cdr)
  (cond ((null lists) `(,acc-car . ,acc-cdr))
        (t (ggg (cdr lists)
                (append acc-car `(,(car (car lists))))
                (append acc-cdr `(,(cdr (car lists))))))))

(defun mapcan-ggg (fn &rest lists)
  (let ((result (list 'dummy)))
    (labels ((%mapcan (fn lists)
               (cond ((member nil lists) result)
                     (t (let ((tmp (ggg lists nil nil)))
                          (nconc result (apply fn (car tmp)))
                          (%mapcan fn (cdr tmp)))))))
      (%mapcan fn lists)
      (cdr result))))

(format t "~%~A" (mapcan-ggg #'+ *list1* *list2* *list3*))
;;; 666
(format t "~%~A" (mapcan-ggg (lambda (x)
                               `(,(1+ x)))
                             '(1 2 3)))
;;; (2 3 4)

正しく返りました!

hyperspecのExsampleでテストする

(mapcan #'(lambda (x y) (if (null x) nil (list x y)))
'(nil nil nil d e)
'(1 2 3 4 5 6)) => (D 4 E 5)
(mapcan #'(lambda (x) (and (numberp x) (list x)))
'(a 1 b c 3 4 d 5))
=> (1 3 4 5)

(format t "~%~A" (mapcan-ggg #'(lambda (x y)
                             (if (null x) nil (list x y)))
                         '(nil nil nil d e)
                         '(1 2 3 4 5 6)))
;;; (D 4 E 5)

(format t "~%~A" (mapcan-ggg #'(lambda (x)
                             (and (numberp x) (list x)))
                         '(a 1 b c 3 4 d 5)))
;;; (1 3 4 5)

正しく返りました!

整える

(defun my-mapcan (fn &rest lists)
  (let ((result (list 'dummy)))
    (labels ((%%mapcan (lists acc-car acc-cdr)
               (cond ((null lists) `(,acc-car . ,acc-cdr))
                     (t (%%mapcan (cdr lists)
                             (append acc-car `(,(car (car lists))))
                             (append acc-cdr `(,(cdr (car lists))))))))
             (%mapcan (fn lists)
               (cond ((member nil lists) result)
                     (t (let ((tmp (%%mapcan lists nil nil)))
                          (nconc result (apply fn (car tmp)))
                          (%mapcan fn (cdr tmp)))))))
      (%mapcan fn lists)
      (cdr result))))

(format t "~%~A" (my-mapcan #'+ *list1* *list2* *list3*))
;;; 666

(format t "~%~A" (my-mapcan (lambda (x)
                               `(,(1+ x)))
                             '(1 2 3)))
;;; (2 3 4)

嵌りポイント

今回の嵌りポイントは

  • nconc自体の挙動。引数にリスト以外を渡した場合の挙動を知りました。
  • 局所変数がリストを束縛する場合の初期化方法。(list 'dummy)と初期化していますが、'(dummy)と初期化すると嵌ります。

最後までご覧いただきありがとうございました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?