2015년 1월 29일 목요일

컴퓨터 프로그램의 구조와 해석(1) 3장 1

개요
프로그램을 설계하는 두가지 방법
1. 시스템을 이루는 낱낱이 물체(Object)라고 보고, 시간에 따라 상태가 변하는 여러 물체를 한데 엮어서 커다란 시스템을 만드는 방법이다.
2. 정보가 강물처럼 끝없이 시스템 속에서 흘러간다 여기고, 시간에 따라 정보가 변하는 모습을 스트림으로 나타낸다.


3.1  덮어쓰기와 갇힌 상태


3.1.1  갇힌 상태변수
물체 상태가 시간에 따라 달라진다.
ex)
(define (balance 100))

(define (withdraw amount)
   (if (>= balance amount)
       (begin (set! balance (- balance amount))
                 balance)
       "Insufficient funds"))
단점)
프로시저가 마음대로 balance 값을 읽을 수도 있고 덮어써 버릴 수도 있다.
해결책 => balance를 withdraw 프로시저 안에 가둔다.

(define (withdraw amount)
  (let ((balance 100))
    (lambda (amount)
      (if (>= balance amount)
          (begin (set! balance (- balance amount))
                    balance)
          "Insufficient funds"))))

(withdraw 25) => 75

(withdraw 25) => 50  //똑같은 식을 두번계산했지만 답은 다르게 나온다.

(withdraw 60) => "Insufficient funds"

/* 특별한 형태

 (set! <name> <new-value>)

이 식을 계산하고 나면, <name>이 가리키던 값이 <new-value> 식을 계산한 값으로 바뀐다

(begin <exp> <exp2> ... <expk>)

<exp>부터 <expk>까지 k개 싱을 차례대로 계산한다. 그리고 마지막 <expk>를 계산한 값을, 전체 begin식을 계산한 값으로 삼는다.

*/

(define (make-withdraw balance)
   (lambda (amount)
       (if  (>= balance amount)
            (begin (set! balance (- balance amount))
                      balance)
            "Insufficient funds"))))

(define W1 (make-withdraw 100))
(define W2 (make-withdraw 100))
//make-withdraw의 성질을 가진 물체를 얼마든지 만들수 있다. 위의 프로시저만 있다면

