728x90

배열(array)

- 주소값을 가지고 포인터 연산을 통해 배열의 모든 연소에 각각 접근할 수 있음

- 배열 선언 시, 메모리에 같은 크기의 공간들을 연속적으로 할당해놓고,

-->첫번째 요소의 주소값을 가지고 정수 연산

-->이어진 다음 요소의 주소를 계산할 수 있음

-->주소에 *를 붙여서 각 요소에 직접 전근 가능

 

예시1

int ary[5] = {1, 2, 3, 4, 5}

cout << art ; //4390184 첫 번째 원소의 주소 반환

cout << art+1 ; //4390188 두 번째 원소의 주소 반환(+4)

파이썬에서 그 배열이 아니다~~~!~!

c++에서 배열은 그 배열 첫번째 원소의 주소값을 반환한다!

 

이때, 'cout << 배열'을 하면, 해당 배열의 첫번째 원소 주소값이 저장되어있긴 하지만,

cout을 만났을 때 '얘는 배열이므로, 저장되어 있는 첫번째 원소의 주소값부터 null의 주소값까지의 value(chars)를 출력해주자

 

로 해석한다.

 

예시2

int* p = ary+1 // 포인터 p에 4390188 입력했다는 것.

이때, p - ary는 4가 아니라 1임. p와 ary 둘 다 '주소'이기 때문에 주소 연산을 수행함.

포인터+정수 → 포인터+(정수 x sizeof포인터)

 

 

pointer & new를 통해 동적 자료 만들기.

 

1.

#include <iostream>
#define SIZE 20
int main(){
    using namespace std;

    double* p3 = new double[3]; //double형 데이터 3개를 저장할 수 있는 공간을 대입
    p3[0] = 0.2; //p3를 배열 이름처럼 취급
    p3[1] = 0.5;
    p3[2] = 0.8;
    //new 연산자를 통해 동적 배열을 생성
    //pointer를 사용하여, 해당 배열의 원소에 개별적으로 접근 가능

    p3 = p3 + 1; //배열에 값을 더하는 행위//double의 크기만큼 더해짐

    cout << p3 <<endl; //0x6000022e1188
    cout << "Now p3[0] is " << p3[0] << " and "; //Now p3[0] is 0.5 //기존 p3[1]의 값
    cout << "p3[1] is " << p3[1] << ".\n"; // p3[1] is 0.8.

    p3 = p3 -1;
    delete[] p3;
    /**/

    char animal[SIZE];
    //char로 문자열을 지정할 때는, 문자열 크기를 미리 선언해야 함
    //컴파일 시간에 배열의 크기를 결정하는 것
    char* ps;

    cout << "동물 이름을 입력하십시오\n";
    cin >> animal;

    ps = new char[strlen(animal)+1]; //사용자가 무슨 animal을 입력하든 그보다 한 칸 더 넉넉히.
    //실행 시간에 배열의 크기를 결정하는 것이, 메모리 차원에서 훨씬 유리함

    strcpy(ps, animal); //animal의 값을 ps에 복사

    cout << "입력하신 동물 이름을 복사하였습니다" << endl;

    cout << "입력하신 동물 이름은 " << animal << "이고, 주소는 " << (int*)animal << "인데," << endl;
            //입력하신 동물 이름은 panda이고, 주소는 0x16f9235e4인데,
    cout << "그것을 복사한 것의 이름은 " << ps << "이고, 주소는 " << &ps << "일까, " << (int*)ps <<"일까?";
            //그것을 복사한 것의 이름은 panda이고, 주소는 0x16f9235d0일까, 0x600000b48030일까?
            //복사된 값이 원래 주소와 다름
}

2.

#include <iostream>
#define SIZE 20

/**/
//동적(dynamic): 컴파일 시간이 아닌, 실행 시간에 size를 받음
//동적 구조체 생성
//temp* ps = new temp;

struct MyStruct{
    char name[20];
    int age;
};

int main() {
    using namespace std;
    MyStruct* temp = new MyStruct; //new 연산자를 통해, 구조체를 동적으로 생성
    cout << "당신의 이름: ";
    cin >> temp->name; //화살표 멤버 연산자

    cout << "당신의 나이: ";
    cin >> (*temp).age; // 화살표 대신, (*구조체 변수). 도 가능

    cout << "안녕하세요! " << (*temp).name << "씨!\n";
    cout << "당신은 " << temp->age << "살 이군요!";

    cout << temp <<endl; //0x600000289180
    cout << (int*)temp << endl; //0x600000a5d180
    cout << &temp << endl; //0x16b0d75f8
//    cout << temp[0] << endl;
    cout << temp->name << endl; //fish
    // char 배열이므로 저장된 건 첫번째 요소의 주소값이지만 cout을 통해 해당 주소값부터 null이 있는 주소값까지의 char들을 출력
    cout << *(temp->name) << endl; //name에 영단어를 입력하면 첫번째 알파벳 f가 나옴(한글은 ?로 뜸)
    cout << &(temp->name) << endl; //0x600000289180
    
    /*번외*/
    cout << (int*)(temp->name) << endl; //0x600000289180
    // !=(int)(*변수) : 변수가 참조하는 대상을 int로 typecast한 결과

}

→ 여기서도 temp 구조체 포인터 안에 저장된 name은 배열이므로, 해당 배열 첫번째 원소의 주소값이 저장되어 있다.

따라서,

- temp->name: 해당 주소값~null의 주소값 범위에 해당하는 chars를 문자열로 출력해주고 : fish

- *(temp->name): 해당 배열 첫번째 원소의 주소값이 가리키는 char를 출력해주고 : f

- &(temp->name): 해당 배열 첫번째 원소의 주소값의 주소값을 출력해주고 : 0x6000~

 

- (int*)(temp->name): 얘는 아직 잘 모르겠다,, ㅎㅎ (int*)pointer는 pointer의 값 자체를 주소값으로 선언해준다는 건 읽었는데, 위 결과와 매칭을 잘 못 시키겠음...

"pointer를 이용하여 출력된 값은 정수 값이고 (int *)pointer를 이용해서 나오는 값은 번지 값이다"
출처:https://sunder4.tistory.com/150

728x90
728x90

c++은 객체지향 프로그래밍

- 컴파일 시간이 아닌 '실행 시간'에 어떠한 '결정'을 내릴 수 있다.

 

e.g.) 배열을 생성할 때,

재래적 절자척 프로그래밍: 배열의 크기를 미리 결정해야 함

객체지향 프로그래밍: 배열의 크기를 실행 시간에 결정할 수 있음-> 값이 변할 수 있는 변수배열의 크기를 결정할 수 있음

 

일주일에 한 번만 200개의 공간이 필요하다면, 재래적 절차적 프로그래밍의 경우 남은 6일 간 180이 낭비됨

객체지향 프로그래밍에서는, 20을 유지하다가, 필요에 따라 간헐적으로 200으로 늘림


포인터

변수 선언 과정에서, 변수의 이름을 통해 변수를 관리하는 것이 아니라, 저장할 데이터를 다루는 '주소'이름을 붙임

즉, 포인터는 포인터의 이름 자체주소를 나타냄.

