이번 강좌는 어느 카페에서 올라온 3두개 7두개로 24를 어떻게 만드나?하는 퀴즈에서 시작되었다.
사칙연산으로 3두개와 7두개로 24를 만들려면 어떻게 해야하나?
아마 대부분 머릿속에서 일일이 대입하고 있는 사람이 많을것이다.원리를 꿰뚫어서 푼다면 당신은 천재 -_-;;; 나의경우는 스킴이라는 언어를 배우고 있을때여서 스킴으로 풀어볼까 했다.그런데 방법을 몰라서 멍때리고 있다가 카페에서 순열을 구하는 코드를 득템해서 풀어보게 되었다. 물론 처음 하는거라서 실수도 있었지만 원하는 값을 찾을 수 있었다. 자 그럼 어떻게 만들지 구상을 해보자.나의 경우는 (+ (+ (+ (+ 3) (+ 7)) 3) 7)부터(/ (/ (/ (- 3) (- 7)) 3) 7)까지그냥 일일이 다 대입해서파일에 한 줄 한 줄저장했다.그중에 맞는게 있지 않겠는가? 후후..그리고 그 데이터들을 읽어와서 실행해봤다.엄청난 장점 중 하나로 스킴은 데이터와 코드의 구분이 없다! 물론 리습도 'ㅅ'그래서 글자들을 읽어와서 그대로 실행할 수 있는 것이다.(가령 (+ 1 2)를 파일에서 읽어와서 실행하면 3이 나온다.)실행해서 24와 같으면 그 식을 출력하고 아니면 넘어간다. 후훗.. 실행해본 결과(+ (+ (+ (+ 7) (+ 7)) 7) 3)(+ (+ (+ (+ 7) (+ 7)) 7) 3)(+ (+ (+ (+ 7) (+ 3)) 7) 7)(+ (+ (+ (+ 7) (+ 3)) 7) 7)(+ (+ (- (+ 7) (- 7)) 7) 3)(+ (+ (- (+ 7) (- 7)) 7) 3)(+ (+ (- (+ 7) (- 3)) 7) 7)(+ (+ (- (+ 7) (- 3)) 7) 7)(- (* (* (+ 3) (+ 3)) 3) 3)(- (* (* (+ 3) (+ 3)) 3) 3)(- (* (* (- 3) (- 3)) 3) 3)(- (* (* (- 3) (- 3)) 3) 3)(* (+ (/ (+ 3) (+ 7)) 3) 7)(* (+ (/ (+ 3) (+ 7)) 3) 7)(* (+ (/ (+ 7) (+ 7)) 7) 3)(* (+ (/ (+ 7) (+ 7)) 7) 3)(* (+ (/ (- 3) (- 7)) 3) 7)(* (+ (/ (- 3) (- 7)) 3) 7)(* (+ (/ (- 7) (- 7)) 7) 3)(* (+ (/ (- 7) (- 7)) 7) 3)이렇게 나왔다 내 코드가 잘 못 되어서 이렇게 나온거다;;우선 같은게 두개씩 출력되었고 3두개 7두개가아니라 3 네개나 3 세개 + 7한개 같은거도나와버렸다. 아무래도 숫자 넣는 칸에 3과7로 만들수 있는 모든 경우의 수를 계산해버린거같다.그럼 뭐 어떤가? 답이 나왔는데...-_-;;3두개 7두개인것을 추려보고 두개인것을 하나로 추려보면(* (+ (/ (+ 3) (+ 7)) 3) 7)과 (* (+ (/ (- 3) (- 7)) 3) 7)가 나온다!자 그럼 계산기로 계산해보자 3나누기 7한것에 3을 더한뒤 7을 곱하면 24가 나온다 후훗...!음수 3을 음수 7로 나누고 3더하고 7곱해도 역시나 24가 나온다. 자 코드를 살펴보자 핵심 함수는 다음과 같다.우선 눈에 익혀두자 아래에 설명할 것이다. ==모든 경우의 수===forfor*/list================================= ===데이터 변환===list->string ================= ===파일에 출력(기록)(파일을 열어야 기록할 수 있다.)===open-output-filedisplay=================== ===파일에서 읽어오기===open-input-fileread======================== ===파일에서 읽어온 식을 실행===make-base-namespaceeval=============================== 자 이제 설명을 해보겠다. ==모든 경우의 수======for===변수 먼저 적고 그뒤에 몸체가 있다는 함수 선언 하는것과 비슷하다.(for () ())for뒤에 오는 첫번째 괄호에는 [변수 리스트]를 넣고 팁>> []가 여러개 넣어도 된다.팁>> []는 ()과같다. 그런데 계속 ()만 쓰면 눈아프니까 'ㅅ';;두번째 괄호에는 그 변수들을 가지고 연산을 하는 칸이다. 비워두면 오류나므로 뭔가 적어두자.(for ([i '(1 2 3)] [j "abc"] [k '(j k l)]) (display (list i j k)))for는 리스트중에 팁>> "abc"는 '(#a #b #c)와 같다고보면 된다. #는 뒤에 문자가 온다는 뜻.팁>> #space는 공백을 뜻한다.for가 첫번째 돌때는 각각 리스트의 첫번째 원소 값이 각각 변수 i j k에 저장되고 두번째 돌때는 두번째 세번째돌때는 세번째 원소값이 변수에 저장된다.즉(1 a j)(2 b k)(3 c l)가 나온다.그럼 다음과 같은 코드는 어떻게 출력될까?(for ([i '(2 3 4 5 6 7 8 9)]) (printf "2 * ~a = ~an" i (* 2 i)))다음과 같이 출력된다.2 * 2 = 42 * 3 = 62 * 4 = 82 * 5 = 102 * 6 = 122 * 7 = 142 * 8 = 162 * 9 = 18========= ===for*/list===(for*/list ([변수1 리스트1] [변수2 리스트2] [변수3 리스트3]) (변수사용하는 함수))이 함수 역시 for와 마찬가지로 for*/list뒤에 첫째 괄호안에 [변수 리스트]를 여러개 적고 (리스트를 하나만 적으면 for나 for*/list나 차이가 없다. 여기서는 3개를 적었다.)그뒤의 괄호안에는 그 변수를 사용하는 계산이 온다.(for*/list ([i '(1 2 3)] [j "abc"] [k '(j k l)]) (list i j k))그럼 이 코드의 결과는 어떻게 나올까?한마디로 말하자면 모든 경우의 수가 리스트로 나온다.다음은 위에 적어둔 for*/list의 결과이다'((1 #a j) >>첫번째 돌아서 (list i j k)를 실행시킨 결과. (1 #a k) (1 #a l) (1 #b j) (1 #b k) (1 #b l) (1 #c j) (1 #c k) (1 #c l) (2 #a j) (2 #a k) (2 #a l) (2 #b j) (2 #b k) (2 #b l) (2 #c j) (2 #c k) (2 #c l) (3 #a j) (3 #a k) (3 #a l) (3 #b j) (3 #b k) (3 #b l) (3 #c j) (3 #c k) (3 #c l)) ===for*===for*라는 함수도있는데 for*와 for*/list의 차이점은 for*/list는 결과들을 리스트로 묶는다는 것이다. for*/list의 결과를 보면 가장 가장자리에 '()가 있는 것을 볼수있다.for*를 연습해보고 싶다면다음과 같이 해보라. display를 추가했다.(for* ([i '(1 2 3)] [j "abc"] [k '(j k l)]) (display (list i j k)))========== ==여기서 잠깐.연습문제!===for*/list를 사용해서 구구단을 출력해 보아라.힌트>>#lang scheme 포함하고 들여쓰기 해서 4줄이면 된다.=============================================================무언가 감이 오지 않는가?for*/list를 사용해서 모든 경우의 식들을 만들고for를 사용해서 그 식들하나하나를 문자열로 바꿔서 저장하게 될것이다.저장 용량은 뭐 200KB안에서 해결되더라. ===데이터 변환======list->string==='(1 2 3)같은 리스트를 "1 2 3"과 같은 문자열로 바꿔준다.파일에 리스트가 아닌 문자열을 출력하기 때문에 사용한것임.(list->string (list 변수1 변수2 변수3 등등))는 변수1 변수2 변수3 등등을 리스트로 만들고 그 리스트를 문자열로 바꾼다.여기서는 변수자리에 #( 같은게 들어갔는데 #는 뒤에 따라오는 문자 하나를 뜻한다.#)는 닫은 괄호를 뜻하고 #a은 문자 a를 뜻한다.즉 "(+ (+ (+ (+ 3) (+ 3)) 3) 3)" 이런걸 만드라는 의미이다.팁>> '(1 (+ 1 1) 3)은 문자 그대로 '(1 (+ 1 1) 3)인 리스트이다. 하지만 (list 1 (+ 1 1) 3)은 '(1 2 3)이 된다. 즉 안의 계산을 먼저하고 그걸 리스트로 만드는 것이다.================= ===입력과 출력이 헛갈리는 분들을 위한 팁===input은 입력 output은 출력이다. 입력은 파일에서 받을수도 사용자에게 받을수도 있다.출력은 화면에 출력할수도있고 파일에 출력할수도 있다.입력이라고 해서 파일에다가 입력하라는 뜻이 아니다.입력은 받는거 출력은 뿌리는 것이다.=========================================== ===파일에 출력(기록)(파일을 열어야 기록할 수 있다.)===팁>> 출력이라는 말이 헛갈릴수 있는데 파일에 출력이라함은 파일에 기록하는것이다.팁>> 경로를 따로 지정안하고 이름만 적어두었으니 소스가 저장된 곳에 test.txt가 생길것이다. (소스를 저장 안 했으면 Racket설치 폴더에 test.txt가 생길것임.)(define out (open-output-file "test.txt")) => 파일열고 out에 대입한다. => 이제 out에다가 출력하면 test.txt에 글자가 써진다.(display strings out) => strings안에 든 내용을 out(즉 test.txt)에 출력한다.(display "rn" out) => r은 커서를 가장 앞으로 옮기고 n은 다음줄로 넘어간다는 뜻이다. => n만 쓰면 메모장으로 열때 깨진 문자를 볼수 있을것이다.팁>> 이렇게 string따로 rn따로 나누어서 파일에 출력 하고싶지 않다면 (fprintf o "~arn" strings)처럼 쓴 후에 o문자열을 한번에 파일에 출력하는 방법이 있다. o에 출력하기 전에 (define o (open-output-string))이런 코드를 적어둬야 된다.열었던 파일을 (close-output-port out)로 닫아주는 센스!!================================================ ===파일에서 읽어오기===(define in (open-input-file "test.txt")) => 파일을 열고 in에 대입했다. => 이제 in을 읽어서 출력하면 파일에서 한줄씩 읽어와서 출력된다.(display (read in)) => read로 in을 읽어서 출력했다.팁>> display를 위에서는 파일에 출력할때 썼는데 뒤에 아무것도 안 적어주면 화면에 출력하라는 뜻이다. => 파일을 열어야 파일에서 읽어올 수 있다.주의>> (open-input-file "test.txt")을 실행하면 파일의 처음부터 읽기시작한다. 만약 (read (open-input-file "test.txt"))을 10번실행하면 10번 모두 첫줄만 나올것이다. 하지만 (read in)으로 적고 10번 실행하면 한줄씩 넘어가서 10줄이 나올것이다. 즉 읽을때 가르키는 곳이 한번 읽을때마다 다음 줄로 넘어간다.======================= ===파일에서 읽어온 식을 실행===(이 namespace와 eval 부분은 필자도 자세히 모르므로 틀려도 양해 바란다 'ㅅ';;)===make-base-namespace===namespace는 기호들과 기호와 관련된 정보를 연결해 놓은 자료이다.eval로 불러온 식을 실행할때 기호들이 무슨 역할을 하는지 여기서 참조한다.그냥 간단하게 eval 쓰기 전에 (define ns (make-base-namespace)) 이런식으로 선언해준다고 생각하자.============================eval===eval은 evaluation의 약자다. 평가라는 뜻이다.(수학에서 값을 구한다라는 뜻이다.)필자는 실행이라는 단어를 많이 썼는데 사실 평가라는 단어가 정석이다.eval은 뒤에 오는 리스트 데이터를 코드라고 생각하고 실행한다.실행할때 기호들을 어떻게 실행할지는 마지막에 오는 namespace 를 참조한다.'(+ 1 2 3)이 있다면 +가 기호/함수 이다. (eval '(+ 1 2 3) ns) 이렇게 적고 실행하면 6이 나올것이다.여기에서는'(+ 1 2 3)대신에 위에서 for*/list로 만든 식이 들어올것이다.다음과 같이 적을것이다.(eval found ns)found에는 다음과 같은 값이 들어갈것이다. 주의>> ??는 생략했다는 뜻. ??를 적는다는의미가 아니다 -_-;;(let ((found (read in))) (??))found에다가 in 파일에서 read한 식을 한줄씩을 저장하므로 '(+ (+ (+ (+ 3) (+ 7)) 3) 3) 이런 데이터가 저장 될것이다.스킴은 데이터와 코드의 구분이 없으므로 이걸 실행할수 있다!!왜 found에다가 저장한 다음에 24인지 비교해서 맞으면 found를 출력했냐는 사람들이 있을것이다.그건 (= (eval (read in) ns) 24)와 같이하면 24일때 그 식을 출력하기 어렵기 때문이다.(필자가 모르는 것일수도 -_-;;)========================================= 다음 편에 계속된다.