(define (make-account balance)
   (define (withdraw amount)
     (if (>= balance amount)
         (begin  (set! balance (- balance amount))
                     balance)
         "Insufficient funds"))

(define (deposit amount)
   (set! balance (+ balance amount))
   balance)
(define (dispatch m)
   (cond  ((eq? m 'withdraw) withdraw)
              ((eq? m 'deposit) deposit)
              (else (error "Unknown request -- MAKE-ACCOUNT"
                                m))))
dispatch)

//자바에서 make-account 객체를 정의하고 그 안에 withdraw와 deposit 메서드를 만든 것과  비슷하다.

(define acc (make-account 100))

((acc 'withdraw) 50) => 50


3.1.2  덮어쓰기가 있어서 좋은 점

변수를 물체에 가두어 놓고 그 값을 알맞은 때에 덮어써서, 물체 속에 상태를 숨겨두는 기법을 알게 되었다. 앞서 프로시저에 인수를 건네주는 방법만 쓰면, 모든 상태를 밖으로 꺼내놓을 수밖에 없다. 모듈 방식으로 프로그램을 짤 때 물체 속에 변하는 상태를 감춰두는 방법이 더 좋다.


3.1.3  덮어쓰기를 끌어들인 대가

함수형 프로그래밍: 덮어쓰기 없이 프로그램 짜는 법

(define (make-simplified-withdraw balance)
   (lambda (amount)
       (set! balance (- balance amount))
       balance))

(define W (make-simplified-withdraw 25))

(W 20) => 5

(W 10) => -5
/*계산과정
((make-simplified-withdraw  25) 20)

((lambda (amount) (set! balance (- 25 amount)) 25) 20)
                              //(balance == 5) == 25
                              //맞바꿈 계산법으로 계산하면 모순이 생김
                              //여기서 깨달아야 하는 중요한 점!!!
                              //변수는 값의 이름이 아니다!!!
                              //변수는 값을 넣어둔 자리의 이름이다!!!
*/

(define (make-decrementer balance)
    (lambda (amount)
        (- balance amount)))

(define D (make-decrementer 25))

(D 20) => 5

(D 10) => 15
/* 계산과정
((make-decrementer  25)  20)

((lambda  (amount) (-  25  amount))  20)

(- 25  20)
*/

같음과 달라짐(변함)

'같다'의 개념 따져보기

(define D1 (make-decrementer 25)) == (define D2 (make-decrementer 25))

(define W1 (make-simplified-withdraw 25)) != (define W2 (make-simplified-withdraw 25))
이유)
(W1 20) => 5

(W1 20) => -15

(W2 20) => 5

=> W1 와 W2가 같은 식을 계산하여 만들어 낸 물체이기는 하지만
   '어떤 식에서 W1을 W2로 맞바꾸더라도 계산 결과가 달라지지 않는다.'라는 관점에서는          틀린 말이다.

=>'같은 것을 같은 것으로 맞바꿀 수 있다'는 원칙에 따라 식을 계산할 때 식의 값이 달라지지 않는다는 성질이 뒷받침된다면, 그런 언어를 '뜻이 한결같은' 언어라고 한다.

명령을 내려서 프로그램 짤 때 생기는 문제

 명령 중심 프로그래밍: 덮어쓰기처럼 명령을 내려서 프로그램을 짜는 방식

(define (factorial n)
   (define (iter product counter)
        (if (> counter n)
            product
            (iter (* counter product)
                   (+ counter 1))))
  (iter 1 1))

(define (factorial n)
   (let ((product 1)
          (counter 1))
      (define (iter)
          (if (> counter n)
              product
               (begin (set! product (* counter product)) // 두 식의 순서만 바뀌어도
                          (set! counter (+ counter 1))         // 답이 다르게 나온다.
                          (iter))))
  (iter)))


3.2  환경 계산법
      //맞바꿈 계산법말고 새로운 계산법

 환경: 변수 일람표를 한 줄로 이어놓은 것
          //변수값을 정의하여 모아둔 표




3.2.1  계산 규칙

환경 계산법에서 프로시저는 코드와, 환경을 가리키는 꼬리를 쌍으로 묶어서 나타낸다.

(define square (lambda (x) (* x x)))


- 프로시저를 여러 인자에 적용하기 위해서는, 먼저 새 변수 일람표를 하나 만들고 그 일람표에서 인자의 이름과 값을 묶어 인자를 정의한다. 그 다음, 새로 만든 환경을 계산 문맥으로 보고 프로시저의 몸을 계산한다. 새 일람표를 둘러싸는 환경은 프로시저 물체가 가리키던 환경이다.

- 어떤 환경에서 lambda 식을 계산하면 새 프로시저 물체가 나온다. 이 물체는 코드와 환경 꼬리를 쌍으로 묶어 만들어진다. 코드는 lambda 식을 그대로 가져온 것이고, 꼬리가 가리키는 환경은 lambda 식의 값을 구할 때 쓴 환경이다.

- define으로 새 이름을 정의하면, 그 이름이 define을 계산하는 환경에서 정의된다.

- 어떤 환경에서 set!을 계산하려면, 그 환경에서 변수가 정의된 곳을 찾아낸 다음에 그 변수의 값을 덮어쓰면 된다.


3.2.2  간단한 프로시저 계산하기


3.2.3  물체에 상태를 넣어두는 곳, 변수 일람표
  ex)
(define (make-withdraw balance)
    (lambda (amount)
        (if (>= balance amount)
            (begin (set! balance (- balance amount))
                       balance)
           "Insufficient funds")))


=>
(define W1 (make-withdraw 100))

=>
(W1 50)
=>


(define W2 (make-withdraw 100))으로 두 번째 물체를 만들 때


3.2.4  안쪽 정의

(define (sqrt x)
   (define (good-enough? guess)
       (< (abs (- (square guess)  x)) 0.001))
    (define (improve guess)
       (average guess (/ x guess)))
   (define (sqrt-iter guess)
       (if (good-enough? guess)
           guess
           (sqrt-iter (improve guess))))
   (sqrt-iter 1.0)) 


- 프로시저를 안쪽에 가두어 정의하면, 그 프로시저 이름은 저를 둘러싼 프로시저 밖에 있는 이름과 뒤섞일 염려가 없다. 갇힌 프로시저는, 프로시저를 불러쓸 때마다 새로 생긴 일람표 속에서 정의되기 때문이다.
- 안쪽에 가두어 정의한 프로시저는 저를 둘러싼 프로시저의 인자를 자유 변수처럼 쓸 수 있다. 갇힌 프로시저를 불러쓰는 환경이, 저를 감싸는 프로시저의 적용 환경에 둘러싸여 있기 때문이다.


2015년 1월 27일 화요일

자바 Console 예제

*예제는 남궁성님의 코드 초보 스터디 카페에 있는 Java 1000제에서 가져왔습니다.

예제를 실행하면 '>>'와 같은 프롬프트가 나타나서 사용자의 입력을 기다리고, 사용자가 입력한 내용을 화면에 출력한다. 만일 사용자가 'q' 또는 'Q'를 입력하면 프로그램을 종료한다. 다음의 예제를 완성하세요

import java.util.*;
public class ConsoleEx1 {
public static void main(String[] args) { 
         while(true) { 
               String prompt = ">>"; 
               System.out.print(prompt); 
               
            Scanner s = new Scanner(System.in);
            String input = s.nextLine();
            System.out.println(input);
            if(input.equalsIgnoreCase("q")){
            break;
            }
       } // while(true) 
   } // main 
} // class


사용자가 입력한 명령라인을 공백(' ')을 구분자로 해서 잘라서 배열에 저장한 다음에 배열의 내용을 출력하는 예제. 코드를 완성하세요.

class ConsoleEx2 { 
      static String[] argArr;       // 입력한 매개변수를 담기위한 문자열배열 

      public static void main(String[] args) { 
            while(true) { 
                  String prompt = ">>"; 
                  System.out.print(prompt); 
                  
                  // 화면으로부터 라인단위로 입력받는다. 
                  Scanner s = new Scanner(System.in); 
                  String input = s.nextLine();

                  String[] argArr = input.trim().split(" +");     
                   // 정규표현식으로서 얼마간의 공백이든 공백 하나로 처리한다
                   // == split("\\s+");    
                  if(input.equalsIgnoreCase("Q")) { // q 또는 Q를 입력하면 실행종료한다. 
                        System.exit(0); 
                  } else { 
                        for(int i=0; i < argArr.length;i++) { 
                              System.out.println(argArr[i]); 
                        } 
                        /* 위의 코드를 향상된 for문으로 변경하면 다음과 같다.
                        for(String arg : argArr) { 
                              System.out.println(arg);                                                       
                        } 
                        */ 
                  } 
            } // while(true) 
      } // main 
} // class

사용자가 입력한 명령라인의 내용을 저장하는 save(String input)메서드와 사용자가 입력한 명령라인의 이력을 보여주는 history()메서드를 완성하세요.

import java.util.*; 
class ConsoleEx3 { 
      static String[] argArr;                         // 입력한 매개변수를 담기위한 문자열배열 
      static LinkedList q = new LinkedList(); // 사용자가 입력한 내용을 저장할 큐(Queue) 
      static final int MAX_SIZE = 5;              // q(큐)에 저장될 수 있는 값의 개수

      public static void main(String[] args) { 
            Scanner s = new Scanner(System.in); // 한번만 생성해서 재사용하면 되므로 반복문 밖으로 이동
            while(true) { 
                  String prompt = ">>"; 
                  System.out.print(prompt); 
                  
                  // 화면으로부터 라인단위로 입력받는다. 
                  String input = s.nextLine(); 

                  save(input); 

                  input = input.trim();        // 입력받은 값에서 불필요한 앞뒤 공백을 제거한다. 
                  argArr = input.split(" +   "); 

                  String command = argArr[0].trim(); 

                  if("".equals(command)) continue; 

                  command = command.toLowerCase(); // 명령어를 소문자로 바꾼다. 

                  if(command.equals("q")) { // q 또는 Q를 입력하면 실행종료한다. 
                        System.exit(0); 
                  } else if(command.equals("history")) {  // equalsIgnoreCase대신 equals를 사용.
                        history(); 
                  } else { 
                        for(int i=0; i < argArr.length;i++) { 
                              System.out.println(argArr[i]); 
                        } 
                  } 
            } // while(true) 
      } // main


      public static void save(String input) { 
            if(input==null || "".equals(input)) return;

            q.add(input);
            if(q.size()>MAX_SIZE){
            q.poll();
            }
      } 
      // 사용자가 입력한 최근 명령을 보여주는 메서드
      public static void history() { 
           for(int i = 0; i < q.size(); i++){
         Object aa = q.get(i);
         
         System.out.println((i+1)+"."+aa);
         
           }
      } 
} // class

사용자 입력을 받는 프롬프트에 현재 작업중인 폴더(디렉토리)의 경로를 표시하는 예제의 코드를 완성하세요.

import java.util.*; 
import java.io.*; 
class ConsoleEx4 { 
      static String[] argArr;                         // 입력한 매개변수를 담기위한 문자열배열 
      static LinkedList q = new LinkedList(); // 사용자가 입력한 내용을 저장할 큐(Queue) 
      static final int MAX_SIZE = 5;              // Queue에 최대 5개까지만 저장되도록 한다. 

      static File curDir;                         // 현재 디렉토리 

      static {
    try{
    String systemType = "user.dir";
     File tempFile = new File(systemType);
     curDir = tempFile.getAbsoluteFile();
    }catch(Exception e){
    System.out.println(e.getMessage());
    }
         
      } 
      public static void main(String[] args) {
            Scanner s = new Scanner(System.in); // 한번만 생성해서 재사용하면 되므로 반복문 밖으로 이동

            while(true) { 
                  try { 
                        String prompt = curDir.getCanonicalPath() + ">>"; 
                        System.out.print(prompt); 
                  
                        // 화면으로부터 라인단위로 입력받는다. 
                        String input = s.nextLine(); 

                        save(input); 

                        input = input.trim();          // 입력받은 값에서 불필요한 앞뒤 공백을 제거한다. 
                        argArr = input.split(" +"); 

                        String command = argArr[0].trim(); 

                        if("".equals(command)) continue; 

                        command = command.toLowerCase(); // 명령어를 소문자로 바꾼다. 

                        if(command.equals("q")) { // q 또는 Q를 입력하면 실행종료한다. 
                              System.exit(0); 
                        } else if(command.equals("history")) { 
                              history(); 
                        } else { 
                              for(int i=0; i < argArr.length;i++) { 
                                    System.out.println(argArr[i]); 
                              } 
                        } 
                  } catch(Exception e) { 
                        System.out.println("입력오류입니다."); 
                  }                   
            } // while(true) 
      } // main

      public static void save(String input) { 
            if(input==null || "".equals(input)) return; 

            q.add(input); // queue에 저장한다.

            // queue의 최대크기를 넣으면 제일 오래된 저장값을 삭제한다. 
            if(((LinkedList)q).size() > MAX_SIZE) 
                  q.remove(); 
      } 

      public static void history() { 
            int i=0; 

            // LinkedList의 내용을 보여준다. 
            ListIterator it =q.listIterator(); 

            while(it.hasNext()) { 
                  System.out.println(++i+"."+it.next()); 
            } 
      } 
} // class

//여기서 접는다 도움이 안되는 거 같다.

정규표현식(자바 Console 예제)

정규표현식

사전적 의미)
특정한 규칙을 가진 문자열의 집합을 표현하는데 사용하는 형식 언어






자바 Quiz 예제

*예제는 남궁성님의 코드 초보 스터디 카페에 있는 Java 1000제에서 가져왔습니다.

 주어진 문자열 데이터를 가지고 실행결과와 같이 출력되도록 코드를 완성하세요.
 데이터의 형식은 문제, 답, 선택지의 순서로 되어 있으며 구분자는 `(숫자1옆의 키)입니다.

[실행결과]
[1] 다음 중 키워드가 아닌 것은?
1.final 2.True  3.if    4.public

[2] 다음 중 자바의 연산자가 아닌 것은?
1.&     2.|     3.++    4.!=    5./     6.^

[3] 다음 중 메서드의 반환값이 없음을 의미하는 키워드는?
1.void  2.null  3.false
public static void main(String[] args) { 
           String[] data = { 
                 "다음 중 키워드가 아닌 것은?`2`final`True`if`public", 
                 "다음 중 자바의 연산자가 아닌 것은?`5`&`|`++`!=`/`^", 
                 "다음 중 메서드의 반환값이 없음을 의미하는 키워드는?`1`void`null`false", 
           }; 
           for(int i=0;i<data.length;i++) {
            String[] dataArray = {};
            String[] dataArray2 = {};
            dataArray2=data[i].split("`",2);
                System.out.println(dataArray2[0]);
                dataArray=dataArray2[1].split("`");
                 for(int j =0; j<dataArray.length;j++){
                 System.out.print(dataArray[j]+" ");
                 }System.out.println();
           } 
     }
}