이를 간접값 연산자, 간접 참조 연산자 * 로 나타냄

 

1-1. 포인터 선언, 변수와 연결

#include <iostream>

using namespace std;

int main(){

    int val = 3;
    cout << &val << endl; //val이라는 변수가 저장된 메모리 주소 : 0x16b0135fc

    /*포인터 선언하기*/
    //포인터는 개념적으로 정수와 다른 개념 (수가 아닌 위치를 나타냄)
    //포인터에는 어떤 정수를 대입하는 것이 아무런 의미가 없음
    //대신, 포인터에 어떠한 변수의 주소를 대입함으로써,
    // 포인터를 이용하여 해당 주소의 값에 직접 변화를 줄 수 있음

    int *a; // c style
    int* b; // c++ style
    int* c,d; //c는 포인터 변수, d는 int형 변수로 선언됨
	
    /*e의 주소 저장을 위해 f가 선언됨*/
    int e = 6;
    int* f; //그 자체로 주소를 나타냄

    f = &e; //포인터 f와 변수 e가 연결됨 -> f가 바뀌면 e도 연동되어 바뀜
    //위 코드는 *f = e;로 대체할 수 없음(error)

    cout << "e의 값 " << e << endl; //e의 값 6
    cout << "*f의 값 " << *f << endl; //*f의 값 6
    cout << "e의 주소 " << &e << endl; //e의 주소 0x16fcf75d8
    cout << "*f의 주소 " << &*f << endl; //*f의 주소 0x16fcf75d8
    cout << f << endl; //0x16f7e35d8

    *f = *f + 1; //주소에 해당하는 값에 직접 변화 주기
    cout << "이제 e의 값은 " << e << endl; //이제 e의 값은 7
    cout << *f << endl; // 0x16d2ef5d8 -> 값이 저장된 주소는 바뀌지 않음

}

- 즉, 포인터는 특정 데이터의 '주소'저장을 위한 용도로 생성됨.

 

1-2. new를 통해 포인터 선언 및 초기화

#include <iostream>

using namespace std;

int main(){

    /*이전시간에 활용한 방법: 주소에 접근할 때 a와 b 둘을 통해 모두 접근 가능*/
    int a;
    int* b = &a;

    //new 연산자
    /*
     * 어떤 데이터형을 원하는지 new 연산자에 알려주면,
     * new 연산자는 '그'에 알맞은 크기의 메모리 블록을 찾아내고
     * 그 블록의 '주소'를 리턴함*/

    int* pointer = new int; //int형을 위한 새로운 메모리가 필요함을 알려줌
    //new는 int임을 확인하여 몇 바이트가 필요한지 계산(e.g. 4btyes)
    //해당 바이트를 저장할 수 있는 메모리 블록을 찾아 해당 주소를 반환
    
 
}

1-3. 배열 형식의 포인터 선언 및 초기화

#include <iostream>

using namespace std;

int main(){
    
    double* p3 = new double[3]; //double형 데이터 3개를 저장할 수 있는 공간을 대입
    p3[0] = 0.2; //p3를 배열 이름처럼 취급
    p3[1] = 0.5;
    p3[2] = 0.8;

    cout << "p3[1] is " << p3[1] << ".\n"; //p3[1] is 0.5.

    p3 = p3 + 1; //배열에 값을 더하는 행위
    //원래 배열 이름은 값을 변경할 수 없음.
    //그러나 포인터는 변수처럼 사용할 수 있어서, 값 변경 가능.
    /* double 데이터 3개를 저장하는 공간이므로,
     p3에 1을 더한다는 것 =뒤에 있던 double형 데이터 공간 1개를 앞당기는 것
     p3[0] -> 0.5를 point
     p3[1] -> 0.8을 point
     */

    cout << "Now p3[0] is " << p3[0] << " and "; //Now p3[0] is 0.5 //기존 p3[1]의 값
    cout << "p3[1] is " << p3[1] << ".\n"; // p3[1] is 0.8.


}

 

 

2. 포인터를 delete

- delete 연산: 사용한 메모리를 다시 메모리 풀의 영역으로 환수 => 환수된 메모리는 프로그램의 다른 부분이 다수 사용할 수 있음
- delete ps; //new를 사용한 다음에는 반드시 delete를 사용해야 함
- delete 없으면 ; 대입은 되었지만 사용되지 않는 메모리 누수 발생 -> 프로그램 먹통

#include <iostream>

using namespace std;

int main(){

    /*delete 연산: 사용한 메모리를 다시 메모리 풀의 영역으로 환수*/
    //환수된 메모리는 프로그램의 다른 부분이 다수 사용할 수 있음

    int* ps = new int;
    //포인터변수 ps를 선언, 메모리 사용
    delete ps; //new를 사용한 다음에는 반드시 delete를 사용해야 함
    //delete 없으면 ; 대입은 되었지만 사용되지 않는 메모리 누수 발생 -> 프로그램 먹통

    /*delete를 사용하는 네 가지 규칙*/
    //1. new로 대입하지 않은 메모리는 delete로 해제할 수 없음
    //2. 같은 메모리 블록을 연달아 두 번 연속 delete로 해제할 수 없음 (한번으로 충분)
    //3. new[]와 같이 대괄호 형식 메모리 대입할 시, delete 역시 delete[]로 해제
    //4. 대괄호를 사용하지 않았다면, delete

    double* p3 = new double[3]; //double형 데이터 3개를 저장할 수 있는 공간을 대입
    p3[0] = 0.2; //p3를 배열 이름처럼 취급
    p3[1] = 0.5;
    p3[2] = 0.8;

    cout << "p3[1] is " << p3[1] << ".\n"; //p3[1] is 0.5.

    p3 = p3 + 1; //배열에 값을 더하는 행위
    //원래 배열 이름은 값을 변경할 수 없음.
    //그러나 포인터는 변수처럼 사용할 수 있어서, 값 변경 가능.
    /* double 데이터 3개를 저장하는 공간이므로,
     p3에 1을 더한다는 것 =뒤에 있던 double형 데이터 공간 1개를 앞당기는 것
     p3[0] -> 0.5를 point
     p3[1] -> 0.8을 point
     */

    cout << "Now p3[0] is " << p3[0] << " and "; //Now p3[0] is 0.5 //기존 p3[1]의 값
    cout << "p3[1] is " << p3[1] << ".\n"; // p3[1] is 0.8.

    p3 = p3 -1;
    delete[] p3;
}

reference: https://inf.run/4ttD

728x90
728x90

1. 배열과 문자열

#include <iostream>

using namespace std;

