2019년 1월 8일 화요일

[on lisp] 4.5 Mapping 매핑

4.5 Mapping
리습에서 또 널리 사용되는 녀석들이 있다. 이걸 매핑 함수라고 한다. 일련의 인수(시퀀스)에 함수를 적용시키는 함수다.
Figure 4.6은 새로운 매핑 함수의 몇가지 예를 보여준다.

(defun map0-n (fn n)
  (mapa-b fn 0 n))
  
(defun map1-n (fn n)
  (mapa-b fn 1 n))

(defun mapa-b (fn a b &optional (step 1))
  (do ((i a (+ i step))
       (result nil))
      ((> i b) (nreverse result))
   (push (funcall fn i) result)))

(defun map-> (fn start test-fn succ-fn)
  (do ((i start (funcall succ-fn i))
       (result nil))
      ((funcall test-fn i) (nreverse result))
    (push (funcall fn i) result)))
 
(defun mappend (fn &rest lsts)
  (apply #'append (apply #'mapcar fn lsts)))

(defun mapcars (fn &rest lsts)
  (let ((result nil))
    (dolist (lst lsts)
   (dolist (obj lst)
     (push (funcall fn obj) result)))
 (nreverse result)))

(defun rmapcar (fn &rest args)
  (if (some #'atom args)
      (apply fn args)
   (apply #'mapcar
          #'(lambda (&rest args)
              (apply #'rmapcar fn args))
    args)))
하... 보기만 해도 머리가 지끈하다. 하나하나 봐보자.
처음 세개의 함수는 편하게 사용하기 위해 만든 것인가보다.
(The first three are for applying a function to a range of numbers without having to cons up a list to contain them.)
보아하니 매개변수로 넘겨주기 귀찮아서, 0부터 n까지의 range를 매개변수로 줄 때 사용한다는 것 같다.
잘 보면 map0-n는 0부터 n까지 map1-n는 1부터 n까지임을 확인할 수 있다.
> (map0-n #'1+ 5)
(1 2 3 4 5 6)
> (map1-n #'1+ 5)
(2 3 4 5 6)
이 주 map0-n과 map1-n는 사실 mapa-b를 이용하여 숫자 범위가 만들어지고 있다.
> (mapa-b #'1+ -2 0 .5) ;; -2부터 0까지 .5 단위로 커지는 range
(-1 -0.5 0.0 0.5 1.0)

;; 코드구경
(defun mapa-b (fn a b &optional (step 1))
  (do ((i a (+ i step))  ;; do (i::인덱스 a::시작점 (+ i step)::업데이트
       (result nil))     ;; 끝나면 리턴할 녀석 nil은 초기값
      ((> i b) (nreverse result)) ;; 시작할 index의 값이 b(마지막)값보다 크면 반대로 돌아야 하니까 result를 nreverse
   (push (funcall fn i) result)))  ;; result 값에 반복문 돌면서 함수 실행후 result에 넣음.
mapa-b보다 더 일반적인 map->가 있다. 이 매핑은 숫자만이 아닌 모든 종류의 객체 시퀀스에 사용할 수 있다.
(defun map-> (fn start test-fn succ-fn)
  (do ((i start (funcall succ-fn i)) ;; succ-fn로 인덱스를 바꿈
       (result nil)) ;; 리턴값
      ((funcall test-fn i) (nreverse result)) ;; test-fn로 끝났는지 확인하고 result 리턴
    (push (funcall fn i) result))) ;; result에 fn를 실행하여 넣는다.
보면 알겠지만 인덱싱을 하던 i가 꼭 숫자일 필요조차 없다. 업데이트나 터미널인지 확인하는 것도 따로 함수를 넣는다.
그렇다면 mapa-b또한 이 유틸리티 함수를 이용하여 재구현해보자.
(defun mapa-b (fn a b &optional (step 1))
  (map-> fn  ;; 요소별로 실행할 함수
         a   ;; 시작숫자
   #'(lambda (x) (> x b))  ;; 끝날 숫자 확인 
   #'(lambda (x) (+ x step)))  ;; 인덱스 업데이트 방법
효율성을 위하여, 빌트인 mapcan함수는 파괴적이다(수정을한다).
(defun our-mapcan (fn &rest lsts)
  (apply #'nconc (apply #'mapcar fn lsts)))
mapcan은 nconc와 함께 리스트를 연결하기 때문에, 첫번째 인수에서 리턴된 리스트를 새로 만들거나, 다음에 변경할 때 해당 리스트가 변경될 수 있다.
단순히 다른 곳에 저장된 리스트을 반환했다면 mapcan을 사용하는 것은 안전하지 않았을 것이다.
대신 우리는 리턴된 리스트를 append로 연결해야 했다. 이러한 경우 mappend는 비파괴적인 mapcan의 대안을 제공한다.

다음 유틸리티, mapcars 함수,는 여러 목록에 함수를 매핑하는 경우사용된다.
우리에게 두 개의 숫자리스트가 있고, 제곱근의 단일 리스트를 얻고 싶다면?
(mapcar #'sqrt (append list1 list2))
하지만 여기서 실행되는 conses는 불필요한 일이다. 우리는 list1, list2를 append하여 즉시 결과를 폐기한다.
mapcars를 사용하면 다음과 같은 결과를 어등ㄹ 수 있다.
(mapcars #'sqrt list1 list2)

;; 코드를 보자.
(defun mapcars (fn &rest lsts)
  (let ((result nil)) ;; 일단 리턴할 result를 nil로
    (dolist (lst lsts) ;; lsts를 lst란 이름으로 순회
   (dolist (obj lst)  ;; lst를 obj란 이름으로 순회
     (push (funcall fn obj) result)))  ;; funcall로 fn에 obj를 넣어서 실행 후 result에 push
 (nreverse result)))  ;; 뒤집어서 던짐.
이러면 우리는 매개변수를 던질때 굳이 cons를 하여 던질 필요가 없다.

Figure4.6의 마지막 함수는 tree를 위한 mapcar다.
rmapcar는 "recursive mapcar"의 줄인말이다. mapcar는 평평한 리스트에서 작동하며, 이녀석은 tree에서 작동한다.
(rmapcar #'princ '(1 2 (3 4 (5) 6) 7 (8 9))) 
123456789
(1 2 (3 4 (5) 6) 7 (8 9))

> (rmapcar #'+ '(1 (2 (3) 4)) '(10 (20 (30) 40)))
(11 (22 (33) 44))


;; 코드구경
(defun rmapcar (fn &rest args)  ;; args가 트리 (&rest인걸보니 여러개 들어올 수 있음)
  (if (some #'atom args)  ;; 하나만 들어왔다면
      (apply fn args)  ;; 바로 하나만 실행해버림
   (apply #'mapcar  ;; 아니라면 mapcar 실행
          #'(lambda (&rest args)
              (apply #'rmapcar fn args))  ;; 재귀를 실행할 람다 생성
    args)))  ;; apply는 마지막 매개변수가 list여야 한다. 이 list들은 추가적인 매개변수들로 적용된다.
;; http://n-a-n-o.com/lisp/cmucl-tutorials/LISP-tutorial-20.html apply 내용출처

나중에 이것들을 실제로 쓸 것이다.
CLTL2에서 소개된 새로운 시리즈의 매크로들은 기존의 리스트 매핑 기능을 어느 정도 쓸모없게 만들었따.
(mapa-b #'fn a b c)
이녀석은 아래처럼 바뀐다.
(collect (#Mfn (scan-range :from a :upto b :by c)))
뭐야 Mfn는 모나드를 말하는 건가?
뭐 여튼 이런게 있다고 한다.

댓글 없음:

댓글 쓰기