문제 링크 : MORSE

문제 링크 : DICT


두 문제 다 같은 개념의 문제이다. 책을 보고도 쉽게 이해가 되지 않아서 머리가 아팠다...

MORSE문제에서의 -를 DICT의 a, o를 DICT의 b로 생각하고 풀면 된다.

이 문제는 k번째 위치에 있는 값을 출력하는 문제인데, 물론 일일이 사전 순서대로 출력하다가, 완성된 부호가 k번째 부호가 아니면, 그 다음 순서의 부호를 찾는 

무식하게 푸는 방법으로도 해결은 가능하다. 근데 문제는 k의 범위가 1억이다. 그냥 무식하게 풀었다가는 시간이 초과될 가능성이 높다.

그래서 여기서 해결하는 방법은, '현재 조합하려는 경우의 수가 찾고자 하는 순서보다 크면 패스, 작으면 찾아나가는' 방법이다.

예를 들어 n개의 -와 m개의 o로 조합을 하는 경우의 수는 이다. 수학을 안한지 오래되어 순열 조합을 까먹어서 기억을 더듬느라 머리가 아팠는데, 복수 순열로 n,m개씩 있을 때, 이를 늘어놓는 경우의 수가이고, 이게  이기 때문이다. 그냥 조합으로 이해하면 될 것 같은데 일단 이렇게 이해하고 넘어갔다...

즉, 현재 구하려는 수가 지금 조합하려는 부호의, 전체 조합 범위에 들어있지 않으면 현재의 구하는 조합의 경우에는 존재하지 않기 때문에, 그 이후는 검색할 필요가 없다는 것이다.


코드 작성 전에 간단한 예를 들어보자.

'-' 2개와 'o' 2개로 조합하는 부호에서, 4번째 수를 찾는다고 하자. 저 요소들로 조합이 가능한 개수는 4C2로, 6가지가 가능하다. 즉, 4번째는 현재 전체 갯수 6개보다 작으므로, 아직 구할 수 있다.

그럼 사전식 순서로 -를 하나 문장에 넣어 이어가보자. 그럼 이제 남은 요소는 '-' 1개와 'o' 2개이다. 이 요소로 가능한 개수는 3C1, 즉 3이다. 

이 말은 첫 글자가 -로 시작하는 문장이 3개(3C1)개라는 말이다. 4번째 문자는, 이 범위를 초과하므로 '-'로 시작하는 것이 아닌 'o'로 시작하는 부호임을 알 수 있다. 

이를 알았으니 이제 '-'가 아닌 'o'로 시작하는 문장중에서, 4-3C1=1번째 시작하는 문장을 찾으면 된다. 이를 지금가지와 같은 방식으로 풀이하면 'o--o'를 구할 수 있다.

밑은 위와 같은 로직으로 구하는 소스코드이다.


#include <iostream>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;
const int M = 1000000000+100;
int bino[201][201];

//파스칼의 삼각형으로 조합들을 미리 구해놓는다. 
void calcBino(){
    memset(bino,0,sizeof(bino));
    for(int i = 0; i<=200;++i){
        bino[i][0] = bino[i][i] = 1;
        for(int j = 1; j<i;++j)
            bino[i][j] = min(M, bino[i-1][j-1]+bino[i-1][j]); // 파스칼의 삼각형 점화식
    }
}
int skip;
void gen(int n, int m, string s){
    //기저사례1 : skip이 0보다 작으면 범위를 벗어난 것이므로 리턴한다. 
    if(skip <0) return;
    //기저사례2 : 더이상 조합할 요소가 없으므로 부호 완성. 
    if(n==0&&m==0){
        cout<<s<<endl;
        --skip;
        return;
    }
    //찾고자 하는 수가 현재 조합할수 있는 갯수를 넘어서면, 
    //현재 부호에 존재하지 않으므로 뛰어넘고 반환한다. 
    if(bino[n+m][n]<=skip){
        skip-=bino[n+m][n];
        return;
    }
    //각 요소가 존재하면 조합한다.  
    if(n>0)gen(n-1,m,s+"-");
    if(m>0)gen(n,m-1,s+"o");
}
int main(){
    int n,m,k;
    cin>>n>>m>>k;
    skip = k-1;
    calcBino(); 
    gen(n,m,"");
}


위의 코드는 구하고자 하는 부호가 현재 범위에 포함되는 지의 여부를 재귀 호출로 넘어가서 검사하였지만, 이렇게 할 필요 없이 검사한 후 뛰어넘는 코드를 작성할 수 있다. 그 코드는 다음과 같다.

 

#include <iostream>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;