int main(){
//c++는 복합데이터형을 제공 : 사용자 정의대로 새로운 데이터형을 만들 수 있음
//복합데이터형 : 기본 정수형과 부동소수점형의 조합
//ex) 배열(array) : 같은 데이터형의 집합

/*배열(array)
 * typeName arrayName[arraySize]

 * 배열 초기화를 선언 이후 나중에 할 수 없음, 배열을 다른 배열에 통째로 대입할 수 없음
 * short month[5] = { ... };
 * short year[12] = { ... };
 * year = month; 불가능함
 * 초기화 값의 개수를 배열 원소의 개수보다 모자라게 제공할 수 있음
 * 배열을 부분적으로 초기화하면 나머지 원소는 모두 0으로 설정
 * 명시적으로 첫 번째 원소만 0으로 초기화하면, 나머지 원소들은 모두 0으로 초기화됨
 * 배열 사이즈를 명시하지 않으면 초기화 시의 원소 개수로 배정됨
 * */

short month[12]; //새로운 배열 선언 //month 배열의 원소는 최대 12개, 모두 short형
short month2[12] = {1, 2, 3};

cout << month2[0] << endl; //인덱스 통해 원소 출력

/*
 * 문자열: 문자의 열
 * char 리스트와 쌍따옴표로 문자열(스트링) 표현 가능
 * */

char a[5] = {'H', 'e', 'l', 'l', 'o'};
//이보다 더 효율적으로 문자열 정의하는 방법
char b[] = "Hello"; //쌍따옴표는 명시적으로 null문자를 포함하므로 스트링 표현 가능

cout << b;

}

2-1. 사용자 입력(cin)

#include <iostream>
#include <cstring>

using namespace std;

int main(){
    //<cstring>의 헤더 파일에 포함되어 있는 <strlen> : 문자열의 길이를 반환하는 함수
    //<sizeof> : 변수의 크기를 반환하는 함수
    const int Size = 15;
    char name1[Size];
    char name2[Size] = "C++programming";

    cout << "안녕 나는" <<name2;
    cout << "이야~\n";
    // cin >> name1; //사용자의 입력을 오른쪽 변수에 저장 //input느낌
    //문제는 화이트스페이스를 반영하지 못하고 그 전으로 자름
    /*이를 해결하는 것이 get & getline*/
    cin.getline(name1, Size); //(변수, 크기) //get도 마찬가지

    cout << "음 " << name1 << "씨, 당신의 이름은 ";
    cout << strlen(name1) << "자입니다만\n";
    cout << sizeof(name1) << "바이트 크기에 저장되었습니다.\n";
    cout << "이름이" << name1[0] << "자로 시작하시는군요.\n";
    name2[3] = '\0'; //문자열 출력 시 null 문자까지만 출력함
    cout << "제 이름의 세 문자는" << name2 << "입니다.";

}

2-2. string

#include <iostream>

using namespace std;

int main(){

    //c++에서 문자열을 다루는 방법 중 하나인 'string'
    /*
     * c스타일로 string 객체를 초기화할 수 있음
     * cin을 사용하여 string 객체에 키보드 입력을 저장
     * cout을 사용하여 string 객체를 보여줌
     * 배열 표기를 사용하여 string객체에 저장된 개별 문자에 접근 가능
     * => 문자열과 속성이 비슷한데,
     * => string을 통해서는 배열을 다른 배열에 통째로 대입할 수 있음 
     * => 배열 크기를 미리 지정하지 않아도 됨 */

    char char1[20];
    char char2[20] = "jauar";
    string str1;
    string str2 = "panda";
    // char1 = char2; //값이 들어가지 않음
    str1 = str2; //제대로 대입 됨
    cout << str1;

}

3. 구조체(struct)

#include <iostream>

using namespace std;

int main(){

    //c++만의 특장점: 사용자의 정의 및 입력대로 원하는 데이터를 만들 수 있음
    /*정말 중요한 '구조체'  (복합데이터형 중 하나)*/
    //배열: 같은 데이터형의 집합
    //구조체: '다른 데이터형이 허용되는' 데이터의 집합

    //축구선수
    struct MyStruct {
        string name;
        string position;
        int height;
        int weight;
    };

    MyStruct A; //A라는 이름의 MyStruct형의 데이터를 만든 것 //약간 class느낌..?
    A.name = "Son";
    A.position = "Striker";
    A.height = 183;
    A.weight = 77;

    MyStruct B = {
            "Min", "defender", 160, 49
    };

    cout << B.weight << endl;

    //구조체 생성 시 중괄호 뒤에 생성할 구조체명을 선언하면
    //실제 선언 시 구조체 형식을 전부 명시하지 않아도 됨

    //또한, 구조체에 포함된 모든 변수를 선언하지 않아도 됨, 자동으로 0이 됨

    struct Ming {
        string firstname;
        string lastname;
    } minji;

    minji.firstname = "minji";
    minji.lastname = "Jo";

    cout << minji.firstname << endl;

    //배열을 선언하듯 구조체를 선언할 수 도 있음
    Ming minfamily[2] = {
            {"jungah", "Han"}, //첫번째 인덱스에 해당하는 정보
            {"seokhyeon", "Jo"} //두번째 인덱스에 해당하는 정보
    };

    cout << minfamily[0].lastname;

}

4. 공용체 (union)

#include <iostream>

using namespace std;

int main(){

    //c++에서 지원하는 복합데이터형 중 : 공용체 & 열거체
    /*공용체(union): 서로 다른 데이터형을 한 번에 한 가지만 보관할 수 있음 */

    union MyUnion{
        int intVal;
        long longVal;
        float floatVal;
    };

    MyUnion test;
    test.intVal = 3;
    test.longVal = 33;
    test.floatVal = 3.3;

    cout << test.intVal << "\n" << test.longVal << "\n" << test.floatVal << endl;
    //1079194419
    //1079194419
    //3.3
    //여러 데이터형을 사용할 수 있지만, 동시에 사용은 불가능
    // -> 메모리 절약 가능 (운체, 하드웨어 등에 주로 활용됨)


}

5. 열거체(enum)

#include <iostream>

using namespace std;

int main(){

    //c++에서 지원하는 복합데이터형 중 : 공용체 & 열거체

    /*열거체(enum): 기호 상수(한번 선언된 이후 변경 불가)를 만드는 또다른 방법*/
    enum spectrum {red, orange, yellow, green=5};
    //1. spectrum을 새로운 데이터형 이름으로 만듦
    //2. red, orange, yellow, green의 열거자를 다양한 수를 0~3까지 정수 값을 각각 나타내는 기호 상수로 만듦
    //-> 즉 문자로 정수를 사용하게 됨
    //-> 사용된 열거자를 상수로 관리함
    // 명시적으로 정수 값을 직접 대입할 수 있음 (자동배정되는 값을 사용하지 않으려면.)
    spectrum a = orange; //열거체 생성 시, 열거자만 대입할 수 있음
    //열거자끼리 직접 산술연산 불가능 (orange+yellow//error)

    int b;
    b = green; //열거자를 int 변수에 대입하면 산술연산 사용 가능
    b = green + 3;


    cout << b << endl;

}

 

reference: https://inf.run/4ttD

728x90
728x90

1. 변수 선언과 규칙

#include <iostream>

using namespace std;

int main(){
    //1. 변수

    /*
     1. 변수의 자료형
     2. 변수의 이름
     3. 변수가 어디에 저장되는가?(메모리 영역) - 컴파일러가 직접 결정해줌
     -> 주소값 알려면 변수 앞에 &붙이면 됨
     */
    int a; //int라는 자료형의 a라는 변수명
    a = 7; //int라는 자료형 명시했으므로 가능
    /*
     * 변수명의 규칙
     1. 숫자로 시작할 수 없음
     2. c++에서 사용하고 있는 키워드는 사용할 수 없음
     3. white space(space/tab/enter)를 사용할 수 없음
     */
    { int b;
        {
            //int a; 하단 블록에서 정의되면, 상단에서 사용될 수 없음
            b=3; //상단 블록에서 정의되었으므로 가능
        }
        //a=5;
    }

    int b = 3; //초기화 : 선언하자마자 대입

    cout << a << b; //73

    //변수는 사용되기 이전에 정의되어야 함

    return 0;
}

 