문제1에서 완성된 코드에 사용자 입력을 받아서 정답여부를 판단하여 마지막에 최종점수 실행결과와 같이 출력하도록 코드를 완성하세요.

[실행결과]
[1] 다음 중 키워드가 아닌 것은?
1.final 2.True  3.if    4.public
[답]2

[2] 다음 중 자바의 연산자가 아닌 것은?
1.&     2.|     3.++    4.!=    5./     6.^
[답]6

[3] 다음 중 메서드의 반환값이 없음을 의미하는 키워드는?
1.void  2.null  3.false
[답]1

정답개수/전체문항수 :3/3

import java.util.*;
public class QuizEx2 {
     public static void main(String[] args) { 
           String[] data = { 
                 "다음 중 키워드가 아닌 것은?`2`final`True`if`public", 
                 "다음 중 자바의 연산자가 아닌 것은?`6`&`|`++`!=`/`^", 
                 "다음 중 메서드의 반환값이 없음을 의미하는 키워드는?`1`void`null`false`", 
           }; 
           
           Scanner s = new Scanner(System.in); 
           int score = 0; 

           for(int i=0;i < data.length;i++) { 
                 String[] tmp = data[i].split("`",3); 

                 String question = tmp[0]; 
                 String answer = tmp[1]; 
                 String[] choices = tmp[2].split("`"); 

                 System.out.println("["+(i+1)+"] "+question); 
                 
                 for(int x=0;x < choices.length;x++) { 
                       System.out.print((x+1)+"."+choices[x]+"\t"); 
                 } 
                 System.out.println(); 
                 String solution = s.nextLine();
                 if(solution.equals(tmp[1])){
                 score++;
                 }
               
 
                 System.out.println(); 
                 System.out.println(); 
           } 
          
          System.out.print("정답개수/전체문항수: "+score+"/3");
     } // main 
}