const int M = 1000000000+100;
int n,m,k,c,bino[201][201];
//파스칼의 삼각형을 통해 조합을 구해놓는다. 
void calcBino(){
    memset(bino,0,sizeof(bino));
    for(int i = 0; i<=200;++i){
        bino[i][0] = bino[i][i] = 1;
        for(int j = 1; j<i;++j)
            bino[i][j] = min(M, bino[i-1][j-1]+bino[i-1][j]); // 파스칼의 삼각형 점화식 
    }
}

int skip = 3;
string gen(int n, int m, int skip){
    //n이 더이상 없으면, o만 존재하므로 m을 반환한다. 
    if(n==0) return string(m,'o');
    
    //'-'를 선택했을경우의 조합의 범위 내에  구하고자 하는 부호를 포함되면
    //'-'를 추가하고 재귀호출한다. 
    if(skip<bino[n-1+m][n-1])
        return "-"+gen(n-1,m,skip);
    //포함되지 않으면 'o'를 추가하고, '-'가 가능한 수만큼 패스하여 다시 검사한다. 
    return "o"+gen(n,m-1,skip-bino[n+m-1][n-1]);
}
int main(){
    cin>>c;
    while(c--){
    cin>>n>>m>>k;
    skip = k-1;
    calcBino(); 
    cout<<gen(n,m,skip)<<endl;
    }
}


'Algorithm > Problems' 카테고리의 다른 글

더블릿 - 댐  (0) 2016.02.26
알고스팟 - NQUEEN, 백준 - 9663 N-QUEEN  (1) 2016.02.25
백준 - 1963 소수경로  (0) 2016.02.24
백준 - 2960 에라토스테네스의 체  (1) 2016.02.24
백준 - 1541 잃어버린 괄호  (0) 2016.02.23


문제 링크


문제에서 풀이 방법을 가르쳐주었기 때문에 그냥 그 로직대로 따라가 풀었다. 코드 라인이 쓸대없이 길어져서 귀찮았던 것 같다.

나는 에라스토테네스의 체로 소수들을 전부 구하고 시작했는데, 풀고 보니 에라스토테네스의 체같은건 필요 없고, 그냥 평범하게 소수를 구하거나 그때그때 판별해도 상관없는 문제였다...

어쨌든 각 각 자리수를 바꿀수 있는 한도 내에서 바꾼 뒤, 해당 숫자의 이전 방문 여부와 에라스토테네스의 체로 구한 배열에서 소수인지 판별하고 큐에 넣어서 반복해주었다.

처음엔 그냥 숫자로만 풀이하려다가 숫자를 배열로, 배열을 숫자로 바꿔서 풀이하였다. 근데 바꾸나 마나였단 생각이 든다...

밑에는 풀이 코드이다.




#include <cstdio>
#include <cmath>
#include <cstring>
#include <queue> 
using namespace std;
int t, x, y, a[10000] = { 0, }, visited[10000] = { 0, }, num[4];
typedef struct {
    int num, count;
}Prime;
queue<Prime> q;
//에라토스테네스의 체로 9999이하의 소수들을 모두 구한다. 
void era(int n) {
    int i;
    while (true) {
        for (i = 2; i <= n; i++)
            if (a[i] == 0) break;
        if (i>n) return;
        a[i] = 1;
        for (int j = 2; j*i <= n; j++) {
            if (a[i*j] == 0) {
                a[i*j] = 2;
            }
        }
    }
}
//배열을 숫자로 바꾼다.
int numToArr(){
    int ret=0;
    for(int i=0; i<4;i++){
        ret+=num[i]*pow(10,3-i);
    }
    return ret;
}
//자리수를 한번 바꾼 값이 소수인지 판별하고, 소수이면 큐에 넣는다.
void check(int i,int count){
    int k = i==0?0:1;
    int bak = num[i];
    while(num[i]-1>=1-k){
        num[i] -=1;
        int n = numToArr();
        if(a[n]==1&&visited[n]==0){
            Prime p1 = {n,count+1};
            visited[n]=1;
            q.push(p1);
        }
    }
    num[i]=bak;
    while(num[i]+1<=9){
        num[i] +=1;
        int n = numToArr();
        if(a[n]==1&&visited[n]==0){
            Prime p1 = {n,count+1};
            visited[n]=1;
            q.push(p1);
        }
    }
    num[i]=bak;
}
//너비우선 탐색.
int dfs() {
    Prime p = { x,0 };
    q.push(p);
    while (!q.empty()) {
        p = q.front(); q.pop();
        //값을 찾았으면 카운트값을 반환 
        if (p.num == y) return p.count;
        //숫자를 배열로 변경 
        for(int i=0;i<4;i++){
            num[i] = p.num/pow(10,3-i);
            p.num = p.num-num[i]*pow(10,3-i);
        }
        for(int i=0;i<4;i++){
            check(i,p.count);
        }
    }
    //못찾았으면 -1을 반환한다. 
    return -1;
}
int main() {
    era(9999);
    scanf("%d",&t);
    while(t--){
        scanf("%d %d", &x, &y);
        int ret = dfs();
        printf(ret==-1?"Impossible\n":"%d\n",ret);
        while(!q.empty())q.pop();
        memset(visited,0,sizeof(visited));
    }
}