2. 정수형 자료형과 실수형 자료형

#include <iostream>
#include <climits>
using namespace std;

int main(){
    /*
     * 정수형: 소수부가 없는 수 -> 음의 정수, 0, 양의 정수
     * */
    //short, int, long, long long
    //메모리가 한정되어 있는데, 정수 크기와 정수 개수의 스펙트럼이 넓다 보니
    //이를 효율적으로 활용하기 위해 정수형을 네 가지의 types로 나누었음

    int n_int = INT_MAX; //이는 <climits>에서 정의된 int의 max값에 대한 '상수'
    short n_short = SHRT_MAX;
    long n_long = LONG_MAX;
    long long n_longlong = LLONG_MAX;

    cout << "int는 " << sizeof n_int << "바이트이다." <<endl; //4바이트
    cout << "이 바이트의 최대값은 " << n_int << " 이다." <<endl;

    cout << "short는" << sizeof n_short << "바이트이다." <<endl; //2바이트
    cout << "이 바이트의 최대값은" << n_short << " 이다." <<endl;
    //short 자료형은 음의 정수를 표현할 수 있음 : ~32768 ~ 32767

    cout << "long은" << sizeof n_long << "바이트이다." <<endl; //4바이트
    cout << "이 바이트의 최대값은" << n_long << " 이다." <<endl;

    cout << "long long은" << sizeof n_longlong << "바이트이다." <<endl; //8바이트
    cout << "이 바이트의 최대값은" << n_longlong << " 이다." <<endl;

    unsigned short b;
    //음의 정수형을 사용하지 않을 예정이므로, 범위를 0~655535까지 넓힐 수 있음
    b = -1;
    cout << b << endl; //65535


    /*
     * 실수형: 소수부를 저장할 수 있음
     */
    float a = 3.14; //3.14
    int c = 3.14; //3

    cout << a << endl << c << endl;

 

3. char형과 bool형

#include <iostream>

using namespace std;

int main(){
    //char: 작은 문자형-아스키코드 // 아스키코드로 표현할 수 있는 문자 내에서만
    int a = 77;
    char b = a; //M
    char c = 'a'; //a
    cout << b << c << endl;

    //큰따옴표로 표현되는 문자열은 char형로 사용될 수 없음!!!
    //c++에서 문자 출력하고자 할 때, 문자를 정수로 저장하는데,
        //이를 위해 어디까지가 문자인지 알아야 함
    //한 개의 문자가 아닌 긴 문자열을 출력하고자 할 때는, 어디까지가 문자인지 표시하는 null 문자(\0)가 필요함
        //큰 따옴표는 명시적으로 null 문자가 표현된 것 -> string 자료형을 위해 사용

    char d[] = {'a', 'b', 'c'};
    cout << d << endl; //abc�aMM //우연치않게 null을 만날 때 까지 출력을 반복

    char e[] = {'a', 'b', 'c', '\0'};
    cout << e << endl; //abc

    //char f = "a" //'a'와 null 문자를 함께 저장한 건데, char은 한 개의 문자만 포함해야 하므로, 오류 발생

    //한글은 char-아스키코드에서 지원해주지 않음, 유니코드로만 표현됨
        //c-wput, location, string형 사용 등

    /*
     * bool 타입: 숫자를 0 혹은 1로만 저장, 0이외 모든 수는 1로 저장됨
     * */

    bool g = 0; //0
    bool h = 1; //1
    bool i = 10; //1

    cout << g << endl << h << endl << i << endl;


}

3. 상수형과 자료형변환

int main(){

    /*상수: 바뀔 필요가 없거나, 바뀌어서는 안 되는 수 -> 변수형까지 선언해야 함*/
    const float PIE = 3.1415926;
    int r = 3;
    float s3 = r*r*PIE;

    cout << s3; //28.2743

    /*데이터형 변환
    1. 특정 데이터형의 변수에 다른 데이터형 값을 대입했을 때
     2. 수식에 데이터형을 혼합하여 사용했을 때
     3. 함수에 매개변수를 전달할 때
     */

    int a = 3.14;
    cout << a << endl; //3 //자동으로 자료형변환

    //강제적으로 데이터형 변환
    //typeName(a) //바꿀데이터형(변수) //(바꿀데이터형)변수
    //c++ : static_cast<typeName>

    char ch = 'M';
    cout << int(ch) << endl << static_cast<int>(ch) << endl << ch;

    return 0;
    
}

4. 산술연산자와 auto

#include <iostream>

using namespace std;

int main(){

    /*
     연산자: + - * / %
     */

    // '/'에서 두 개의 피연산자가 모두 정수이면 '몫' 계산임
    // 계산에서 둘 중 하나가 실수형이면, 결과도 실수형으로 나옴
    int a = 3 + 2;
    cout << a; //5

    //연산 시 일반대수학의 우선순위를 따름 (곱하기, 나누기 > 더하기, 빼기)
    //곱하기와 나누기 간 순서는... 구글링해보기!

    /*auto*/
    auto n = 100; //int형으로 자동 지정
    //그러나 auto로 인해 의도와 다른 지정이 발생할 수 있으므로 사용 지양

}

 

 

728x90
728x90
#include <iostream> //전처리 지시자

/*
c++에서 함수를 사용 하고자 한다면
반드시 그 함수(ex.cout; 출력)의 원형을 미리 정의 하여야 한다
*/

using namespace std;
//세미콜론 ; 은 종결자의 역할: 한 문장의 끝으로 인식(엔터키 필요 없음)
//using namespace std가 없다면, 뒤 cout과 endl 앞에 'std::'를 추가로 붙여야 함

int main(){
    //c++ 코드에는 반드시 main의 이름을 가지고 있는 함수가 있어야 한다

    cout << "Hello, World!" << endl;
    //cout : 출력
    //꺽쇠 두 개 : 데이터의 흐름, 데이터의 방향을 나타냄
    //endl : 줄바꿈
    //std::cout<< "" <<std::endl;

    return 0;
}
  • 전처리 지시자: include <iostream>
  • 세미콜론을 통한 문장의 종결
  • using namespace std
    • std::
  • main의 이름을 가진 함수의 필요성
  • cout
  • 꺽쇠를 통한 데이터 흐름 표시
  • endl

reference: https://inf.run/JGFT

 

728x90

728x90

Reference(아주굿): https://sonsnotation.blogspot.com/2020/11/8-normalization.html

 

4 types of Normalization

Normalization

1. 개념: 데이터의 범위를 사용자가 원하는 범위로 제한하는 것

 

2. 여러 기법

- feature들의 0~1 scale (ex. 이미지 데이터 픽셀이 0~255 사이의 값 → 0.0~1.0 사이의 값)

- 표준화(Standardization): 표준정규분포를 갖도록 평균을 빼고 표편으로 나누는 것

- whitening(e.g. batch normalization): 2단계로 구성 (1) Stardardize(standard normalize) pre-activation function (2) Scale and shift

etc.

 

3. 장점

: optimal solution으로의 수렴 속도가 빨라진다.

layer가 1개이든, 2개 이상의 딥러닝이든, 정규화를 할 경우 loss function의 형태가 elongated(타원형)에서 spherical(구 형)으로 바뀌고, optimal solution으로 가는 경로가 더 효율적으로 개선된다.

 


Batch Normalization & Layer Normalization

 

Batch Norm Layer Norm
- Whitening 기법 베이스
(0) 매 순간(=weight가 training되는 순간)마다
(1) Stardardize(standard normalize) pre-activation function
(2) Scale and shift

-
 입력 데이터의 분포를 조정하여, 신경망 내부에서 학습 안정성과 속도 개선
주로 CNN, FCNN에서 활용
->배치 내부의 통계적 변화로 인한 그래디언트 소실 문제를 완화
주로 RNN, 순차적 데이터(시퀀스) 다루는 경우 활용
-> 시간적인 의존성을 고려하여 안정적인 정규화를 제공
주로 mini-batch 내 데이터가 여러 개(=batch size가 2 이상)인 경우
-> 미니배치 크기에 영향을 받음
mini-batch에 들어있는 데이터가 한 개(=batch size가 1)인 경우에도 사용 가능
Standardize에서 사용하는 평균과 표준편차는,
mini-batch 내 모든 데이터의 Feature(채널) '별'로 평균과 표편을 계산.

테스트 시에는, 전체 데이터셋의 평균과 표편을 사용 (배치별로 구해뒀던 평균과 분산을 버리지 않고 메모리에 기억해뒀다가, 재사용)
Standardize에서 사용하는 평균과 표준편차는,
batch에 있는 '모든 feature(모든 채널)'에 대한 평균과 분산
즉, hidden layer 전체의 평균과 분산으로 normalization

 

장점

layer가 여러 개인 DL에서 ...

- 기존에 back prop 시, activation function(eg. 기울기가 0인 영역이 넓은 sigmoid / tanh이나, G.V. issue를 간접적으로 회피하는 ReLU 등.)을 통과하면서 gradient vanishing 문제가 발생하는데,

 

매 순간 Whitening을 수행하여 input을 평균 0, 표준편차 1인 분포로 normalization시켜 주면,

 

- G.V. issue의 근본 원인인 Internal Covariance Shift 문제(아무리 input layer에서 정규분포를 가지는 입력을 줘도 hidden layer를 지나면서 그 분포가 점점 정규분포를 벗어나는 현상)를 해결

Internal Covariance Shift 현상
Internal Covariance Shift로 가속화되는 gradient vanishing

 

Whitening의 두 단계

1. Normalize pre-activation function : pre-activate 된 샘플을 standard normalize

2. Scale and shift : standard normalize 된 샘플의 variance와 bias를 조절할 수 있는 γ, β를 부과해서 parameterize

variance와 bias를 다시 조절하는 이유는 sigmoid의 경우는 중심에 분포가 몰려 있는 것이 효과적이지만, 다른 activation은 그렇지 않기 때문이다. 즉, 학습을 통해 γ와 β를 학습해 적절한 분포를 가지게 하기 위함.

728x90
728x90

문제 개요

- 새로 이동한 칸에 적혀 있는 알파벳은 지금까지 지나온 모든 칸에 적혀 있는 알파벳과는 달라야 함

- 말이 최대한 몇 칸을 지날 수 있는지 (첫 번째 칸도 포함)

https://www.acmicpc.net/problem/1987

 

1987번: 알파벳

세로 R칸, 가로 C칸으로 된 표 모양의 보드가 있다. 보드의 각 칸에는 대문자 알파벳이 하나씩 적혀 있고, 좌측 상단 칸 (1행 1열) 에는 말이 놓여 있다. 말은 상하좌우로 인접한 네 칸 중의 한 칸으

www.acmicpc.net

나의 코드

R, C = list(map(int, input().split()))
inputs = []
for _ in range(R):
    inputs.append(list(input()))

from collections import deque

def solution(R,C,inputs):
    max = 1
    # cand = deque([((0,0),inputs[0][0])])
    cand = set()
    cand.add(((0,0),inputs[0][0]))

    dx = [-1,0,0,1]
    dy = [0,-1,1,0]
    
    while cand:
        (x,y), a = cand.pop()
        for k in range(4):
            cx = x + dx[k]
            cy = y + dy[k]
            if cx<0 or cx>R-1 or cy<0 or cy>C-1:
                continue
            else:
                if inputs[cx][cy] not in a:
                    cand.add(((cx,cy), a+inputs[cx][cy]))
                    if len(a+inputs[cx][cy]) > max:
                        max = len(a+inputs[cx][cy])                 
                    
    return max

print(solution(R,C,inputs))

우여곡절

- 구현은 크게 어렵지 않았으나, 그놈의 효율성 문제... ㅎ

- BFS에 deque가 사용된다면 안 된다니! :D set 자료형을 사용해야 했다

why? 중복 이슈

deque의 시간복잡도는 O(1)이 맞음. 그러나 deque를 사용한 코드를 보면 중복값에 대해서도 모두 O(1)의 연산을 하니, 아무리 O(1)이지만 중복값이 수없이 많다면... 효율성 문제 발생. set 자료구조자체가 중복을 허용하지 않기때문에 훨씬 효율적으로 처리하며, 아마 deque에도 중복체크 넣으면 효율적으로 개선될 것.

=>같은 위치에 같은 문자열을 가진 상황을 중복해서 확인할 필요없으니, 같은 값은 같은 객체로 취급하는 집합 자료형을 사용

이라는데...... 이 문제에서 어떤 경우에 중복이 발생하는지 아직 이해하지 못했다.

그냥 visted에서 deque와 set 모두 구현해보고 시간이 더 빠른 걸 택한다는...분도 계셨다.

더 알아봐야겠다.

 

참고: https://www.acmicpc.net/board/view/81429

 

- 한번도 방문한 적 없은 알파벳만을 선택한다는 조건(a) 자체가 → visit한 원소는 아예 제외한다는 조건(b)을 포함한다

: 즉, a가 구현되면 b를 별도로 구현할 필요가 없다

: 즉, 방문한 알파벳에 대한 리스트(a)만 체크하면, 이미 방문한 원소에 대한 리스트(visited)를 따로 생성할 필요가 없다

 

- 참, 저기서 add할 때 a.append(inputs[cx][cy])로 하면 안 된다. for문 전체에 global하게 해당되는 a가 변형되기 때문이다. 슬라이싱 통해 temp로 복제하는 방법도 있지만, 그보다는 그냥 마지막에 cand에 add할 때 '+' 를 사용해주는 게 best!

 

새로 배운 것

1. 문자열(string)은 자기들끼리 덧셈 연산이 된다.

print('a'+'b') #ab

2. set 자료형

- append가 불가능한 대신 add로!

- pop을 할 경우 임의의 원소가 반환되므로, remove를 사용해야 한다 (나는 여기서... 대강 pop으로 때려맞혀서 이건 수정해야 한답)

https://blockdmask.tistory.com/451

 

[python] 파이썬 set (집합) 자료형 정리 및 예제

안녕하세요. BlockDMask 입니다. 오늘은 파이썬에서 집합 자료형인 set 자료형에 대해서 이야기 해보려 합니다. 집합 자료형은 다른 자료형의 중복 제거할때 사용을 하기도 하는데요. 자세한것은 예

blockdmask.tistory.com

3. time 모듈로 코드 실행시간(시간복잡도) 측정

import time

#코드 하단에 추가
t0 = time.time()
print(time.time() - t0)

4. deque..

Deque(데크)는 double-ended queue 의 줄임말로, 앞과 뒤에서 즉, 양방향에서 데이터를 처리할 수 있는 queue형 자료구조

https://excelsior-cjh.tistory.com/entry/collections-%EB%AA%A8%EB%93%88-deque

 

collections 모듈 - deque

collections.deque 1. deque란 Deque(데크)는 double-ended queue 의 줄임말로, 앞과 뒤에서 즉, 양방향에서 데이터를 처리할 수 있는 queue형 자료구조를 의미한다. 아래의 [그림1]은 deque의 구조를 나타낸 그림이

excelsior-cjh.tistory.com

 

728x90
728x90

 

https://www.acmicpc.net/problem/2206

 

2206번: 벽 부수고 이동하기

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 당신은 (1, 1)에서 (N, M)의 위치까지 이동하려 하는데, 이때 최단 경로

www.acmicpc.net

 

 

나의 코드

inputs=[]
[N,M] = list(map(int,input().split()))

for _ in range(N):
    inputs.append(list(map(int,input())))

from collections import deque

def solution(N, M, inputs):
    if N==1 and M==1:
        return 1

    cand = deque()
    visited = [[[0,0] for _ in range(M)] for _ in range(N)] #3차원의 visited array
    cand.append(((0,0),0))
    visited[0][0][0] = 1

    dx = [-1, 0, 1, 0]
    dy = [0, -1, 0, 1]

    while cand:
        ((x,y),i) = cand.popleft()
        for k in range(4):
            cx = x + dx[k]
            cy = y + dy[k]
            if cx<0 or cx>N-1 or cy<0 or cy>M-1:
                continue
            else:
                if inputs[cx][cy] == 0 and visited[cx][cy][i]==0:
                    if cx == N-1 and cy == M-1:
                        return visited[x][y][i] + 1
                    cand.append(((cx,cy),i))
                    visited[cx][cy][i] = visited[x][y][i] + 1

                elif inputs[cx][cy] == 1 and i==0:
                    cand.append(((cx,cy),1))
                    visited[cx][cy][1] = visited[x][y][0] + 1

    return -1

print(solution(N,M,inputs))

 

1. BFS + Depth !!를 효율적으로 구현

 visited[cx][cy][i] = visited[x][y][i] + 1

 

2. visited를 두 유형으로 나누어야 한다(visited array를 3차원으로 표현) → 벽을 부수기 전 & 벽을 부순 후.

- 이유:

결국 방문배열이란 것은, bfs에서 '모든 경우'를 살펴보며 진행할 때, 같은 행위를 두번하면 비효율적이니 그걸 막기 위해 사용합니다.

따라서 방문배열은 '모든 경우'를 나타낼 수 있어야 합니다. 일반적인 bfs 문제에서 모든 경우는 결국 '이 위치에 이미 왔었다' 입니다.

따라서 그냥 2차원 방문배열로 나타내면 되는거구요.

이 문제에서 모든 경우는 말로 설명하자면 '해당 위치에 벽을 부순 상태로 이미 왔었거나, 해당 위치에 아직 벽을 부수지 않은 상태로 이미 왔었다.' 정도가 되겠습니다.

따라서 이걸 표현하기 위해 3차원 배열로 나타낸거구요.

이런식으로 모든 경우를 나타낼 수 있는 방문배열을 만들지 않을 경우 다음과 같은 문제가 발생합니다.

출처: https://www.acmicpc.net/board/view/67446

 

 - 추가 설명:

모든 작업을 한 번의 BFS 탐색을 통해 처리해야했기 때문에, 최적의 벽을 '탐색'하는 것보다는 '기록'하는 방식이 필요했다.

그리고 3차원 배열을 통해서 이 문제를 해결할 수 있었다.

visited에는 이미 우리에게 친숙한 2차원 배열의 각 인덱스 [x][y]뿐 아니라 w라는 값을 하나 더 만들어서 (x, y)까지 오는 과정에서 벽을 뚫은 경우와 그렇지 않고 온 경우를 모두 기록해준다. (각각 1번 인덱스와 0번 인덱스에 기록)

다시 말해, visited[x][y][0]에 (x, y)까지 벽을 뚫지 않고 왔을 때 걸린 거리(시간)을 기록해준다.

반대로, visited[x][y][1]에는 (x, y)까지 오는 데 벽을 한 번이라도 뚫고 온 경우 걸린 거리(시간)을 기록해준다.

출처: https://seongonion.tistory.com/107

 

추가: https://nahwasa.com/entry/BFS-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%84%88%EB%B9%84-%EC%9A%B0%EC%84%A0-%ED%83%90%EC%83%89-%EB%B0%B0%EC%97%B4-BFS-%EA%B7%B8%EB%9E%98%ED%94%84-BFS

 

3. 이하 코드는 항상 요긴하게 씁사..

        for k in range(4):
            cx = x + dx[k]
            cy = y + dy[k]
            if cx<0 or cx>N-1 or cy<0 or cy>M-1:
                continue

 

 

- 배운 점: BFS문제 다 똑같지 않다. 변형 테크닉이 어렵고, 익숙해지면 아이디에이션 생각보다 간단하다.

 

 


나의 방황 기록

초기 코드1: Depth를 2 type으로 구분하지 않고 1 type으로 몰아갔다..

→ 당연히 틀림 (초반에는 경로가 비효율적이더라도 벽을 아직 부수지 않아서 최종 목적지까지 다다를 수 있는 경로가 배제됨)

inputs=[]
[N,M] = list(map(int,input().split()))

for _ in range(N):
    inputs.append(list(map(int,input())))

from collections import deque

def solution(N, M, inputs):

    cand = deque()
    cand.append(((0,0),0))
    inputs[0][0] = 1

    dx = [-1, 0, 1, 0]
    dy = [0, -1, 0, 1]

    while cand:
        ((x,y),i) = cand.popleft()
        for k in range(4):
            cx = x + dx[k]
            cy = y + dy[k]
            if cx<0 or cx>N-1 or cy<0 or cy>M-1:
                continue
            else:
                if inputs[cx][cy] == 0:
                    inputs[cx][cy] = inputs[x][y]+1
                    if cx == N-1 and cy == M-1:
                        return inputs[cx][cy]
                    cand.append(((cx,cy),i))
                elif inputs[cx][cy] == 1:
                    if i == 0:
                        inputs[cx][cy] = inputs[x][y]+1
                        cand.append(((cx,cy),1))
    
    return -1

print(solution(N,M,inputs))

 

초기 코드2: visited 내역을 queue의 각 원소마다 포함해줬다

예상한 대로 시간초과... 애초에 이렇게 풀 수 있는 문제 거의 없음

조건 추가된 depth 계산은, 무조건 이 방식 말고 다른 방식을 떠올려야 함

(ex. visited array를 별도로 생성하여 0->1->2.. 이런 식의 표현을 베이스로 깔거나(이후 변형) / 조건 잘 따져서 global queue(set)을 생성하거나 등등. )

inputs=[]
[N,M] = list(map(int,input().split()))

for _ in range(N):
    inputs.append(list(map(int,input())))

from collections import deque

def solution(N, M, inputs):
    if N==1 and M==1:
        return 1
    
    cand = deque()
    cand.append(((0,0),0,[(0,0)]))

    dx = [-1, 0, 1, 0]
    dy = [0, -1, 0, 1]

    while cand:
        ((x,y),i,visit) = cand.popleft()
        for k in range(4):
            cx = x + dx[k]
            cy = y + dy[k]
            if (cx<0 or cx>N-1 or cy<0 or cy>M-1) or ((cx, cy) in visit):
                continue
            else:
                if inputs[cx][cy] == 0:
                    if cx == N-1 and cy == M-1:
                        return len(visit) + 1
                    cand.append(((cx,cy),i, visit+[(cx,cy)]))
                elif inputs[cx][cy] == 1:
                    if i == 0:
                        cand.append(((cx,cy),1,visit+[(cx,cy)]))
    
    return -1

print(solution(N,M,inputs))

 

728x90
728x90

 Keras의 callback

: training 단계에서(epoch 시작부터 끝까지) 어떠한 동작을 수행하는 object

- callback들을 통해서 tensorboard에 모든 batch of training들에 대해 metric 수치를 모니터링할 수도 있고, 이를 저장하는 것도 가능

- Early Stop이나 Learning Rate Scheduling과 같은 기능을 통해 학습결과에 따라 학습을 멈추거나 학습률을 조정할수도 있음

=> 이처럼 Callback들을 잘 활용한다면, 딥러닝 학습의 결과를 보다 좋게 만들 수 있음.

 

주요 활용되는 callbacks

  1. ModelCheckpoint:
    • 기능: 모델의 훈련 중에 지정된 에포크마다 모델의 가중치를 저장.
    • 특징: 훈련 중간에 모델의 상태를 저장하여 최상의 성능을 보이는 모델을 선택할 수 있도록 도와줌. 이후 모델 재사용 또는 재훈련에 유용.
    • 아래 코드 참조
  2. EarlyStopping:
    • 기능: 모델의 훈련 중에 지정된 지표(monitor)를 모니터링하여 성능이 향상되지 않을 경우, 훈련을 조기 종료시킴.
    • 특징: 오버피팅을 방지하고 불필요한 계산을 줄여줌. 훈련 속도를 개선하고 최적의 성능을 달성할 수 있음.
  3. ReduceLROnPlateau:
    • 기능: 모델의 훈련 중에 지정된 지표(monitor)를 모니터링하고, 성능 개선이 없을 경우 학습률(learning rate)을 감소시킴.
    • 특징: 학습률을 동적으로 조정하여 학습 과정을 안정화하고, 최적의 학습률을 찾아주는 역할을 수행.
  4. TensorBoard:
    • 기능: 모델의 훈련 과정과 성능 지표를 시각화하기 위해 TensorBoard 로그를 생성.
    • 특징: 훈련과정을 시각화하여 모델의 동작을 이해하고 모델의 성능을 모니터링할 수 있도록 도와줌.
  5. LearningRateScheduler:
    • 기능: 지정된 학습률 스케줄링 함수를 사용하여 학습률을 동적으로 조정.
    • 특징: 학습률을 조정하여 학습 과정을 안정화하고 최적의 학습 속도를 찾아주는 역할. ReduceLROnPlateau와 유사하지만 더 많은 커스터마이징이 가능.
    • 아래 코드 참조

- Model Checkpoint with 코드!

tf.keras.callbacks.ModelCheckpoint(
    filepath,
    monitor: str = "val_loss",
    verbose: int = 0,
    save_best_only: bool = False,
    save_weights_only: bool = False,
    mode: str = "auto",
    save_freq="epoch",
    options=None,
    initial_value_threshold=None,
    **kwargs
)

 

- Keras에서 제공하는 LearningRateScheduler with 코드!!

=> 얘는 도대체 무슨 기능을 하는지 헷갈려서.. 알아봄

import tensorflow as tf

def lr_scheduler(epoch, lr):
    if epoch % 30 == 0 and epoch != 0:
        lr = lr * 0.1
    return lr

# LearningRateScheduler 콜백 함수 생성
scheduler = tf.keras.callbacks.LearningRateScheduler(lr_scheduler)

# 모델 컴파일
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])

