콘솔에 삼각형

         *
       * *
      *   *
     *     *
    *       *
   *         *
  *           *
 *             *
*****************


을 출력하는 Common Lisp 소스 코드를 작성해 보자. 이런 소스 코드의 작성은 학원이나 학교에서 프로그래밍 입문자에게 과제로 많이 주어지는 것 중의 하나이다. 코끼리를 보거나 만진 사람들이 저마다 그 생김새를 말할 때 제각기 다르게 표현할 수 있듯이 이런 소스 코드의 작성도 알고 보면 얼마든지 많은 방법이 있을 것이다. 여기서는 쉬운 코드 부터 작성해 보고 차츰차츰 소스를 바꾸어 가면서 Common Lisp 프로그래밍의 기초부분을 터득해 보기로 한다.

모든 소스 코드에서는 삼각형 출력 부분 담당 함수 printTriange를 별도로 구현하였다.

우선 첫번 째 예제는 Common Lisp의 컨솔 출력 함수 format t 의 사용법만 알면 누구나 코딩할 수 있는 매우 단순한 소스 코드이다. 문자열 상수 내에서 ~%는 C, C++, Java, C#, Python 같은 대부분의 절차형 언어에서 사용하는 메타문자 \n에 해당한다.

아래의 모든 소스는 CLisp으로 실행되도록 작성되었다.


삼각형 출력 예제 1
;;  Filename: printTriangle1.lsp
;;            Print a triangle on console.
;;
;;  Execute: clisp printTriangle1.lsp
;;
;;      Date:  2013. 9. 5.

(defun printTriange()
    (format t "        *        ~%")
    (format t "       * *       ~%")
    (format t "      *   *      ~%")
    (format t "     *     *     ~%")
    (format t "    *       *    ~%")
    (format t "   *         *   ~%")
    (format t "  *           *  ~%")
    (format t " *             * ~%")
    (format t "*****************~%") )

(printTriange)




위의 소스 코드는 아무 알고리즘도 없는 너무 단순한 코드이다. 이런 코드를 작성했다간 출력 모양이나 크기를 변경해야 하는 상황을 맞이하면 워드프로세서로 문서 만드는 것 이상으로 많은 수작업을 하거나 아니면 포기하는 지경에 이를 수도 있다. 그래서 다음 처럼 좀 더 나은 소스 코드를 작성하였다.  문자를 하나씩 출력하는 함수 my-write를 정의하여 이를 사용하였다. (write는 Common Lisp에서 이미 정의되어 있으므오 다른 이름 my-write으로 하였다.) C, C++, Java, C#, Python, Ruby 등의 대부분의 언어에서는 특수문자 +, -, * 등을 함수명이나 변수명에 사용하지 못하지만, Common Lisp 언어에서는 이들을 사용해도 된다.  특히 *variable-name* 처럼 변수명의 처음과 끝에 *를 사용하는 것은 (Common Lisp 소스 코드 작상 관습상) 전역변수인 것으로 본다.



삼각형 출력 예제 2
;;  Filename: printTriangle2.lsp
;;            Print a triangle on console.
;;
;;  Execute: clisp printTriangle2.lsp
;;
;;      Date:  2013. 9. 5.

(defun my-write(s)
    (format t "~A" s) )

(defun printTriange()
    (loop for i from 0 below 8 do
        (loop for k from 0 below (- 8 i) do
            (my-write " ") )
        (loop for k from 0 below (+ (* 2 i) 1) do
            (if (or (zerop k) (= k (* 2 i)))
                (my-write "*")
                (my-write " ") ))
        (loop for k from 0 below (- 8 i) do
            (my-write " ") )
        (format t "~%") )

    (loop for i from 0 below 17 do
        (my-write "*") )
    (format t "~%")
)

(printTriange)



위의 소스 코드는 my-write 함수와 format t "~%"  를 적절히 사용하여 구현되었다. 숫자 몇 곳만 수정하면 출력되는 삼각형의 크기를 쉽게 바꿀 수 있다. 한 줄에 출력될 문자를 구성하는 알고리즘은 위의 예제와 근본적으로 같지만 한 문자식 출력하는 대신에 한 줄에 출력될 문자열을 만들어서 출력하는 소스 코드를 다음 예제와 같이 작성해 보았다.
또 빈칸 17개의 문자로 구성된 리스트를 생성하기 위한 구문은

        (setf line2 (make-list 17 :initial-element " "))

이다.


삼각형 출력 예제 3
;;  Filename: printTriangle3.lsp
;;            Print a triangle on console.
;;
;;  Execute: clisp printTriangle3.lsp
;;
;;      Date:  2013. 9. 5.

(defun join-string-list (string-list)
    (format nil "~{~A~^~}" string-list))