'Algorithm > Problems' 카테고리의 다른 글

알고스팟 - NQUEEN, 백준 - 9663 N-QUEEN  (1) 2016.02.25
알고스팟 - MORSE, DICT  (0) 2016.02.25
백준 - 2960 에라토스테네스의 체  (1) 2016.02.24
백준 - 1541 잃어버린 괄호  (0) 2016.02.23
백준 - 1946 신입사원  (0) 2016.02.23


문제 링크


소수와 관련된 다른 문제를 풀기위해 자세히 알아보게된 에라토스테네스의 체이다. 에라토스테네스의 체란 빠르게 소수들을 구하는 방법이다. 즉 특정수 n 이하의 모든 소수를 구하고 싶으면, n이하의 수중 합성수들을 지워나가면, 남은 숫자들은 소수가 된다는 것에서 착안한다.

먼저 2 이상의 가장 작은 수중, 아직 지우지 않는 수를 찾는다.  아직 하나도 지우지 않았으므로 2를 선택한다. 2는 소수이다, 그럼 2의 배수들은 전부 합성수들이므로, 2의 배수들은 모두 지워나간다.

그다음에 지워지지 않은 가장 작은 수는 3이다. 3은 소수이고, 3의 배수들은 전부 합성수이므로 3의 배수는 전부 지워나간다. 숫자를 다 지울때까지 반복한다.

이게 바로 에라토스테네스의 체이다. 





빠른 이해를 위한 이미지. 출처 위키피디아


이 문제는 에라토스테네스의 체처럼 소수를 구하는 문제와는 거리가 멀고, 에라토스테네스의 체가 어떤 순서로 숫자들을 지워나가고, 원하는 순서의 숫자를 구하는 문제이다. 그냥 이는 에라토스테네스의 체를 구현한 다음 해당 번째에서 출력하고 종료하면 되는 문제였다.

밑은 해당 소스코드이다.


#include <cstdio>
int n,k,a[1001]={0,},i,count=0;
int main(){
    scanf("%d %d",&n,&k);
    while(count!=k){
        //지우지 않은 가장 작은 수를 찾는다. 
        for(i=2;i<=n;i++)
            if(a[i]==0) break;
        //다 찾았는데 i가 1000이라면 다 지웠으므로 탈출한다. 
        if(i==1000) break;
        //i번째 수를 1을 채워 지웠음을 표시하고, 차례로 그 배수를 지워나간다. 
        for(int j=1;j*i<=n;j++){
            //만약 지우지 않은 숫자이면 해당 숫자 카운트를 증가시킨다. 
            if(a[i*j]==0){
                a[i*j]=1;
                count++;
            }
            if(count==k){
                printf("%d",i*j);
                return 0;   
            }
        }
    }
}


'Algorithm > Problems' 카테고리의 다른 글

알고스팟 - NQUEEN, 백준 - 9663 N-QUEEN  (1) 2016.02.25
알고스팟 - MORSE, DICT  (0) 2016.02.25
백준 - 1963 소수경로  (0) 2016.02.24
백준 - 1541 잃어버린 괄호  (0) 2016.02.23
백준 - 1946 신입사원  (0) 2016.02.23


문제 링크


greedy 문제이다. 풀이는 간단하다. 괄호 개수의 제한이 없기 때문에 '-'를 만나면, 다음 '-'를 만날 때까지 그 값들을 전부 더한 뒤 계산 값에서 빼주면 된다.

왜냐하면 괄호 앞에 '-'가 있을 경우, 분배 법칙으로 괄호 안의 양수 값들은 전부 '-'로 처리되기 때문이다. 

나는 처음에 '-'를 토큰으로 문자열을 전부 나눈 뒤, 전부 덧셈 연산 처리를 한 다음에 빼줘서 풀었다.

하지만 그럴 필요 없이 그냥 값들을 전부 더해주다가 '-'를 만나면 그때부터 전부 다 빼주면 된다. 어차피 첫 '-'뒤의 '+'들은 전부 분배 법칙으로 '-'가 될 것이며, 다음 '-'를 만나면 그 전에 괄호를 닫고 다시 열게 되기 때문이다.