# 콜백 함수 등록하여 모델 훈련
model.fit(x_train, y_train, epochs=100, callbacks=[scheduler])

예제에서 lr_scheduler 함수는 현재 에포크를 확인하여 에포크가 30 배수일 때마다 학습률을 0.1 감소시킴

- 이렇게 하면 모델은 훈련 중간에 학습률을 조정하여 나은 성능을 얻을 있음.

- 이같이 Keras의 LearningRateScheduler는 사용자의 필요에 따라 학습률을 동적으로 스케줄링하여 함수를 원하는대로 정의할 수 있음

- callback함수의 일종이므로, callbacks 리스트에 append해주어야 함! :)

 

 

reference: https://rinha7.github.io/keras-callbacks/

https://keras.io/api/callbacks/base_callback/

 

728x90

728x90

 

0. why skip-connection?

https://lv99.tistory.com/25

 

ResNet (Shortcut Connection과 Identity Mapping) [설명/요약/정리]

등장 배경 레이어가 많아져, 인공신경망이 더 깊어질 수록 gradient vanishing/exploding 문제가 커집니다. 이전의 다른 방법들로 이 문제를 해결했다고 생각했지만, 수십개의 레이어를 가진 모델에서는

lv99.tistory.com

 

1. General Architecture of ResNet