문제2에서 완성된 코드를 이용해서, 문제의 순서를 임의로 바꾸고, 선택지도 임의로 바뀌어 출력되도록 하세요.

[실행결과]
[1] 다음 중 자바의 연산자가 아닌 것은?
1./     2.|     3.++    4.!=    5.^     6.&
[답]5

[2] 다음 중 키워드가 아닌 것은?
1.final 2.True  3.if    4.public
[답]2

[3] 다음 중 메서드의 반환값이 없음을 의미하는 키워드는?
1.null  2.void  3.false
[답]2

정답개수/전체문항수 :3/3

import java.util.*; 
class QuizEx3 { 
      public static void main(String[] args) { 
            String[] data = { 
                  "다음 중 키워드가 아닌 것은?`2`final`True`if`public", 
                  "다음 중 자바의 연산자가 아닌 것은?`6`&`|`++`!=`/`^", 
                  "다음 중 메서드의 반환값이 없음을 의미하는 키워드는?`1`void`null`false`", 
            }; 
            
            Scanner s = new Scanner(System.in); 
            int score = 0; 

            shuffle(data); // 문제를 섞는다.

            for(int i=0;i < data.length;i++) { 
                  String[] tmp = data[i].split("`",3); 

                  String question = tmp[0]; 
                  String answer = tmp[1]; 
                  String[] choices = tmp[2].split("`"); 

                  shuffle(choices); // 선택지를 섞는다.

                  System.out.println("["+(i+1)+"] "+question); 

                  for(int x=0;x < choices.length;x++) { 
                 System.out.print((x+1)+"."+choices[x]+"\t");
                  } 

                  System.out.println(); 
                  System.out.print("[답]"); 
                  String input = s.nextLine(); 
                  
                  if(input.equals(answer)) { 
                        score++; 
                  } 

                  System.out.println(); 
                  System.out.println(); 
            } 

            System.out.println("정답개수/전체문항수 :"+score+"/"+data.length); 
      } // main 

      public static void shuffle(String[] data) {
           // 코드를 완성하세요.
           if(data.length<=0){
         System.out.print("장난치냐? 잘하자~ 응?");
       
           }else{
          for(int i=0; i<data.length*2;i++){
         int idx = (int) (Math.random()*data.length);
          String temp = data[0];
          data[0]=data[idx];
          data[idx]=temp;
           }
           //  ScrambledWord 문제의 적용
      } // shuffle() 
   }
}






[출처] [Java1000제] Simple Quiz 2 - 간단한 다지선다형 문제 (남궁성의 코드초보스터디(자바 java, c언어)) |작성자 남궁성