우선 처음에 그냥 '-'를 기준으로 토큰을 나눈 뒤 풀었던 코드이다. 나눈 토큰 들을 '+'처리한 다음에 첫 문자열을 제외한 나머지 문자열들은 전부 빼줬다.

//-가 나오면, 그 때부터 다음 -가 나올때까지 전부다 괄호를 쳐버리면 된다.
//-를 토큰으로 문자열을 추출한다. 그 문자열 안에 있는 값들의 +연산을 모두 처리하면 된다. 
#include <cstdio>
#include <cstring>
#include <cstdlib>
char cal[51] = { 0, };
char ca[51][51] = {0,};
char* ptr;
int ret,i=0;
//+연산을 처리하는 함수 
int calculate(char* str) {
    int r = 0;
    char* ptr2;
    ptr2 = strtok(str, "+");
    r += atoi(ptr2);
    while (ptr2 = strtok(NULL, "+")) {
        r += atoi(ptr2);
    }
    return r;
}
int main() {
    scanf("%s", cal);
    //-를 토큰으로 분해하고, 각 문자열을 저장한다. 
    ptr = strtok(cal, "-");
    strcpy(ca[i++],ptr);
    while (ptr = strtok(NULL, "-")) {
        strcpy(ca[i++],ptr);
    }
    //분해한 문자열을 전부 +연산 처리를 하고, 첫 수를 제외한 나머지 수는 전부 뺄셈 처리를 한다. 
    ret=calculate(ca[0]);
    for(int j=1;j<i;j++){
        ret -= calculate(ca[j]);
    }
    printf("%d",ret);
}


이 코드만 봐도 알고 보면 첫 '-'이후의 값들은 전부 다 빼주면 된다는 걸 알 수 있다.

다음 코드는 이를 적용해 보았다.


#include <cstdio>
#include <cstring>
#include <cstdlib>
char cal[51] = { 0, };
char ca[51] = {0,};
char* ptr;
int ret,i=0;
//덧셈을 처리하는 함수. 전과 달라진건 -,+을 안가리고 전부 더해버린다. 
int calculate(char* str) {
    int r = 0;
    char* ptr2;
    ptr2 = strtok(str, "+-");
    r += atoi(ptr2);
    while (ptr2 = strtok(NULL, "+-")) {
        r += atoi(ptr2);
    }
    return r;
}
int main() {
    scanf("%s", cal);
    //첫 -를 기준으로 토큰을 나눈다. 
    ptr = strtok(cal, "-");
    strcpy(ca,ptr);
    //첫 - 이후의 문자열을 받는다. 
    ptr = strtok(NULL,"");  
    ret=calculate(ca);
    //첫 - 이후의 문자열은 전부 덧셈 처리한 뒤 빼준다. 
    if(ptr!=NULL)ret-=calculate(ptr); 
    printf("%d",ret);
}

마지막으로는 다른 분 코드를 참조해 짜본, 라이브러리 함수를 쓰지 않고 배열을 하나하나 검사해서 짜는 코드이다.


#include <cstdio> char cal[51] = { 0, }; int ret=0,n=0,i=0,flag=0; int main() { scanf("%s", cal); while(cal[i] != 0){ //숫자이면 if(cal[i] >='0' && cal[i]<='9'){ //이전에 저장한 숫자의 자릿수를 하나 늘려주고 이 값을 더해준다. n*=10; n+=cal[i]-'0'; } //그 후 지금까지 숫자값들을 0으로 초기화해준다. else{ //-연산자를 만난적이 없으면, 즉 flag가 0이면 그냥 더해준다. if(flag==0) ret +=n; //-연산자를 만난적이 있으면, 즉 flag가 1이면 뺄셈처리한다. else ret-=n; //첫 -이면, 즉 -이고 flag가 0이면 flag를 1로 만들어준다. if(cal[i]=='-'&&flag==0) flag=1; n = 0; } i++; } //마지막 숫자를 flag에 맞게 처리해준다. if(flag==0) ret+=n; else if(flag==1) ret-=n; printf("%d",ret); }


'Algorithm > Problems' 카테고리의 다른 글

알고스팟 - NQUEEN, 백준 - 9663 N-QUEEN  (1) 2016.02.25
알고스팟 - MORSE, DICT  (0) 2016.02.25
백준 - 1963 소수경로  (0) 2016.02.24
백준 - 2960 에라토스테네스의 체  (1) 2016.02.24
백준 - 1946 신입사원  (0) 2016.02.23


문제 링크