1.1 Main Block of ResNet

https://www.neuralception.com/objectdetection-batchnorm/

2. Architecture of ResNet50

https://eremo2002.tistory.com/76

 

ResNet50 구현해보기

케라스를 이용하여 ResNet50을 구현하였다.ResNet 50-layer 네트워크 구조는 다음과 같다. 그리고 레이어가 50개 이상인 버전에서는 오른쪽과 같은 bottleneck skip connection 구조를 사용한다. 케라스에서 제

eremo2002.tistory.com

 

https://www.researchgate.net/figure/ResNet50-architecture-shown-with-the-size-of-the-filters-and-outputs-of-each_fig5_338563385

 

 

728x90
728x90

https://school.programmers.co.kr/learn/courses/30/lessons/43238

 

문제 요약

 

목표: 총 심사 시간 최소화.

 

Input

n: 입국 심사 기다리는 사람 수 (엄~~~~청 큼.)

Times: ‘각 심사관이 한 명을 심사하는 데 걸리는 시간’이 담긴 배열 (각 시간이 엄청 큼) (사람 수는 10만.)

e.g) 6, [7,10]

 

Return

심사 총 시간.

e.g) 28

 

한 심사대 - 한 명

가장 앞에 있는 사람: 비어 있는 심사대에서 심사 받기.