(defun printTriange()
    (let ((line2 (list)))
        (setf line2 (make-list 17 :initial-element " "))
        (loop for i from 0 below 8 do
            (setf line2 (make-list 17 :initial-element " "))
            (setf (nth (- 8 i) line2) #\*)
            (setf (nth (+ 8 i) line2) #\*)
            (format t "~A~%" (join-string-list line2)) )

        (setf line2 (make-list 17 :initial-element " "))
        (loop for i from 0 below 17 do
            (setf (nth i line2) #\*) )
        (format t "~A~%" (join-string-list line2))
    )
)

(printTriange)




별(*) 문자를 이용하여 삼각형을 출력하는 일은 빈칸 문자와 별 문자를 적당한 좌표(위치)에 촐력하는 일이다. 출력될 한 줄의 스트링을 완성한 후 하나의 format t 구문으로 출력하는 기법으로 소스 코드를 작성해 보았다. 소스 코드 중에

           (whites (make-string 17 :initial-element #\Space))
           (stars (make-string 17 :initial-element #\*))

은 지정된 개수(여기서는 17) 만큼 :initial-element 옵션에 주어진 character를 중복 연결하는 구문이다.




삼각형 출력 예제 4
;;  Filename: printTriangle4.lsp
;;            Print a triangle on console.
;;
;;  Execute: clisp printTriangle4.lsp
;;
;;      Date:  2008/04/02

(defun printTriange()
    (let* ((whites (make-string 17 :initial-element #\Space))
           (stars (make-string 17 :initial-element #\*))
           (line2 (concatenate 'string (substring whites 0 8) "*" (substring whites 0 7))) )
   
        (format t "~A~%" line2)
        (loop for i from 1 below 8 do
            (setf line2 (concatenate 'string (substring whites 0 (- 8 i)) "*" (substring whites (- 8 i) (+ 7 i)) "*" (substring whites (+ 7 i) 17)))
            (format t "~A~%" line2))
        (format t "~A~%" stars)
    )
)

(printTriange)




string은 immutable이라 그 내용을 변경할 수 없지만, 리스트는 그 요소(item)를 아무 때 나 변경할 수 있다. 한줄에 출력될 각 문자를 리스트 타입의 변수 line2에 저장한 다음 format t 문으로 출력 시

    (join-string-list line2)

로 그 리스트의 모든 요소(item)가 모두 연결되어 출력되게 하였다.



삼각형 출력 예제 5
;;  Filename: printTriangle5.lsp
;;            Print a triangle on console.
;;
;;  Execute: clisp printTriangle5.lsp
;;
;;      Date:  2013. 9. 5.

(defun join-string-list (string-list)
    (format nil "~{~A~^~}" string-list))

(defun printTriange()
    (let* ((whites (make-string 17 :initial-element #\Space))
           (stars (make-string 17 :initial-element #\*))
           (start 8)
           (line2 (make-list 17 :initial-element " ")))

        (setf (nth start line2) "*")
        (format t "~A~%" (join-string-list line2))

        (loop for i from 1 below 8 do
            (setf line2 (make-list 17 :initial-element " "))
            (setf (nth (- start i) line2) (format nil "~A" (aref stars (- start i))))
            (setf (nth (+ start i) line2) (format nil "~A" (aref stars (+ start i))))
            (format t "~A~%" (join-string-list line2)) )

        (format t "~A~%" stars)
    )
)


(printTriange)



출력되는 삼각형이 좌우 대칭이라는 사실에 착안하여, 다음 소스 코드에서는  각 줄을 처음 8자, 중앙 한 문자, 끝 8자(처음 8자의 역순)로 string을 만들어 출력하였다.



삼각형 출력 예제 6
;;  Filename: printTriangle6.lsp
;;            Print a triangle on console.
;;
;;  Execute: clisp printTriangle6.lsp
;;
;;      Date:  2013. 9. 5.

(defun printTriange()
    (let* ((whites (make-string 8 :initial-element #\Space))
           (stars (make-string 8 :initial-element #\*))
           (start 8)
           (line (concatenate 'string whites "*" whites)))

        (format t "~A~%" line)

        (loop for i from 1 below 8 do
            (setf line (concatenate 'string (substring whites 0 (- 8 i)) "*" (substring whites (- 8 i) (1- 8))))
            (format t "~A ~A~%" line (reverse line)) )

        (setf line (concatenate 'string stars "*" stars))
        (format t "~A~%" line)
    )
)

(printTriange)




다음 소스 코드는 한 줄에 출력될 문자열의 데이터를 17비트 이진법 수로 구성하고, 이 이진법수의 비트가 0인 곳에는 빈칸을, 1인 곳에는 별(*)을 출력하는 기법으로 작성되었다.



삼각형 출력 예제 7
;;  Filename: printTriangle7.lsp
;;            Print a triangle on console.
;;
;;  Execute: clisp printTriangle7.lsp
;;
;;      Date:  2013. 9. 5.

(setf *BASE36* "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")

(defun itoa(num radix)
    (let* ((isNegative nil)
           (a 0)
           (arr (list))
           (q 0)
           (r 0))

        (if (equal radix nil) (setf radix 10))
  
        (if (minusp num) (progn
            (setf isNegative t)
            (setf inum (- num)) ))

        (setf q num)
      
        (loop while (>= q radix) do
            (setf r (mod q radix))
            (setf q (floor (/ q radix)))
            (setf arr (nconc arr (list (substring *BASE36* r (1+ r)))))
        )

       (setf arr (nconc arr (list (substring *BASE36* q (1+ q)))))
       (if isNegative (nconc (ncons arr (list "-"))) )

       (setf arr (reverse arr))
       (join-string-list arr)
    )
)

(defun join-string-list (string-list)
    (format nil "~{~A~^~}" string-list))

(defun printTriange()
    (let* ((start #x100)
           (total 0)
           (val start)
           (s "")
           (data ""))
        (loop for k from 0 below 8 do
            (setf val (logior (ash start k) (ash start (- k))))
            (setf data (itoa val 2))
            (setf s "")
            (loop for i from 0 below (- 17 (length data)) do
                (setf s (concatenate 'string s " ")) )
            (loop for i from 0 below (length data) do
                (if (equal (aref data i) #\0)
                   (setf s (concatenate 'string s " "))
                   (setf s (concatenate 'string s "*")) ))
            (format t "~A~%" s)
            (setf total (+ total val)) )

        (setf val (logior (ash start 8) (ash start -8)))
        (setf total (+ total val))
        (setf data (itoa total 2))
        (setf s "")
        (loop for i from 0 below (- 17 (length data)) do
            (setf s (concatenate 'string s " ")) )
        (loop for i from 0 below (length data) do
            (if (equal (aref data i) #\0)
                (setf s (concatenate 'string s " "))
                (setf s (concatenate 'string s "*")) ))
        (format t "~A~%" s)
    )
)

(printTriange)




기본적인 원리는 위의 소스 코드와 같지만 이진법수의 한 비트 마다 한 문자씩 츨력하는 대신에 출력될 한 줄의 string을 완성하여 이를 format t 구문으로 출력하는 기법으로 재작성한 것이 다음의 소스 코드이다. (setf 새스트링 (string-replace-char 원래스트링 원본문자 타겟문자)) 을 이용하여 모든 0을 빈칸으로, 모든 1을 별(*) 문자로 바꾸었으며, 별(*) 문자만으로 이루어진 마지막 줄 출력을 위해 변수 total을 준비하였다. loop for 반복 구문의 블럭 내에서 구문

            (setf total (+ total val))

이 하는 일이 무엇인지 이해할 수 있으면 좋겠다.




삼각형 출력 예제 8
;;  Filename: printTriangle8.lsp
;;            Print a triangle on console.
;;
;;  Execute: clisp printTriangle8.lsp
;;
;;      Date:  2013. 9. 5.

   
(setf *BASE36* "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")

(defun itoa(num radix)
    (let* ((isNegative nil)
           (a 0)
           (arr (list))
           (q 0)
           (r 0))

        (if (equal radix nil) (setf radix 10))
  
        (if (minusp num) (progn
            (setf isNegative t)
            (setf inum (- num)) ))

        (setf q num)
      
        (loop while (>= q radix) do
            (setf r (mod q radix))
            (setf q (floor (/ q radix)))
            (setf arr (nconc arr (list (substring *BASE36* r (1+ r)))))
        )

       (setf arr (nconc arr (list (substring *BASE36* q (1+ q)))))
       (if isNegative (nconc (ncons arr (list "-"))) )

       (setf arr (reverse arr))
       (join-string-list arr)
    )
)

(defun join-string-list (string-list)
    (format nil "~{~A~^~}" string-list))

(defun char-replace (a b c)
    (if (= (char-code a) (char-code b))
          c
          a ))

(defun string-to-list (s)
    (loop for char across s
            collect char) )

(defun list-to-string (a)
    (format nil "~{~A~^~}" a))

(defun replacer(b c) #'(lambda (x) (char-replace x b c)))

(defun string-replace-char (s a b)
    (let* ((arr (string-to-list s))
           (arr2 (mapcar (replacer a b) arr)))
       (list-to-string arr2) ))

(defun printTriange()
    (let* ((zeros "00000000")
           (start #x100)
           (total 0)
           (val start)
           (line ""))

        (loop for k from 0 below 8 do
            (setf val (logior (ash start k) (ash start (- k))))
            (setf line (itoa val 2))
            (setf line (concatenate 'string (substring zeros 0 (- 17 (length line))) line))
            (setf line (string-replace-char line #\0 #\Space))
            (setf line (string-replace-char line #\1 #\*))
            (format t "~A~%" line)
            (setf total (+ total val)) )

        (setf val (logior (ash start 8) (ash start -8)))
        (setf total (+ total val))
        (setf line (itoa total 2))
        (setf line (string-replace-char line #\0 #\Space))
        (setf line (string-replace-char line #\1 #\*))
        (format t "~A~%" line)
    )
)

(printTriange)




소스 코드가 처음 것 보다 매우 복잡해졌지만, Common Lisp의 리스트를 이용해서 구현해 보았다. Common Lisp 언어 처럼 함수형 언어에서는 리스트가 매우 중요한 지료형(타입)이다. 별(*) 문자만으로 구성된 마지막 줄 출력을 위해 리스트 타입의 변수 last를 준비하였다. 또 리스트에 속한 모든 item을 출력하도록 구현된 Common Lisp 함수의 정의

    (defun join-string-list (data sep)
        (format nil (concatenate 'string "~{~A" sep "~^~}") data))

과 이의 사용

            (format t "~A~%" (join-string-list 리스트 구분자))

은 음미해볼 만한 부분이다.



삼각형 출력 예제 9
;;  Filename: printTriangle9.lsp
;;            Print a triangle on console.
;;
;;  Execute: clisp printTriangle9.lsp
;;
;;      Date:  2013. 9. 6.

#|
(setf *BASE36* "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")

(defun itoa(num radix)
    (let* ((isNegative nil)
           (a 0)
           (arr (list))
           (q 0)
           (r 0))

        (if (equal radix nil) (setf radix 10))
  
        (if (minusp num) (progn
            (setf isNegative t)
            (setf inum (- num)) ))

        (setf q num)
      
        (loop while (>= q radix) do
            (setf r (mod q radix))
            (setf q (floor (/ q radix)))
            (setf arr (nconc arr (list (substring *BASE36* r (1+ r)))))
        )

       (setf arr (nconc arr (list (substring *BASE36* q (1+ q)))))
       (if isNegative (nconc (ncons arr (list "-"))) )

       (setf arr (reverse arr))
       (join-string-list arr)
    )
)

(defun char-replace (a b c)
    (if (= (char-code a) (char-code b))
          c
          a ))

(defun string-to-list (s)
    (loop for char across s
            collect char) )

(defun list-to-string (a)
    (format nil "~{~A~^~}" a))

(defun replacer(b c) #'(lambda (x) (char-replace x b c)))

(defun string-replace-char (s a b)
    (let* ((arr (string-to-list s))
           (arr2 (mapcar (replacer a b) arr)))
       (list-to-string arr2) ))
|#

(defun join-string-list (data sep)
    (format nil (concatenate 'string "~{~A" sep "~^~}") data))

(defun printTriange()
    (let* ((data (make-list 17 :initial-element " "))
           (last (make-list 17 :initial-element " "))
           (start 8))

        (setf (nth start data) "*")
        (setf (nth start last) "*")
        (format t "~A~%" (join-string-list data ""))

        (setf (nth start data) " ")

        (loop for k from 1 below 8 do
            (setf (nth (- start k) data) "*")
            (setf (nth (- start k) last) "*")
            (setf (nth (+ start k) data) "*")
            (setf (nth (+ start k) last) "*")
            (format t "~A~%" (join-string-list data ""))
            (setf (nth (- start k) data) " ")
            (setf (nth (+ start k) data) " ")
        )

        (setf (nth (- start 8) last) "*")
        (setf (nth (+ start 8) last) "*")
        (format t "~A~%" (join-string-list las ""t))
    )
)

(printTriange)





다음 예제는 수학에서 xy-좌표평면에 점을 찍듯이 논리 구문

             (x + y - 8 == 0) or (y - x + 8 == 0) or (y - 8 == 0)

가 참이 되는 위치에 별(*) 문자를 표시하는 기법으로 작성된 소스 코드이다.




삼각형 출력 예제 10
;;  Filename: printTriangle10.lsp
;;            Print a triangle on console.
;;
;;  Execute: clisp printTriangle10.lsp
;;
;;      Date:  2013. 9. 6.

(defun printTriange()
    (let* ((x 0)
           (y 0)
           (a ""))
          
        (loop for y from 0 below 9 do
            (loop for x from 0 below 17 do
                (if (or (zerop (- (+ x y) 8)) (zerop (+ (- y x) 8)) (zerop (- y 8)))
                    (setf a "*")
                    (setf a " "))
                (format t "~A" a)
            )
            (format t "~%")
        )
    )
)

(printTriange)





Posted by Scripter
,