greedy로 푸는 문제이다.
내가 풀이한 방법은 먼저 서류 등수나 면접 등수를 기준으로 오름차순 정렬을 한다
자신을 제외한 다른 모든 신입 사원과 점수를 비교해서, 무조건 적어도 하나의 항목은 다른 인원보다 높아야 하므로 두 항목 중 하나라도 1등인 지원자는 무조건 선발되기 때문에 1등에 대한 카운트 하나를 증가시켜준다. 만약 서류 등수를 기준으로 오름차순 정렬을 했으면, 서류 등수를 따라 올라가면서, 처음에 서류 1등이 가진 면접 등수보다 등수가 높은 인원을 찾는다. 만약 찾았다면 그 지원자는 서류 1등보다 서류 등수는 낮지만 면접 등수는 높으므로 선발 자격이 된다.
그리고 기준을 방금 찾은 지원자의 면접 등수로 설정하고, 그 새로 기준을 잡은 면접 점수보다 높은 사람을 찾아나간다. 애초 서류 등수 기준으로 오름차순을 했으므로, 그 뒤에 현재 기준의 면접 점수보다 등수가 높은 사람의 서류 등수는 무조건 그 전에 찾은 지원자보다 낮지만 면접 점수는 높게 되기 때문에 선발 조건을 만족할수밖에 없다. --> 즉, 정렬한 이후에는 최장증가부분수열을 찾으면 된다.

이렇게 로직 생각을 하고 처음에 코드를 작성하였다. 

#include <cstdio> #include <algorithm> int t,n,rank[100000][2],r1,r2; int recruit(){ //정렬한다. for(int i = 0; i<n; i++){ for(int j= 1;j<n-i;j++){ if(rank[j][0]<rank[j-1][0]){ std::swap(rank[j][0],rank[j-1][0]); std::swap(rank[j][1],rank[j-1][1]); } } } int min = rank[0][1],count = 1;; for(int i=0;i<n;i++){ if(min>rank[i][1]){ count++; min = rank[i][1]; } } return count; } int main(){ scanf("%d",&t); while(t--){ scanf("%d",&n); //각 사원의 등수 입력 for(int i =0; i<n;i++){ scanf("%d %d",&rank[i][0],&rank[i][1]); } printf("%d\n",recruit()); } }


정렬은 그냥 편하게 버블 소트로 하였더니 시간 초과가 발생하였다. 이를 해결하려고 퀵소트로 바꿔서 다시 정렬을 해보았으나. 이 또한 시간 초과가 발생하였다. 

그러면 입력을 받은 후 정렬을 하는 것이 아니라, 입력 도중에 정렬을 할 필요가 있었다. 고민하던 차에 ,이 문제에서 애초에 '1위부터 N위까지 동석차 없이 결정된다고 가정'했으므로, 그냥 등수에 맞는 배열의 인덱스에다가 저장하면 될 일이었다. 


#include <cstdio>
using namespace std;
int t,n,rank[100001],rx,ry;
int recruit(){
    int min = rank[1],count = 1;
    for(int i=1;i<=n;i++){
        if(min>rank[i]){
            count++;
            min = rank[i];
        }
    }   
    return count;
}
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        //각 사원의 등수 입력 
        for(int i =0; i<n;i++){
            //서류점수 등수 순서대로 면접 점수를 입력받는다. 
            scanf("%d %d",&rx,&ry);
            rank[rx] = ry; 
        }
        printf("%d\n",recruit());
    }
}


그렇게 코드를 다시 작성하였다. 다른 사람들의 코드를 보니 이 방법이나, 정렬 코드를 짜는 것이 아닌 STL의 sort를 통한 정렬은 시간 초과 없이 작동하는 것 같았다.

아마 테스트케이스중에 완전 역순으로 정렬된 테스트케이스가 있어 정렬에 n^2이 걸렸기 때문인 것 같았다. 이 경우에 대한 처리나 머지소트로 정렬했으면 시간초과가 안날것 같기는 하다.

어려운 문제는 아니었지만 너무 틀에 박힌 생각보다는 좀 더 폭넓게 생각하는 버릇이 필요한 것 같다는 생각이 든다..


'Algorithm > Problems' 카테고리의 다른 글

알고스팟 - NQUEEN, 백준 - 9663 N-QUEEN  (1) 2016.02.25
알고스팟 - MORSE, DICT  (0) 2016.02.25
백준 - 1963 소수경로  (0) 2016.02.24
백준 - 2960 에라토스테네스의 체  (1) 2016.02.24
백준 - 1541 잃어버린 괄호  (0) 2016.02.23

클라이언트(브라우저)와 웹서버간의 통신시, 클라이언트는 웹서버에 request(요청) 메세지를 보내고 웹서버는 response(응답)메세지를 보낸다.


request 메시지 구성 :

 <메소드><공백><URI><공백><HTTP 버전>

 리퀘스트 라인 , 대략적인 리퀘스트 내용을 알 수 있다.

 <필드명> : <필드값>