더 빨리 끝나는 심사대 있으면: 기다렸다가 그곳에서 심사 받기.

*20분이 되었을 때, 두 번째 심사대가 비지만 6번째 사람이 그곳에서 심사를 받지 않고 1분을 더 기다린 후에 첫 번째 심사대에서 심사를 받으면 28분에 모든 사람의 심사가 끝납니다.

 

예제

 

(7) 끝난 사람 1

(10) 끝난 사람 2

(14) 끝난 사람 3

(20) 끝난 사람 4

(21) 끝난 사람 5

(28) 끝난 사람 6

(30) 끝난 사람 6

 

정답은 각 시간의 배수 중 하나.

  • 7 (<=7*1, 10*0) : 1
  • 14 (<=7*2, 10*1) : 3
  • 21 (<=7*3, 10*2): 5
  • 28 (<=7*4, 10*2): 6

 

  • 10 (<=10*1, 7*1) : 2
  • 20 (<=10*2, 7*2) : 4
  • 30 (<=10*3, 7*4) : 7 (6)
  • 40 (<=10*4, 7*5) : 9 (6)

times의 각 원소의 배수들을 모두 나열.

각 배수를 우측의 부등호처럼 표현하고 곱한 수의 합이 n이상인 경우 중

가장 작은 배수값을 찾아냄.