"
"
"

 메시지 헤더 , 클라이언트의 정보나 사용 언어 종류등 리퀘스트의 부가적인 정보를 포함한다

 <메시지 본문>

 POST 메소드로 전송 시 송신 되는 데이터

메소드 

클라이언트가 서버에 어떤 요청을 보낼것인지에 대한 명세. GET,POST,HEAD등이 있다. GET은 지정한 정보를 얻기 위한 방식이고 POST는 데이터를 송신하는 경우에 사용한다. GET방식으로 데이터를 송신시에는 URI에 포함되어 전송되고, POST에 전송시에는 메시지 본문에 포함되어 전송된다. 많은 양의 데이터를 전송시에는 POST가 적합하다.

그 외에 서버의 파일을 삭제하는 DELETE 메소드나 서버에 파일을 치환하거나 새로 생성하는 PUT 메소드등이 있는데 이를 통해 웹서벌 파일 서버와 유사하게 구현할 수도 있다.

reponse 메시지 구성 : 

 <HTTP 버전><공백><스테이터스 코드><공백><응답문구>

 스테이터스 라인, 응답의 상태를 알 수 있다.

 <필드명> : <필드값>
"
"
"

 메시지 헤더 , 서버 정보나 사용 언어 종류등 리스폰스의 부가적인 정보를 포함한다

 <메시지 본문>

 요청의 응답으로 전송하는 데이터(텍스트나 바이너리 데이터 등)

스테이터스 코드 

요청에 대한 결과의 상태를 표시하며 숫자로 나타난다. 대표적인 스테이터스 코드는 다음과 같다.   

1XX : 처리의 경과 상황 등을 통지

2XX : 정상 종료

3XX : 다른 조치가 필요함을 나타낸다.

4XX : 클라이언트 측 오류

5XX : 서버 측 오류

웹페이지의 요청 시 응답 메시지의 메시지 본문에 HTML코드가 삽입되어 전송되는데, 코드 내에 이미지나 동영상과 같은 영상의 출력을 요청하는 태그가 있을 시, 이를 요청하는 리퀘스트 메시지를 전송하게 된다.

즉 요청한 페이지에 이미지 파일이 하나 포함되어 있다면, 페이지를 요청하는 리퀘스트 메시지, 페이지에 포함되어있는 이미지를 요청하는 리퀘스트 메시지, 총 2번의 메시지를 요청하고 응답하게 된다.

 

자바스크립트는 기존 C++,Java와 같은 클래스 기반 객체 지향 언어와는 객체 생성 방법이 다르다.


자바스크립트 자체에 클래스라는 개념이 없기 때문이다. 자바스크립트에서 객체를 생성할 수 있는 방법은 크게 3가지로 나뉜다.

1. 기본 객체(Object() 객체)의 생성자 함수를 이용

2. 객체 리터럴을 이용

3. 생성자 함수 이용


위 순서대로 객체 생성 방법에 대해 하나씩 알아보겠다.


1. 기본 객체(Object() 객체)의 생성자 함수를 이용

Object 객체를 생성하고, 여기에 새로운 프로퍼티를 추가하는 방법이다. 객체는 new 연산자를 이용해서 생성이 가능하므로, 기본 객체를 다음과 같이 생성하자.

var member = new Object()

이 같이 기본 객체를 생성한 뒤에, 필요한 프로퍼티를 추가한다.

member.Id = 'phs1116';
member.pass = '1234';
member.gender = 'male'

이 변수의 타입과 변수를 출력해보자.

console.log(typeof member);
console.log(member);

출력값은 다음과 같다.

object
Object {Id: "phs1116", pass: "1234", gender: "male"}

이로서 member변수가 여러 프로퍼티를 포함한 객체로 생성됬음을 알 수 있다.


2. 객체 리터럴을 이용

그 다음은 1번과 같이 기본 객체를 생성하여 프로퍼티를 추가하는 방법이 아닌, 자체로 바로 객체를 생성하는 방법이다. 방법은 간단한데, 변수를 생성할 때 중괄호({})안에 생성할 객체의 프로퍼티를 정의하면 된다. 안에 아무것도 넣지 않고, 중괄호 만으로 생성을 하면 빈 객체가 생성된다. 프로퍼티를 선언할 땐 다음과 같은 방식으로 선언한다.

프로퍼티 이름 : 프로퍼티 값

1번과 같은 객체를 객체 리터럴 방식으로 생성해보자.

var member = {
  Id : 'phs1116',
  pass : '1234',
  gender : 'male'
};