이때 합이 n이상이면 max(합, n)으로 간주.

 

28 // 7 = 4

28 // 10 = 2

 


내 코드

 

def solution(n, times):
    
    times.sort()
    start = times[0]
    end = times[0]*n
    cand = end #candidate: 정답 후보
    
    while start <= end: #이분탐색은 start, end를 지정해주어야 함
        mid = (start+end) // 2 #start, end를 바탕으로 mid 생성

        if sum([mid//i for i in times]) < n:
            start = mid + 1
        elif sum([mid//i for i in times]) > n:
            cand = mid
            end = mid - 1
        else:
            if start==end: #해당 조건 없으면 무한루프
                return start
            cand = mid
            end = mid
    
    #반복문 종료되었을 때, 반복문의 최종 candidate값으로 정답을 반환
    
    return cand
728x90

728x90

Major types of Loss function

1. MSE /. MSE / ,,, (회귀에 사용됨)

2. Margin-based (Discriminative setting)
• 0/1 loss
• logloss
• exponential loss
• hinge loss

 

3. Probabilistic setting
• Cross-Entropy (실제로 많이 사용됨)

- 크로스 엔트로피란 무엇?: https://3months.tistory.com/436 (아주 잘 정리됨, 이하 내용의 출처)

예측 모형은 실제 분포인 q를 모르고, 모델링을 하여 q 분포를 예측하고자 하는 것이다.
예측 모델링을 통해 구한 분포를 p(x) 라고 해보자. 실제 분포인 q를 예측하는 p 분포를 만들었을 때, 이 때 cross-entropy 는 아래와 같이 정의된다. 

Cross-Entropy

- q와 p가 모두 식에 들어가기 때문에, cross-entropy 라는 이름이 붙었다고 할 수 있다. 

- 머신러닝을 통한 예측 모형에서 훈련 데이터에서는 실제 분포인 p 를 알 수 있기 때문에(지도 학습) cross-entropy 를 계산할 수 있다.

- 즉, 훈련 데이터를 사용한 예측 모형에서 cross-entropy는 실제 값과 예측값의 차이(dissimilarity)를 계산하는데 사용.

 

예를 들어, 가방에 0.8/0.1/0.1 의 비율로, 빨간/녹색/노랑 공이 들어가 있다고 하자, 하지만 직감에는 0.2/0.2/0.6의 비율로 들어가 있을 것 같다. 이 때, entropy 와 cross-entropy 는 아래와 같이 계산된다.

 

 

- 크로스 엔트로피 based on Negative Log-likelihood

why log?: 곱의 계산을 합의 계산으로 바꾸기 위해


정답인 class에 대해 우리 모델이 부여한 score (정답이 아닌 class로 예측할 확률은 모두 생략)


why negative?


*잠시, Likelihood-function이 뭔데?

https://angeloyeo.github.io/2020/07/17/MLE.html

- likelihood 라는 것은 특별히 어려운 것이 아니고, 지금 얻은 데이터가 특정 분포로부터 나왔을 가능도.

그림 1. 획득한 데이터와 추정되는 후보 분포 2개(각각 주황색, 파란색 곡선으로 표시)

눈으로 보기에도 파란색 곡선 보다는 주황색 곡선에서 이 데이터들을 얻었을 가능성이 더 커보인다. 왜냐면 획득한 데이터들의 분포가 주황색 곡선의 중심에 더 일치하는 것 처럼 보이기 때문이다. 이 예시를 보면, 우리가 데이터를 관찰함으로써 이 데이터가 추출되었을 것으로 생각되는 분포의 특성을 추정할 수 있음을 알 수 있다. 여기서는 추출된 분포가 정규분포라고 가정했고, 우리는 분포의 특성 중 평균을 추정하려고 했다.

출처: https://angeloyeo.github.io/2020/07/17/MLE.html

그림 2. 주황색 후보 분포에 대해 각 데이터들의 likelihood 기여도를 점선의 높이로 나타냈다.

 

- 수치적으로 이 likelihood를 계산하기 위해서는 각 데이터 샘플에서 후보 분포에 대한 높이(즉, likelihood 기여도)를 계산해서 다 곱함.

출처: https://angeloyeo.github.io/2020/07/17/MLE.html

 

 

  • CrossEntropyLoss != Log-likelihood model
    ("Negative Log"-likelihood는 loss function의 미분이 용이하고 직관적이도록 돕는 아이디어같은.)

    ("Likelihood function" 역시 파라미터 추정을 위한 컨셉' 느낌.)
  • 파이토치에서 CrossEntropyLossLogSoftmax를 적용한 후 Negative Log-likelihood(NLL) Loss까지 적용한 식과 동일하다. (여기서 LogSoftmax는Softmax 함수를 적용한 후 Log 함수를 적용하는 것과 동일한 함수.)
  • CrossEntropyLoss 안에서 LogSoftmax와 Negative Log-likelihood 가 진행되기 때문에 softmax나 log 함수가 적용되지 않은 모델 output(raw data)을 input으로 주어야 합니다.
  • 이와 달리 NLLLoss 안에서는 softmax나 log함수가 이뤄지지 않습니다. 그래서 모델 output(raw data)을 input으로 그대로 사용하는 것이 아니라 LogSoftmax 함수를 적용한 후 input으로 사용해야합니다.

 

 

출처: https://supermemi.tistory.com/entry/Loss-Cross-Entropy-Negative-Log-Likelihood-%EB%82%B4%EC%9A%A9-%EC%A0%95%EB%A6%AC-Pytorch-Code


• KL Divergence (예측 분포와 정답 분포 간 차이 기반)

 

728x90

+ Recent posts