이렇게 생성한 변수를 확인해보면 이 또한 여러 프로퍼티를 포함한 객체임을 알 수 있다.


3. 생성자 함수 이용

자바스크립트의 함수는 생성자 함수로도 정의할 수 있다. 생성자 함수라는 것은 일정의 형식이 정해진 것이 아니라, 정의한 함수에 new 키워드를 붙여 변수에 호출하면 그 자체로 생성자의 역할을 한다. 생성자 함수와 일반 함수의 구분이 명확치 않기 때문에, 이의 구분을 위해 생성자 함수의 첫 글자는 대문자로 쓰는 것을 권장하고 있다.

생성자 함수를 new 키워드로 이용해서 생성하는 객체는 다음과 같이 작동한다.

1. 먼저 빈 객체를 생성하고, this를 이 객체에 바인딩한다.(new 키워드를 사용하지 않을경우엔 this는 전역객체 window에 바인딩된다.) 또한 생성한 객체의 프로토타입 객체를(__proto__) 자신의 생성자의 프로토타입 프로퍼티로 설정한다.

2. 생성자 내부에 this 키워드로 정의된 프로퍼티들을 객체 내에 동적으로 생성한다.

3. 생성자 내부에 return문이 따로 명시되어있지 않으면, 생성한 객체를 리턴한다. 뿐만 아니라 this 키워드를 리턴해도 생성한 객체를 반환한다. (new 키워드를 사용하지 않았고(즉 함수로 사용하였고), return문을 명시하지 않았을 경우에는 undefined가 반환된다.)



생성자는 다음과 같이 정의한다.

var Member = function(Id,pass,gender) {
    this.Id  = Id;
    this.pass = pass;
    this.gender = gender;
}
var Member = function(Id,pass,gender) {
    this.Id  = Id;
    this.pass = pass;
    this.gender = gender;
    return this;
}

위 생성자를 다음과 같이 new 키워드로 호출하고, console.log로 출력해보면

var member = new Member('phs1116','1234','male');

console.log(member);
console.dir(member);

Member {Id: "phs1116", pass: "1234", gender: "male"}

가 출력되어 member 변수가 Member 생성자의 객체로 생성됬음을 알 수 있다.


부모 객체의 프로토타입을 가르키는 [[Prototype]]프로퍼티(__proto__)도 Member 생성자의 프로토타입 객체로 설정됬음을 확인할 수 있다.


기본 객체를 이용하는 방법이나, 객체 리터럴을 이용하는 방법은 같은 형태의 객체를 다시 생성할 수 없다. 같은 형태의 여러 객체를 생성하고자 한다면 생성자 함수를 정의하여 사용해야 할 것이다.

또한 생성자 함수를 이용해서 객체를 생성하면 프로토타입 객체(__proto__)는 생성자의 프로토타입 프로퍼티로 설정하지만, 객체 리터럴은 Object 객체의 프로토타입으로 설정된다. 이는 자바스크립트에서 객체를 생성할 때, 자동적으로 프로토타입 객체가 생성자의 프로토타입 프로퍼티로 설정되는데, 객체 리터럴을 사용할경우 Object()생성자를 사용하여 생성하는 것과 같기 때문이다.


웹개발을 시작하기 전부터 질리도록 들었던 단어가 있다. 바로 MVC 패턴으로, 이는 웹개발 관련 블로그나 커뮤니티에서 하루도 빠짐없이 언급 될 정도로 웹 개발 하면 필수적으로 알아야 할 디자인 패턴이다. MVC패턴에 대해 설명하기 전에 모델 1 구조와 모델 2 구조를 먼저 설명하고자 한다.

JSP로 구성할 수 있는 웹 어플리케이션의 아키텍쳐는 크게 모델 1, 모델 2로 나뉜다. 이 두 모델의 큰 차이점은 JSP가 결과의 출력 뿐만 아니라 요청에 대한 모든 로직들을 처리하냐, JSP는 결과의 출력만 담당하냐의 차이이다.


1. 모델 1 구조


모델 1은 뷰와 로직을 모두 JSP 페이지 하나에서 처리하는 구조를 말하며, 위 사진과 같은 구성을 띈다. 모델 1을 구성하는 요소는 크게 나누면 다음과 같다.

1. JSP

2. 자바빈 혹은 서비스 클래스

 JSP 페이지 내에 로직 처리르 위한 자바 코드가 출력을 위한 코드와 함께 섞여 삽입된다. 브라우져에서 요청이 들어오면 JSP 페이지는 자신이 직접 자바빈이나 따로 작성한 서비스 클래스를 이용하여 작업을 처리하고, 그 처리한 정보를 클라이언트에 출력한다. 과거에 가장 많이 사용되었던 아키텍쳐로, 간단한 페이지를 구성하거나 여러 책의 초반 예제로 많이 사용되기때문에 익숙한 방식이다. 모델 1은 다음과 같은 장점과 단점을 가지고있다.


 장점

 단점

- 구조가 단순하여 익히기가 쉽다.

- 위와 같은 이유로 숙련된 개발자가 아니더라도 구현이 용이하다.

- 출력을 위한 뷰 코드와 로직 처리를 위한 자바 코드가 함께 섞이기 때문에 JSP 코드 자체가 복잡해진다.

- JSP 코드에서 백엔드와 프론트엔드가 혼재되기 때문에 분업이 용이하지 않다.

- 코드가 복잡해져 유지보수가 어렵다.



2. 모델 2 구조



모델 2는 모든 처리를 JSP 페이지 하나가 담당하는것과는 달리 JSP페이지와 서블릿, 그리고 로직을 위한 클래스가 나뉘어 브라우저 요청을 처리한다. 모델 2를 구성하는 요소는 다음과 같다.

1. 서블릿

2. JSP

3. 자바빈 혹은 서비스 클래스

  요청이 들어오면 요청에 대한 로직 처리는 이를 처리할 모델(Model)인 서비스클래스 혹은 자바빈이 담당하고, 요청 결과는 유저에게 결과를 보여줄 뷰(View)단인 JSP에 출력되며,  이를 위한 모든 흐름 제어는 컨트롤러(Controller)서블릿에서 담당한다.



3. MVC 패턴과 모델2

즉 모델 2 구조란 MVC(Model-View-Controller) 패턴을 웹 개발에 도입한 구조이며. 모델 2와 MVC 패턴의 형태는 완전히 같은 형태를 가진다.

MVC 패턴

모델 2

 

Model

서비스 클래스 or 자바빈 

 비즈니스 로직을 처리하는 모든것들이 모델에 속한다. 컨트롤러로부터  특정 로직에 대한 처리 요청(ex: 게시판 글쓰기, 회원가입, 로그인 등)이 들어오면 이를 수행하고 수행 결과를 컨트롤러에 반환한다. 필요한 정보는 request 객체나 session 객체에 저장하기도 한다.

View 

JSP 페이지 

 클라이언트에 출력되는 화면을 말한다. 모델1과 달리 로직 처리를 위한 코드가 내포되어 있지 않다. 요쳥 결과의 출력 뿐만 아니라 컨트롤러에 요청을 보내는 용도로도 사용된다. request 객체나 session 객체에 저장된 정보를 토대로 화면을 출력한다.

Controller 

서블릿 

 MVC 패턴(모델 2)모든 흐름 제어를 맡는다. 브라우져로부터 요청이 들어오면, 어떤 요청인지를 분석하여 이 요청을 처리하기 위한 모델을 사용하여 처리한다. 사용한 모델로부터 처리 결과를 받으면 추가로 처리하거나 가공해야할 정보가 있다면 처리 후 request 객체나 session객체에 저장하고, 뷰(JSP 페이지)를 선택하여 foward나 redirect 하여 클라이언트에 출력한다.


모델 2(MVC 패턴)은 모델1의 단점에 의해 탄생했기 때문에, 모델1이 가지고있는 모든 단점들을 해소하였지만 반대로 다루기 어렵다는 단점이 있다.


 장점

 단점

- 출력을 위한 뷰 코드와 로직 처리를 위한 자바 코드를 분리하기 때문에 JSP 모델1에 비해 코드가 복잡하지 않다.

- 뷰, 로직처리에 대한 분업이 용이하다.

- 기능에 따라 분리되어있기 때문에 유지보수가 용이하다.

- 구조가 복잡하여 습득이 어렵고 작업량이 많다.

- JAVA에 대한 깊은 이해가 필요하다.


모델 1 VS 모델 2

모델 2가 뒤늦게 나왔다고 무조건 장점만 가지고 있는 것은 아니다. 모델 2는 규모가 큰 프로젝트나 업데이트가 빈번한 프로젝트엔 용이할지는 모른다. 하지만 규모가 많이 크지 않고 업데이트가 적은 프로젝트일 경우엔 쓸대없이 복잡한 형태로 작업량이 늘어날 뿐이다. 이 경우에는 간단한 모델 1으로 개발하는게 더 나을수도 있다. 모델 1과 모델 2은 완전히 다른 장점과 단점을 가지고 있으므로 하나만 무조건적으로 사용한다기보단 상황에따라 적절한 선택이 필요할 것 같다. 


'JAVA > JSP,Servlet' 카테고리의 다른 글

커넥션풀(Connection Pool) - DBCP 예제  (0) 2016.02.08

+ Recent posts