13장 연결 리스트

  • 연결 리스트란 각 노드가 달느 노드를 가리키는 자료구조이다
  • 고정된 크기를 갖는 배열과는 달리 실행 시간에 메모리를 할당하거나 해제할 수 있는 동적 자료구조
  • 연결 리스트 자료구조는 각 노드가 다음 노드에 대한 참조를 갖는 자료구조로 data와 next속성이 있다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class SinglyLinkedListNode {
    constructor (data) {
    this.data = data;
    this.next = null; // 노드 마지막 연결지점은 null값임
    }
    }

    class SinglyLinkedList {
    constructor () {
    this.head = null;
    this.size = 0;
    }

    isEmpty() {
    return this.size === 0;
    }
    }
  • 연결 리스트의 시작을 헤드라고 부르고 이 값은 아무 항목도 삽입되지 않으면 null이다
  • 연결 리스트의 헤드가 비어있는 경우엔 신규 노드로 설정되고 비어있지 않다면 예전 헤드가 temp에 저장된 후에 새로운 헤드가 신규로 추가된 노드가 된다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 위에서 이어짐
    insert(value) {
    if (this.head === null) this.head = new SinglyLinkedListNode(value);
    else {
    let temp = this.head;
    this.head = new SinglyLinkedListNode(value);
    this.head.next = temp;
    }
    this. size++;
    }
  • 단일 연결 리스트에서 노드를 삭제하는 것은 해당 노드의 참조를 제거함으로써 구현할 수 있다
  • next가 가리키는 노드를 찾고 해당 노드의 next 주소값을 이전 노드의 next값으로 할당하면 노드를 삭제할 수 있다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    remove(value) {
    let currHead = this.head;
    if (currHead.data === value) {
    // 삭제하고자 하는 노드가 지금 데이터와 같다면 헤드를 다음 노드로 변경하고 사이즈를 줄임
    this.head = currHead.next;
    this.size--;
    } else {
    // 헤드를 prv로 저장하고
    let prev = currHead;
    while (currHead.next) {
    // next 주소값이 null이 아닐때까지 순회한다
    if (currHead.data === value) {
    // 해당 값을 찾았다면 이전 값의 next를 현재 값의 next로 변경 (중간 잘라냄)
    prev.next = currHead.next;
    // prev값을 지금 head로 변경
    prev = currHead;
    currHead = currHead.next;
    break;
    }
    prev = currHead;
    currHead = currHead.next;
    }

    if (currHead.data === value) {
    // 삭제하는 노드의 next 주소값은 null로 변경하여 잘라내기
    prev.next = null;
    }

    this.size--;
    }
    }
  • 연결 리스트의 헤드에 있는 항목을 삭제하는 것은 O(1)만에 가능한데 맨 처음에 있는 값이기 때문
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    deleteHead() {
    let toReturn = null;

    if (this.head !== null) {
    toReturn = this.head.data;
    this.head = this.head.next;
    this.size--;
    }
    return toReturn;
    }
  • 연결 리스트 내의 값을 검색하려면 모든 next 포인터를 반복순회해야한다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    find (value) {
    let currHead = this.head;
    while(currHead.next) {
    if (currHead.data === value) {
    return true;
    }
    currHead = currHead.next;
    }
    return false;
    }
  • 이중 연결 리스트는 양방향 단일 연결 리스트 같은 것이다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class DoubleLinkedListNode {
    constructor (data) {
    this.data = data;
    this.next = null;
    this.prev = null;
    }
    }

    class DoubleLinkedList {
    constructor () {
    this.head = null;
    this.tail = null;
    this.size = 0;
    }

    isEmpty () {
    return this.size === 0;
    }
    }
  • 양방향으로 연결되어 있으므로 노드들은 next와 prev 둘 다 갖는다
  • 헤드에 항목을 삽입하는 것은 prev 포인터를 갱신해야 한다는 점 빼고는 단일 연결 리스트와 같다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    insertHead (value) {
    if (this.head === null) {
    // 헤드가 비어있다면 이 헤드에 새로운 값을 갖는 노드를 추가
    this.head = new DoublyLinkedListNode(value);
    this.tail = this.head;
    // 단 하나만 있을 경우에 이 노드가 헤드이자 테일이기 때문
    } else {
    // 이미 헤드가 있다면 임시로 노드를 만들고
    let temp = new DoublyLinkedListNode(value);
    // 만든 노드의 next값을 현재 헤드값으로 변경하고
    temp.next = this.head;
    // 현재 헤드값의 prev값을 새로 만든 노드로 설정한다 (양방향이므로)
    this.head.prev = temp;
    // 새로 만든 노드가 새 헤드가 된다 (있었던 헤드가 밀려남)
    this.head = temp;
    }
    this.size++;
    }
  • 헤드를 삭제할 때 연결 리스트가 하나의 노드라면 prev와 next 모두 null로 설정해야 하며 여러개 존재하는 경우 삭제하는 헤드의 next값을 헤드로 설정한다
  • 신규 헤드의 prev값을 null로 설정하여 참조를 제거한다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    deleteHead () {
    let toReturn = null;
    if (this.head !== null) {
    toReturn = this.head.data;

    if (this.tail === this.head) {
    this.head = null;
    this.tail = null;
    } else {
    this.head = this.head.next;
    this.head.prev = null;
    }
    }
    this.size--;
    return toReturn; // dequeue와 같은 방식임
    }
  • 테일 항목 삭제도 헤드 삭제와 같은 방식으로 제거한다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    deleteTail() {
    let toReturn = null;

    if(this.tail !== null) {
    toReturn = this.tail.data;

    if (this.tail === this.head) {
    this.head = null;
    this.tail = null;
    } else {
    this.tail = this.tail.prev;
    this.tail.next = null;
    }
    }
    this.size--;
    return toReturn;
    }
  • 양항향 연결 리스트에서는 헤드에서 헤드의 next 포인터를 사용할 수 있다 (반대로도 가능)
    1
    2
    3
    4
    5
    6
    7
    8
    findFromHead (value) {
    let currHead = this.head;
    while (currHead.next) {
    if (currHead.data === value) return true;
    currHead = currHead.next;
    }
    return false;
    }
  • 단일 연결 리스트 뒤집기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const reverseSinglyLinkedList = sll => {
    let node = sll.head;
    let prev = null;
    // 방향성을 반대로 바꾸는 과정
    while (node) {
    let temp = node.next;
    node.next = prev;
    prev = node;
    if (!temp) break;
    node = temp;
    }
    return node;
    }
  • 연결 리스트에서 중복된 항목 제거하기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const deleteDuplicateDataInSLL = sll => {
    let track = [];

    let temp = sll.head;
    let prev = null;
    while (temp) {
    if (track.indexOf(temp.data) >= 0) {
    prev.next = temp.next;
    sll.size--;
    } else {
    track.push(temp.data);
    prev = temp;
    }
    temp = temp.next;
    }
    console.log(temp);
    }
  • 개선하기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const deleteDuplicateDataInSLL = sll => {
    let track = {};
    let temp = sll.head;
    let prev = null;
    while (temp) {
    if(track[temp.data]) {
    prev.next = temp.next;
    sll.size--;
    } else {
    track[temp.data] = true;
    prev = temp;
    }
    temp = temp.next;
    }
    console.log(temp);
    }

    14장 캐싱

  • 캐싱은 자료를 임시 메모리에 저장하는 과정으로 대표적인 캐싱 방법에는 LFU(Least Frequently Used) 캐싱과 LRU(Least Recently Used) 캐싱이 있다
  • 최근에 접근한 메모리 위치를 다시 접근할 가능성이 높고(시간적 지역성), 최근에 접근한 메모리 위치 주변의 위치를 다시 접근할 가능성이 높다(공간적 지역성)
  • LFU 캐싱은 운영체제가 메모리를 관리하기 위해 사용하는 캐싱 알고리즘으로 가장 참조 빈도가 낮은 항목을 삭제한다
  • LFU 캐시는 이중 연결 리스트를 사용해 O(1)시간에 항목을 제거한다
  • LFU의 노드에는 freqCount 속성이 있다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    class LFUNode {
    constructor (key, value) {
    this.prev = null;
    this.next = null;
    this.key = key;
    this.data = data;
    this.freqCount = 1;
    }
    }

    class LFUDoublyLinkedList {
    constructor () {
    this.head = new LFUNODE('buffer head', null);
    this.tail = new LFUNODE('buffer tail', null);
    this.head.next = this.tail;
    this.tail.prev = this.head;
    this.size = 0;
    }

    insertHead (node) {
    node.next = this.head.next;
    this.head.next.prev = node;
    this.head.next = node;
    node.prev = this.head;
    this.size++;
    }

    removeTail () {
    let oldTail = this.tail.prev;
    let prev = this.tail.prev;
    prev.prev.next = this.tail; // 전 전 노드의 next를 테일로 지정
    this.tail.prev = prev.prev; // 테일을 제거하면 테일 앞 값을 테일로 지정해주어야 함
    this.size--;
    return oldTail;
    }

    removeNode(node) {
    node.prev.next = node.next;
    node.next.prev = node.prev;
    this.size--;
    }
    /*
    LFU의 set을 구현하기 위해서는 신규 항목의 삽입과 예전 항목의 교체가 필요함
    신규 항목을 삽입할 때 신규 노드가 생성되고 캐시 capacity가 꽉차지 않았다면 freq의 빈도 이중 연결 리스트에 삽입할 수 있다
    캐시가 꽉 찬 경우 빈도 이중 연결 리스트의 테일 항목이 삭제된 다음 삽입된다
    */
    set(key, value) {
    let node = this.keys[key];
    if (node === undefined) {
    node = new LFUNode(key, value);

    this.keys[key] = node;

    if (this.size !== this.capacity) {
    if (this.freq[1] === undefined) {
    this.freq[1] = new LFUDoublyLinkedList();
    }
    this.freq[1].insertHead(node);
    this.size++
    } else {
    let oldTail = this.freq[this.minFreq].removeTail();
    delete this.keys[oldTail.key];

    if (this.freq[1] === undefined) {
    this.req[1] = new LFUDoublyLinkedList();
    }

    this.freq[1].insertHead(node);
    this.minFreq = 1;
    } else {
    let oldFreqCount = node.freqCount;
    node.data = value;
    node.freqCount++;

    this.freq[oldFreqCount].removeNode(node);

    if (this.freq[node.freqCount] === undefined) {
    this.freq[node.freqCount] = new LFUDoublyLinkedList();
    }

    this.freq[node.freqCount].insertHead(node);

    if (oldFreqCount === this.minFreq
    && Object.keys(this.freq[oldFreqCount]).size === 0) {
    this.minFreq++;
    }
    }
    }
    }

    get (key) {
    let node = this.keys[key];

    if (node === undefined) return null;
    else {
    let oldFreqCount = node.freqCount;
    node.freqCount++;
    this.freq[oldFreqCount].removeNode(node);

    if (this.freq[node.freqCount] === undefined) {
    this.freq[node.freqCount] = new LFUDoublyLinkedList();
    }

    this.freq[node.freqCount].insertHead(node);

    if (oldFreqCount === this.minFreq
    && Object.keys(this.freq[oldFreqCount]).length === 0) {
    this.minFreq++;
    }
    return node.data;
    }
    }
    }

    class LFUCache {
    constructor (capacity) {
    this.key = {}; // LFUNode를 저장하는 공간
    this.freq = {};// LFUDoublyLinkedList를 저장하는 공간
    this.capacity = capacity;
    this.minFreq = 0;
    this.size = 0;
    }
    }
  • LRU 캐싱은 가장 오래된 항목을 먼저 제거하는 캐싱 알고리즘이다
  • 캐시의 항목에 접근하면 해당 항목은 리스트의 뒤(가장 최신)로 이동하고 가장 앞에 있는 항목이 제거되며 새 항목은 가장 뒤에 추가된다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    class DLLNode {
    constructor (key, data) {
    this.key = key;
    this.data = data;
    this.next = null;
    this.prev = null;
    }
    }

    class LRUCache {
    constructor (capacity) {
    this.keys = {};
    this.capacity = capacity;
    this.head = new DLLNode('', null);
    this.tail = new DLLNode('', null);
    this.head.next = this.tail
    this.tail.prev = this.head;
    }

    removeNode (node) {
    let prev = node.prev,
    next = node.next;
    prev.next = next;
    next.prev = prev;
    }

    addNode (node) {
    let realTail = this.tail.prev;
    realTail.next = node;

    this.tail.prev = node;
    node.prev = realTail;
    node.next = this.tail;
    }

    get (key) {
    let node = this.keys[key];
    if (node === undefined) return null;
    else {
    this.removeNode(node);
    this.addNode(node);
    return node.data;
    }
    }

    set (key, value) {
    let node = this.keys[key];
    if (node) this.removeNode(node);

    let newNode = new DLLNode(key, value);

    this.addNode(newNode);
    this.keys[key] = newNode;

    if (Object.keys(this.keys).length > this.capacity) {
    let realHead = this.head.next;
    this.removeNode(realHead);
    delete this.keys[realHead.key];
    }
    }
    }
  • 대부분의 경우 LFU는 LRU보다 성능이 떨어진다 LFU는 특정 시점에 한해 자주 사용된 경우를 배제할 수 없기 때문

Comment and share

11장 해시테이블

  • 해시 테이블은 키-값 쌍을 기반으로 자료를 얻을 수 있다
  • 해시 테이블에는 put()과 get()의 두 함수가 있어 자료를 저장하고 얻어올 수 있다
  • localStorage는 해시 테이블에 기반한 자료구조로 모든 브라우저가 지원하는 기본 자바스크립트 객체이다
  • 해시 테이블에서 가장 중요한건 해시 함수로 이 함수는 특정 키를 자료를 저장하는 배열의 인덱스로 변환한다
  • 좋은 해시 함수는 동일한 키는 동일한 해시값을 생성해야 하며(결정성), 시간 복잡도는 O(1)이어야 하며(효율성) 배열 전체를 최대한 활용해야 한다(균일한 분배)
  • 해싱의 첫번째 기법은 소수를 사용하는 것 이다
  • 소수에 의한 모듈러는 고정된 크기에 대해 가장 균등한 분배를 보장한다
  • 소수 모듈러로 나눈 나머지 값으로 인덱싱하는 해시 테이블에서 같은 값이 있다면 충돌이 일어나게 되는데 이를 회피하기 위하여 탐사 해싱 기법을 이용한다
  • 선형 탐사는 만약 같은 키로 해싱된다면 그 다음 빈 곳을 찾아 해싱한다
  • 이 때 get(key)함수를 사용한다면 충돌된 부분을 찾은 후에 해당 값을 찾고자 테이블을 순회한다
  • 이차 탐사는 선형 탐사와는 달리 1씩 증가시키지 않고 완전 제곱을 사용하여 사용 가능한 인덱스에 키를 균등하게 분배한다
  • 이차 해싱 함수를 사용해 원래 해싱 함수로부터 나온 결과를 한 번 더 해싱하면 키를 균일하게 분배할 수 있다
  • 해시 테이블 구현 (교재에는 옛날 방식으로 구현되어 있어서 class 문법을 사용하였음)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    class HashTable {
    constructor(size) {
    this.size = size;
    this.keys = this.initArray(size);
    this.values = this.initArray(size);
    this.limit = 0;
    }

    put(key, value) {
    if (this.limit >= this.size)
    throw 'hash table is full';

    let hashedIndex = this.hash(key);

    while (this.keys[hashedIndex] != null) {
    hashedIndex++
    hashedIndex = hashedIndex % this.size;
    }
    this.keys[hashedIndex] = key;
    this.values[hashedIndex] = value;
    this.limit++
    }

    get(key) {
    let hashedIndex = this.hash(key);

    while (this.keys[hashedIndex] != key) {
    hashedIndex++
    hashedIndex = hashedIndex % this.size;
    }
    return this.value[hashedIndex];
    }

    hash(key) {
    if(!Number.isInteger(key))
    throw 'must be number';
    return key % this.size;
    }

    initArray(size) {
    return Array.from({length: size}, () => null);
    }
    }
  • 이차 탐사 사용하기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    class HashTable {
    constructor(size) {
    this.size = size;
    this.keys = this.initArray(size);
    this.values = this.initArray(size);
    this.limit = 0;
    }

    put(key, value) {
    if (this.limit >= this.size)
    throw 'hash table is full';

    let hashedIndex = this.hash(key);

    while (this.keys[hashedIndex] != null) {
    hashedIndex++
    hashedIndex = hashedIndex % this.size;
    }
    this.keys[hashedIndex] = key;
    this.values[hashedIndex] = value;
    this.limit++
    }

    get(key) {
    let hashedIndex = this.hash(key);

    while (this.keys[hashedIndex] != key) {
    hashedIndex++
    hashedIndex = hashedIndex % this.size;
    }
    return this.value[hashedIndex];
    }

    hash(key) {
    if(!Number.isInteger(key))
    throw 'must be number';
    return key % this.size;
    }

    initArray(size) {
    return Array.from({length: size}, () => null);
    }
    }
  • 이차 탐사 (put, get 함수만)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    put(key, value) {
    if (this.limit >= this.size)
    throw 'hash table is full';

    let hashedIndex = this.hash(key), squareIndex = 1;

    while (this.keys[hashedIndex % this.size] != null) {
    hashedIndex += Math.pow(squareIndex, 2);
    squareIndex++;
    }
    this.keys[hashedIndex % this.size] = key
    this.value[hashedIndex % this.size] = value;
    this.limit++
    }

    get(key) {
    let hashedIndex = this.hash(key);

    while (this.keys[hashedIndex] != key) {
    hashedIndex++
    hashedIndex = hashedIndex % this.size;
    }
    return this.value[hashedIndex];
    }
  • 선형 탐사를 활용한 이중 해싱
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    put(key, value) {
    if (this.limit >= this.size)
    throw 'hash table is full';

    let hashedIndex = this.hash(key);

    while (this.keys[hashedIndex] != null) {
    hashedIndex++;
    hashedIndex = hashedIndex % this.size;
    }
    this.keys[hashedIndex] = key
    this.value[hashedIndex] = value;
    this.limit++
    }

    get(key) {
    let hashedIndex = this.hash(key);

    while (this.keys[hashedIndex] != key) {
    hashedIndex++
    hashedIndex = hashedIndex % this.size;
    }
    return this.value[hashedIndex];
    }

    hash(key) {
    if (!Number.isInteger(key))
    throw 'must be number'
    return this.secondHash(key);
    }

    secondHash(hashedKey) {
    let R = this.size - 2;
    return R - hashedKey % R;
    }
  • 해시테이블은 크기가 처음에 정의되는(size를 매개변수로 생성자에 전달한 것을 보자) 고정된 크기의 자료구조이다
  • 해시 함수를 사용해 균등하게 분배하고 충돌이 일어나는 것을 최대한 막아야한다

12장 스택과 큐

  • 스택은 마지막에 삽입된 항목만을 제거하고 접근할 수 있다
  • 자바스크립트에서 배열에는 스택 클래스를 정의한 pop과 push라는 메서드가 있다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class Stack {
    constructor (arr) {
    this.arr = [];
    if(arr) this.arr = arr;
    }

    getBuffer() {
    return this.arr.slice();
    }

    isEmpty() {
    return this.arr.length === 0;
    }

    peek() {
    return this.arr[this.arr.length-1];
    }

    push(value) {
    this.arr.push(value);
    }

    pop() {
    return this.arr.pop();
    }
    }
  • 위에서부터 n번째 노드에 접근하기 위해 pop을 n번 호출해야 한다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const stackAccessNthTopNode = (stack, n) => {
    let bufferArr = stack.getBuffer();
    if (n <= 0) throw 'error';

    let bufferStack = new Stack(bufferArr);

    while(--n! === 0)
    bufferStack.pop();

    return bufferStack.pop();
    }
  • 검색 또한 이와 비슷한 방식으로 구현할 수 있다
    1
    2
    3
    4
    5
    6
    7
    8
    const stackSearch = (stack, element) => {
    let bufferArr = stack.getBuffer();
    let bufferStack = new Stack(bufferArr);
    while(!bufferStack.isEmpty()){
    if (bufferStack.pop() === element) return true;
    }
    return false;
    }
  • 큐는 스택과 달리 첫번째로 추가도니 항목만을 제거할 수 있는 자료 구조이다
  • 자바스크립트에서는 shift()와 push()라는 메서드가 있어 이 함수를 호출하면 배열의 첫번째 항목을 제거해 리턴한다
  • 큐에 추가하는 것을 인큐(enqueuing)라고, 제거하는 것을 디큐(dequeuing)라 한다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class Queue {
    constructor (arr) {
    this.arr = [];
    if(arr) this.arr = arr;
    }

    getBuffer() {
    return this.arr.slice();
    }

    isEmpty() {
    return this.arr.length === 0;
    }

    peek() {
    return this.arr[0]; // 제거하지 않고 값만 리턴
    }

    enqueue(value) {
    return this.arr.push(value);
    }

    dequeue() {
    return this.arr.shift();
    }
    }
  • 큐에 접근할 때엔 인덱스로 접근할 수 없다 n번째 노드에 접근하려면 디큐를 n번 호출해야 한다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const queueAccessNthTopNode = (queue, n) => {
    let bufferArr = queue.getBuffer();
    if (n <= 0) throw 'error'

    let bufferQueue = new Queue(bufferArr);

    while(--n !== 0)
    bufferQueue.dequeue(); // 필요한 노드 직전까지 디큐

    return bufferQueue.dequeue(); // 리턴으로 디큐하면 해당 노드가 필요한 노드
    }
  • 스택을 사용해 큐를 구현하기
    → 두개의 스택을 사용한다면 큐를 만들 수 있다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class TwoStackQueue {
    constructor() {
    this.inbox = new Stack();
    this.outbox = new Stack();
    }

    enqueue(value) {
    this.inbox.push(value);
    }

    dequeue() {
    if(this.outbox.isEmpty()) {
    while(!this.inbox.isEmpty()){
    this.outbox.push(this.inbox.pop());
    }
    }
    return this.outbox.pop();
    }
    }
  • 큐를 사용해 스택을 구현하기도 마찬가지로 두 개의 큐를 사용하면 스택을 구현할 수 있다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class QueueStack {
    constructor() {
    this.inbox = new Queue();
    }

    push(value) {
    this.inbox.enqueue(value);
    }

    pop() {
    let size = this.inbox.arr.length - 1;
    let cnt = 0;
    let bufferQueue = new Queue();
    while(++counter <= size)
    bufferQueue.enqueue(this.inbox.dequeue());
    let popped = this.inbox.dequeue();
    this.inbox = bufferQueue;
    return popped;
    }
    }
  • 고객 객체를 매개변수로 받아 선입선출(Queue) 방식으로 음식을 주문 처리하는 점원 클래스를 설계
    1. 점원은 주문을 위해 고객의 이름과 주문 항목을 요구
    2. 첫번째로 받은 주문을 먼저 처리함
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      class Customer {
      constructor (name, order) {
      this.name = name;
      this.order = order;
      }

      addOrder (customer) {
      this.customers.enqueue(customer);
      }

      deliverOrder() {
      let finishedCustomer = this.customers.dequeue();
      }
      }

      function Cashier() {
      this.customers = new Queue();
      }
  • 스택을 사용해 괄호 검증기를 설계하기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const BracketVerification = string => {
    let stack = new Stack();
    for (let pos = 0; pos < string.length; pos++) {
    let currentChar = string.charAt(pos);
    if(currentChar === "("){
    stack.push(currentChar);
    } else if (currentChar ===")") {
    if(stack.isEmpty()) return false;
    stack.pop();
    }
    }
    return stack.isEmpty();
    }
  • 정렬 가능한 스택 설계하기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class sortableStack {
    constructor (size) {
    this.size = size;
    this.mainStack = new Stack();
    this.sortedStack = new Stack();
    for (let i = 0; i < this.size; i++) {
    this.mainStack.push(Math.floor(Math.random() * 11));
    }
    }

    sortStackDescending() {
    while(!this.mainStack.isEmpty()){
    let temp = this.mainStack.pop();
    while(!this.sortedStack.isEmpty && this.sortedStack.peek() <temp) {
    this.mainStack.push(this.sortedStack.pop());
    }
    this.sortedStack.push(temp)
    }
    }
    }

Comment and share

9장 집합

  • 집합이란 유한하고 구분되는 객체들의 그룹
  • 자바스크립트에서는 Set이 기본으로 지원된다
    let exampleSet = new Set();
  • Set의 주요 특징은 유일함을 확인 한다는 것이다(중복되는 항목은 허용되지 않음)
  • Set.delete는 boolean값을 리턴하며 항목을 삭제할 수 있게 하는 메서드이다
  • Set.has는 집합 내에 존재하는지 확인하며 .delete / .has는 모두 O(1)의 시간 복잡도를 갖는다
  • Set을 사용하여 교집합 구하기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const intersectSets = (setA, setB) => {
    let intersection = new Set();
    for (let element of setB) {
    if (setA.has(element)) {
    intersection.add(element)
    }
    return intersection
    }
    } // O(n)
  • 어떤 집합이 다른 집합의 모든 항목을 포함하는 경우에는 상위 집합이 된다
    1
    2
    3
    4
    5
    6
    7
    const isSuperSet = (setA, subSet) => {
    for (let element of subSet) {
    if(!setA.has(element))
    return false;
    }
    return true;
    }
  • Set을 사용한다면 합집합도 쉽게 구현할 수 있다
    1
    2
    3
    4
    5
    6
    7
    const unionSet = (setA, setB) => {
    let union = new Set(setA);
    for (let element of setB) {
    union.add(element);
    }
    return union
    }
  • Spread Operator를 사용한 방법
    1
    2
    3
    const unionSetWithSpreadOperator = (setA, setB) => {
    return new Set([...setA,...setB]);
    }
  • 차집합은 .delete로 구현할 수 있다
    1
    2
    3
    4
    5
    6
    7
    const differenceSet = (setA, setB) => {
    let differ = new Set(setA);
    for (let element of setB) {
    differ.delete(element);
    }
    return differ;
    }
  • 집합을 사용해 배열의 중복 항목을 확인하기
    1
    2
    3
    4
    const checkDupl = arr => {
    let mySet = newSet(arr);
    return mySet.size < arr.length;
    }
  • 개별적인 배열들에서 유일한 값만을 반환하기
    1
    2
    3
    4
    5
    const uniqueList = (arr1, arr2) => {
    // arr1 뒤에 arr2 배열을 붙이고 Set을 사용해 유니크한 값만 남기기
    let mySet = new Set(arr1.concat(arr2));
    return Array.from(mySet);
    }

    10장 검색과 정렬

  • 검색은 자료 구조의 항목들을 반복적으로 접근하는 것 , 정렬은 자료구조의 항목들을 순서대로 위치시키는 것
  • 한 인덱스씩 순차적으로 접근하는 선형 검색
    1
    2
    3
    4
    5
    6
    const linearSearch = (arr, n) => {
    for (let i = 0, len = arr.length; i < len; i++) {
    if (arr[i] === n) return true;
    }
    return false;
    }
  • 정렬된 자료라면 이진검색을 사용할 수 있다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const binarySearch = (arr, n) => {
    let lowIndex = 0, highIndex = arr.length;
    while (lowIndex <= highIndex) {
    let midIndex = Math.floor((lowIndex + highIndex)/2);
    if (arr[midIndex] === n) return midIndex;
    else if (n > arr[midIndex]) lowIndex = midIndex+1;
    else highIndex = midIndex-1;
    return -1;
    }
    }
  • 이진검색을 사용하려면 자료를 정렬해야함
  • 버블 정렬은 전체 배열을 순회하며 두 항목을 비교하고 교체하는 작업을 반복한다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const swap = (arr, idx1, idx2) => {
    let temp = arr[idx1];
    arr[idx1] = arr[idx2];
    arr[idx2] = temp;
    }

    const bubbleSort = arr => {
    for (let i = 0, len = arr.length; i < len; i++) {
    for (let j = 0; j <= i; j++){
    if (arr[j] > arr[j+1]) {
    swap(arr, i, j)
    }
    }
    }
    return arr;
    } // for문을 두 번 사용하므로 O(n의 제곱)값이 된다
  • 선택 정렬은 가장 작은 항목을 찾고 해당 항목의 배열의 현 위치에 삽입하는 식으로 동작한다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const selectionSort = items => {
    let len = items.length, min;
    for (let i = 0; i < len; i++) {
    min = i;
    for (j = i+1; j < len; j++){
    if(items[j] < items[min]) min = j;
    if (i != min) swap(items, i, min);
    }
    }
    return items;
    }
  • 삽입 정렬을 배열을 순차적으로 검색하면서 정렬되지 않은 항목들을 왼쪽으로 이동시킨다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const insertionSort = items => {
    let len = items.length,
    value, // 지금 비교중인 값
    i, // 정렬 안된 부분
    j; // 정렬 된 부분
    for (let i = 0; i < len; i++){
    //
    value = items[i];
    for (j = i-1; j > -1 && items[j] > value; j--) {
    items[j+1] = items[j]
    }
    items[j+1] = value;
    }
    return items
    }
  • 퀵 정렬은 기준점을 기준으로 배열을 나눠 모든 항목이 정렬될 때까지 과정을 반복하는 정렬법이다
  • 이 방법은 분할 정복을 이용한 재귀법으로 처리되어 평균 O(nlog2(n))으로 처리되지만 최악의 경우(이미 정렬된 배열을 받을 경우) O(n의 제곱)값을 갖는다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    const quickSort = arr => {
    return quickSortHelper(arr, 0, arr.length-1);
    }

    const quickSortHelper = (arr, left, right) => {
    let idx;
    if (arr.length > 1) {
    idx = partition(arr, left, right);
    if (left < idx - 1) quickSortHelper(arr, left, idx -1);
    if (idx < right) quickSortHelper(arr, idx, right)
    }
    return arr;
    }

    const partition = (arr, left, right) => {
    let pivot = arr[Math.floor((left+right)/2)] // 배열의 중간값을 탐색
    // 정렬하다보면 left는 계속 증가하고 right는 계속 감소하므로
    // left값이 right값을 넘어가게 됨 (base condition)
    while (left <= right) {
    while(pivot > arr[left]) left++
    while(pivot < arr[right]) right++
    if (left <= right) {
    let temp = arr[left];
    arr[left] = arr[right];
    arr[right] = temp;
    left++;
    right--;
    }
    }
    return left;
    }
  • 퀵정렬의 단점은 기준점을 항상 잘못 선택하는 경우 시간 복잡도가 O(n의 제곱)이 될 수 있다는 것
  • 퀵선택(quick select)은 정렬되지 않은 목록에서 k번째로 작은 항목을 찾는 선택 알고리즘으로 퀵정렬과는 달리 한쪽만 재귀적으로 수행함
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 정렬되지 않은 목록에서 k번째로 작은 항목을 찾기
    const quickSelect = (arr, low, high, k) => {
    let p = partition(arr, low, high);
    if (p == (k-1)) return A[p];
    else if (p > (k-1)) return quickSelect(arr, low, p-1, k);
    }

    const medianQuickSelection = arr => {
    return quickSelect(arr, 0, arr.length-1, Math.floor(array.length/2))
    } // O(n)의 시간복잡도
  • 병합 정렬는 각 배열에 하나의 항목이 존재할때까지 나누고 순서대로 연결함
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const merge = (leftArr, rightArr) => {
    let results = [], leftIdx = 0, rightIdx = 0;
    while (leftIdx < leftArr.length && rightIdx < rightArr.length){
    if (leftArr[leftIdx] < rightArr[rightIdx]) {
    results.push(leftArr[leftIdx++]);
    } else {
    results.push(rightArr[rightIdx++]);
    }
    }
    let leftReamins = leftArr.slice(leftIdx),
    rightRemains = rightArr.slice(rightIdx);
    return results.concat(leftReamains).concat(rightRemains);
    }

    // 재귀함수로 배열을 전부 길이 1인 배열로 쪼개고 merge로 합하고를 반복한다
    const mergeSort = arr => {
    if (arr.length <2) return arr;

    let mid = Math.floor(arr.length/2),
    leftArr = arr.slice(0, mid),
    rightArr = arr.slice(mid);

    return merge(mergeSort(leftArr), mergeSort(rightArr))
    }
  • 계수 정렬은 값들을 비교하지 않기 때문에 O(k+n)시간 안에 수행된다
  • 계수 정렬은 숫자에 대해서만 동작하고 특정 범위가 주어져야 한다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const countSort = arr => {
    let hash = {}, countArr =[];
    // 해시 테이블을 생성하고 해시 테이블에 해당 값의 갯수를 셈
    for (let i = 0, len = arr.length; i < len; i++) {
    if (!hash[arr[i]]) hash[array[i]] = 1;
    else hash[array[i]]++;
    }
    // 값의 숫자를 센 해시테이블에서 값을 꺼내고 그 수 만큼 push를 반복
    for (let key in hash) {
    for (let i = 0; i < hash[key]; i++) {
    countArr.push(parseInt(key));
    }
    }
    return countArr;
    } // O(k+n) 시간 복잡도를 가짐
  • 자바스크립트의 내장 정렬 함수인 sort는 기본 오름차순으로 정렬하고 arr.sort((a,b) => a-b)와 같이 사용한다
  • 수학 라이브러리를 사용하지 않고 정수의 제곱근 함수 구하기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const sqrtIntNaive = num => {
    if (num === 0 || num === 1) return num;

    let idx = 1, square = 1;
    while (square < number) {
    if (square === number) return square;

    idx++;
    square = idx*idx;
    }
    return idx;
    }
  • 이진 검색 알고리즘을 이용한 정수의 제곱근 함수 구하기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const sqrtInt = num => {
    if (num === 0 || num === 1) return num;

    let start = 1, end = num, ans;

    while (start <= end) {
    let mid = parseInt((start+end)/2)
    if (mid * mid === num) return mid;
    if (mid * mid < num) {
    start = mid+1;
    ans = mid
    } else {
    end = mid -1
    }
    }
    return ans;
    } // O(log2(n))
  • 배열의 두 항목을 더해서 주어진 수가 될 수 있는지 확인하기
    1
    2
    3
    4
    5
    6
    7
    8
    const findTwoSum = (arr, sum) => {
    for (let i = 0, len = arr.length; i < len; i++) {
    for (let j = i+1; j < len; j++) {
    if (arr[i]+arr[j] === sum) return true
    }
    }
    return false
    }
  • 배열에서 단 한번만 등장하는 항목 찾기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    const findOnlyOnce = (arr, low, high) => {
    if (low > high) return null; // 배열의 길이가 0일때는 널값을 리턴
    if (low === high) return arr[low]; // 길이가 1일때

    let mid = Math.floor((high+low)/2); // 절반값을 구하고

    if (mid % 2 === 0) {
    if (arr[mid] === arr[mid+1]) // 오른쪽과 비교
    return findOnlyOnce(arr, mid+2, high);
    else
    return findOnlyOnce(arr, low, mid)
    } else {
    if (arr[mid] === arr[mid-1]) // 왼쪽과 비교
    return findOnlyOnce(arr, mid+1, high);
    else
    return findOnlyOnce(arr, low, mid-1);
    }
    }

    const findOnlyOnceHelper = arr => {
    return findOnlyOnce(arr, 0, arr.length);
    }
  • 문자열을 길이순으로 정렬하는 자바스크립트 정렬 비교 함수
    1
    2
    3
    const sortByStringLength = arr => {
    return arr.sort((a, b) => a.length - b.length)
    }
  • 단어 세기 목록 구현하기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    const wordCnt = sentence => {
    let wordsArr = sentence.replace(/[.]/g, "").split(''),
    occurenceList = {}, answerList = {};

    for (let i = 0, wordsLen = wordsArr.length; i < wordsLen; i++) {
    let currWord = wordsArr[i];
    if (!occurenceList[currWord]) occurenceList[currWord] = 1;
    else occurenceList[currWord]++;
    }

    let arrTemp = [];
    for (let prop in occurenceList) {
    arrTemp.push([occurenceList[props], prop])
    }

    arrTemp.sort((a,b) => b[0] - a[0]);

    for (let i = 0, len = arrTemp.length; i < len; i++) {
    let curr = arrTemp[i];
    answerList[curr[1]] = curr[0];
    }
    return answerList;
    }

Comment and share

6장 자바스크립트 객체

  • 자바스크립트가 여러 용도로 사용될 수 있는 이유는 객체 덕분이다
  • 객체 상수 {} 또는 new Object()를 통해 객체를 생성할 수 있다
  • object.propertyName 또는 object[‘propertyName’]으로 접근할 수 있다
  • 프로토타입을 활용 상속은 자바스크립트의 유일한 상속방법이다
  • 자바스크립트는 동적이고 클래스는 새로운 함수 멤버를 이후에 필요할 때 추가할 수 있기 때문
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const classExample = () => {
    this.array = [1,2,3,4,5];
    this.name = "JavaScript";
    }

    let example1 = new classExample();

    classExample.prototype.sayName = function() {
    console.log(this.name);
    }
    // 동적으로 생성된 객체에 함수를 추가할 수 없다
    // example1.prototype.sayName2 = function() {} 같은 것은 불가능
  • 객체의 속성들을 다른 범위에서도 직접 접근할 수 있다
  • 비공개 변수(캡슐화)를 흉내내려면 지역변수를 선언하고 해당 변수에 접근할 수 있는 getter(), setter()를 만들 수 있다
  • 객체에 속성을 추가하기
    1
    2
    3
    4
    let obj = {};
    obj.example = 'value';
    obj['example'] = 'change';
    // 어떤 식으로 접근하든 성능상의 차이는 없음

    7장 자바스크립트 메모리 관리

  • V8 자바스크립트 엔진 및 최신 자바스크립트 엔진에는 가비지 컬렉터가 있어 사용하지 않는 변수를 삭제한다
  • 객체에 대한 참조가 있다면 해당 참조는 메모리에 있는 것이고, 객체 내에 저장된 변수를 참조할 때엔 해당 객체 전체를 로딩한다
  • DOM 항목을 가리키는 변수가 이벤트 콜백 외부에 선언된 경우엔 해당 DOM 항목을 제거하더라도 메모리에 남게 된다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    let one = document.getElementById('one');
    let two = document.getElementById('two');
    one.addEventListener('click', () => {
    two.remove();
    console.log(two); // 삭제 이후에도 two를 참조하게 되므로 메모리 누수가 발생한다
    })
    /*-------------------------------------*/
    let one = document.getElementById('one');
    one.addEventListener('click', () => {
    let two = document.getElementById('two');
    // 블록 내에서만 사용하도록 처리하면 메모리 누수를 줄일 수 있다
    two.remove();
    })
    /*-------------------------------------*/
    let one = document.getElementById('one');
    one.addEventListener('click', () => {
    let two = document.getElementById('two');
    // 블록 내에서만 사용하도록 처리하면 메모리 누수를 줄일 수 있다
    two.remove();
    })
    one.removeEventListener('click');
  • 객체가 window 전역 객체에 포함되는 경우에도 해당 객체는 메모리에 존재하므로 가능하면 전역 변수는 사용하지 않는 것이 좋다
  • 객체 내 하나의 속성만 필욯나 경우 전체 객체를 매개변수로 전달해서는 안된다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let test = { prop : 'test' }
    /* 이렇게 구현해서는 안된다
    const printProp = (test) => {
    console.log(test.prop)
    }
    */
    const printProp = prop => {
    console.log(prop)
    }
    printProp(test.prop);
  • 원치 않는 객체 속성은 delete 연산자로 제거할 수 있다 (객체가 아니라면 작동하지 않는다)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const colorCount = arr => {
    // 함수 밖에서 선언하면 메모리 낭비가 되므로 안에 선언해주자
    let RED = 0, GREEN = 1, BLUE = 2;
    let counter = new Array(3).fill(0);
    for (let i = 0, arrLen = arr.length; i < arrLen; i++) {
    let curr = arr[i];
    if (curr === RED) {
    counter[RED]++
    } else if (curr === GREEN) {
    counter[GREEN]++
    } else if (curr === BLUE) {
    counter[BLUE]++
    }
    }
    return counter;
    }

    8장 재귀

  • 재귀함수는 대개 분할 정복에 사용된다
  • 재귀함수를 사용하려면 점차 기저 조건 에 접근하는 분할 정복 방식 으로 구성하여야 한다
  • 피보나치 수열은 대표적인 예
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // for문을 활용한 함수
    const getFibo = n => {
    if (n <= 1) return n;
    let sum = 0, last = 1, lastlast = 0;
    for (let i = 1; i < n; i++){
    sum = lastlast + last;
    lastlast = last;
    last = sum;
    }
    return sum;
    }
    1
    2
    3
    4
    5
    const getFibo = n => {
    if (n <= 1) return n
    else return getFibo(n-1) + getFibo(n-2);
    // n-1 = last, n-2 = lastlast 인 셈
    }
  • 꼬리재귀를 이용한 구현
    1
    2
    3
    4
    5
    const getFiboBetter = (n, lastlast, last) => {
    if (n === 0) return lastlast;
    if (n === 1) return last;
    return getFiboBetter(n-1, last, lastlast + last)
    }
  • 파스칼의 삼각형
    1
    2
    3
    4
    5
    6
    const pascalTriangle = (row, col) => {
    if (col === 0) return 1;
    else if (row === 0) return 0;
    else
    return pascalTriangle(row - 1, col) + pascalTriangle(row -1, col - 1);
    }
  • 삼항연산자를 이용한 파스칼의 삼각형
    1
    2
    3
    4
    5
    const pascalTriangle = (row, col) => {
    return (col === 0) ? 1
    : (row === 0) ? 0
    : pascalTriangle(row - 1, col) + pascalTriangle(row -1, col - 1);
    }
  • 재귀의 빅오 분석은 점화식 을 분석해야한다
  • 이 점화식을 분석할 때 마스터 정리 를 자주 사용한다
    → T(n) = aT(n/b) + O(n^c) (단, a >= 1 , b >= 1)
    → a는 재귀호출에 곱해지는 계수이고 b는 로그 항임
    → 비재귀 요소인 O(n^c)의 c가 logb(a)보다 작다면 T(n) = O(n^3)
    → c가 logb(a)와 같은 경우엔 T(n) = O(nlog(n))
    → c가 logb(a)보다 크다면 T(n) = O(n^2)
  • 10진수를 2진수로 변환하기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 1. base condition
    const base10ToString(n) {
    let binaryString ='';

    function base10ToStringHelper(n) {
    if (n < 2) {
    binaryString += n;
    return;
    } else {
    base10ToStringHelper(Math.floor(n/2));
    base10ToStringHelper(n%2);
    }
    }
    base10ToStringHelper(n);

    return binaryString;
    } // 시간 복잡도 O(log2(n));
  • 배열의 모든 순열 출력하기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 1. base condition : beginIndex === endIndex;
    const swap = (strArr, index1, index2) => {
    let temp = strArr[index1];
    strArr[index1] = strArr[index2];
    strArr[index2] = temp;
    }

    const permute = (strArr, begin, end) => {
    if (begin === end) console.log(strArr);
    else {
    for (let i = begin; i < end + 1; i++){
    swap (strArr, begin, i);
    permute(strArr, begin + 1, end);
    swap(strArr, begin, i);
    }
    }
    }

    const permuteArray = strArr => {
    permute(strArr, 0, strArr.length - 1);
    }
  • 객체 펼치기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    const dictionary = {
    'Key1' : '1',
    'Key2' : {
    'a' : '2',
    'b' : '3',
    'c' : {
    'd' : '3',
    'e' : '1'
    }
    }
    }

    const flattenDictionary = dictionary => {
    let flattenedDictionary = {};

    function flattenDictionaryHelper (dictionary, propName) {
    if (typeof dictionary != 'object') {
    flattenedDictionalry[propName] = dictionary;
    return;
    }
    for (let prop in dictionary) {
    if (propName === '')
    flattenDictionaryHelper(dictonary[prop], propName+prop);
    else
    flattenDictionaryHelper(dictonary[prop], propName+'.'+prop)
    }
    }
    flattenDictionaryHelper(dictionary, '');
    return flattenedDictionary;
    } // 시간 복잡도 O(n)
  • 회문 검사하기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const isPalindrome = word => {
    return isPalindromeHelper(word, 0, word.length - 1);
    }

    const isPalindromeHelper = (word, beginPos, endPos) => {
    if (beginPos >= endPos) return true;
    if (word.charAt(beginPos) !== word.charAt(endPos)) return false
    else return isPalindromeHelper(word, beginPos + 1, endPos - 1);
    }

Comment and share

5장 자바스크립트 배열

  • 자바스크립트의 배열은 삽입, 삭제, 접근에 O(1) 복잡도를 갖는다
  • 삽입은 .push()나 .pop()을 사용해 배열 마지막에 추가하거나 삭제할 수 있다
  • array[1]과 같이 특정 인덱스에 접근한다면 메모리 주소로부터 직접 값을 얻어온다 (Java처럼 메모리 주소값을 알 수 있는 방법은 거의 없다, 스택오버플로우 참고)
  • n개의 항목을 방문하는 for문은 당연히도 O(n)의 시간 복잡도를 갖는다
  • while(true)와 같이 for( ; ;)로도 무한루프를 구현할 수 있다
  • for문은 for (index in arr) 이나 for (element of arr)와 같이 사용할 수 있다
  • 일반적인 for문과 달리 forEach는 배열의 각 요소에 대해 callback을 실행하므로 중간에 break할 수 없다 (MDN 참고)
  • 유용한 Array 내장함수
  1. .slice(begin, end) : 얕은 복사
    기존 배열을 수정하지 않고 배열의 일부분을 리턴하며 end값이 없다면 자동으로 맨 끝 인덱스로 가정한다
  2. .splice(begin, size, element1, element2…)
    첫 번째 매개변수에 지정한 값에 size만큼 잘라내고, 3번째 이후 element들이 주어졌다면 begin 인덱스부터 삽입된다
  3. .concat()
    신규 항목을 배열의 맨 뒤에 추가하고 리턴함
  4. .length
  5. Spread Operator : 깊은 복사
    1
    2
    3
    4
    5
    6
    7
    // 얕은 복사
    let shallowCopy1 = [1, 2, 3];
    let shallowCopy2 = shallowCopy2 // 혹은 shallowCopy1.slice();

    // 깊은 복사
    let deepCopy = [1, 2, 3];
    let deepCopy = [...deepCopy];
  • 배열 arr이 있고 weight가 주어졌을 때 합쳐서 weight가 되는 배열 내 항목 두 개의 인덱스를 찾기 (단, weight가 되는 두 값이 없을 경우 -1을 리턴)
    1
    2
    3
    4
    5
    6
    7
    8
    const findSum = (arr, weight) => {
    for (let i = 0, arrLength = arr.length; i < arrLength; i++) {
    for (let j = i+1; j < arrLength; j++) {
    if (arr[i]+arr[j] == wieght) return [i, j]
    }
    }
    return -1;
    } // n길이의 배열을 순회하는 for문이 두개 중첩되므로 시간 복잡도는 O(n의 2승)
  • 좀 더 알아보기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const findSumBetter = (arr, weight) => {
    let hashtable = {};
    for (let i = 0, arrLength = arr.length; i < arrLength; i++) {
    let currEl = arr[i], subVal = weight - currEl;
    if (hashtable[currEl] != undefined) return [i, hashtable[currEl]]
    else hashtable[subVal] = i;
    }
    return -1;
    } // 차이를 저장하고 해시테이블에 저장함으로써 O(n) 복잡도로 감소
    // 대신 공간 복잡도는 O(n) (해시테이블 만큼) 으로 늘어남
  • 두 개의 정렬된 배열이 동일한 크기일 때 해당 배열들의 중앙값 찾기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    const medianOfArray = arr => {
    let len = arr.length;
    if (len % 2 === 1) return arr[Math.floor(length/2)]
    else return (arr[len/2] + arr[len/2-1])/2
    }

    const medianOfTwoSortedArr = (arr, arr2, pos) {
    // 배열의 길이가 0보다 같거나 작다면 -1을 리턴
    if (pos <= 0)
    return -1;
    // 배열의 길이가 1이라면 각 배열의 값을 더하고 2로 나눈 값을 리턴
    if (pos === 1)
    return (arr[0] + arr2[0])/2
    // 배열의 길이가 2라면 0번째값의 최대값, 1번째값의 최소값을 구해 합하고 2로 나눈값을 리턴
    if (pos === 2) {
    return (Math.max(arr[0], arr2[0]) + Math.min(arr[1], arr2[1]))/2;
    }
    // 두 배열의 중간값을 구하고
    let median = medianOfArr(arr);
    let median2 = medianOfarr(arr2);

    // 중간값이 같다면 어느 한쪽을 리턴
    if (median === median2) {
    return median1;
    }

    // 짝수 길이의 배열이라면 offset값을 부여
    let evenOffset = pos % 2 === 0 ? 1 : 0
    // 배열 중간값을 앞뒤로 찾아보기 위해 인덱스 절반값에서 빼고 더한다
    let offsetMinus = Math.floor(pos/2) - evenOffset,
    offsetPlus = pos - Math.floor(pos/2) + evenOffset;

    // 첫번째 배열(arr)의 중간값이 두번째 배열(arr2)의 중간값보다 작다면
    // arr[arr[중간값], arr[중간값+1]], arr2[arr2[중간값-1], arr2[중간값]]를 매개변수로 재귀호출
    // 이 함수는 pos값이 2보다 작거나 같을 때 끝남 (분할 정복)
    if (median < median2) {
    return medianOfTwoSortedArray(arr.slice(offsetMinus), arr2.slice(0, -offsetMinus), offsetPlus);
    } else {
    return medianOfTwoSortedArray(arr2.slice(offsetMinus), arr1.slice(0, -offsetMinus), offsetPlus);
    }
    } // 매번 배열을 반으로 나누므로 시간 복잡도는 O(log2(n))이다
  • k개의 정렬된 배열에서 공통 항목 찾기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    const commonElements = kArray => {
    let hashmap = {}, last, answer = [];
    for (let i = 0, kArrLen = kArray.length; i < kArrLen; i++) {
    let currArr = kArr[i];
    last = null;
    for (let j = 0, currArrLen = currArr.length; j < currArrLen; j++) {
    let currEl = currArr[j];
    // 배열값을 받아 hashmap에 접근하여 값이 없다면 1로, 있다면 ++한다
    if (last != currArr) {
    if (!hashmap[currEl])
    hashmap[currEl] = 1;
    else
    hashmap[currEl]++
    }
    // 이 배열을 정렬된 배열이므로
    // 마지막으로 꺼내온 배열의 원소를 last로 저장하여
    // 다음 인덱스에 같은 값이 나온다면 패스하도록 설정
    last = currEl
    }
    }

    for (let prop in hashmap) {
    // 배열의 갯수만큼 있어야 공통값임
    if (hashmap[prop] === kArray.length)
    answer.push(parseInt(prop)) // push는 O(1)임
    }
    return answer;
    } // 시간 복잡도는 O(kn)
  • 자바스크립트 함수형 배열 메소드
  1. map 함수는 배열을 순회하며 배열의 모든 항목에 함수를 적용하고 변화된 새 배열을 리턴함
  2. filter 함수는 전달받은 조건을 충족시키는 요소만 있는 새 배열을 리턴함
  3. reduce 함수는 모든 항목을 하나의 값으로 결합함
  • 자바스크립트에는 다차원 배열이 없는 대신 가변 배열이 있다
  • 배열의 나선형 출력
    → 왼쪽에서 오른쪽으로, 위에서 아래쪽으로, 오른쪽에서 왼쪽으로, 아래쪽에서 위로 출력하기
    → 네 가지 연산에 제한을 걸기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    let M = [
    [1,2,3,4,5],
    [6,7,8,9,10],
    [11,12,13,14,15],
    [16,17,18,19,20]
    ]

    const spiralPrint = M => {
    let topRow = 0, leftCol = 0, btmRow = M.length-1, rightCol = M[0].length-1;
    while (topRow < btmRow && leftCol < rightCol) {
    for (let col = 0; col <= rightCol; col++)
    console.log(M[topRow][col]);
    topRow++;
    for (let row = topRow; row <= btmRow; row++)
    console.log(M[row][rightCol]);
    rightCol--;
    if (topRow <= btmRow) {
    for (let col = rightCol; col >= 0; col--)
    console.log(M[btmRow][col]);
    btmRow--;
    }
    if (leftCol <= rightCol) {
    for (let row = btmRow; row > topRow; row--)
    console.log(M[row][leftCol]);
    leftCol++
    }
    } // 시간 복잡도 O(mn) (배열 순회이므로 이차원 배열의 m*n 크기만큼)
  • 틱택토 게임
    → for문으로 세개의 행 모두를 확인하고 모든 열을 확인하고 대각선을 확인
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    const checkRow = (rowArr, letter) => {
    for (let i = 0; i < 3; i++){
    if (rowArr[i]!=letter) return false;
    }
    return true;
    } // 가로

    const checkCol = (boardMatrix, colIdx, letter) => {
    for (let i = 0; i < 3; i++) {
    if(boardMatrix[i][colIdx] != letter) return false;
    }
    return true;
    } // 세로

    const ticTackToe = (boardMatrix, letter) => {
    let rowWin = checkRow(boardMatrix[0], letter)
    || checkRow(boardMatrix[1], letter)
    || checkRow(boardMatrix[2], letter)

    let colWin = checkCol(boardMatrix, colIdx[0], letter)
    || checkCol(boardMatrix, colIdx[1], letter)
    || checkCol(boardMatrix, colIdx[2], letter)
    let digonalLeft = (
    boardMatrix[0][0] == letter
    && boardMatrix[1][1] == letter
    && boardMatrix[2][2] == letter
    )
    let digonalRight = (
    boardMatrix[2][0] == letter
    && boardMatrix[1][1] == letter
    && boardMatrix[0][2] == letter
    )
    return rowWin || colWin || digonalLeft || digonalRight;
    }
  • 길 찾기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    const findChar = (char, matrix) => {
    let row = matrix.length, col = matrix[0].length;
    for (let i = 0; i < row; i++) {
    for (let j = 0; j < col; j++) {
    if (matrix[i][j] === char) return [i,j]
    }
    }
    }

    const pathRinder = matrix => {
    let row = matrix.length, col = matrix[0].length;
    let start = findChar('e', matrix), end = findChar('x', matrix);
    path (start[0], start[1]);

    function path(x,y) {
    if (x - row - 1 || y > col -1 || x < 0 || y < 0) return false
    if (x === end[0] && y === end[1]) return true;
    if (matrix[x][y] == '%' || matrix[x][y] == '+') return false
    matrix[x][y] = '+';

    if (path(x, y-1) || path(x+1, y) || path(x-1, y) || path(x-1, y)) return true;
    matrix[x][y] = '.';
    return false
    }
    }
  • 행렬 회전
    → 행렬의 세 번째 열이 회전된 행렬의 첫 번째 행이 된다
    → 행렬의 두 번째 열이 회전된 행렬의 두 번째 행이 된다
    → 행렬의 첫 번째 열이 회전된 행렬의 세 번째 행이 된다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const rotateMatrix = matrix => {
    let len = matrix.length;
    for (let x = 0; x < len; x++) {
    for (let y = x; y < len - x - 1; y++){
    // 현재값을 임시로 저장
    let temp = matrix[x][y];
    // 현재 값의 오른쪽 값을 위로 옮김
    matrix[x][y] = matrix[y][len-1-x];
    // 현재 값의 아래 값을 오른쪽으로 옮김
    matrix[y][len-1-x] = matrix[len-1-x][n-1-y];
    // 현재 값의 왼쪽 값을 밑으로 옮김
    matrix[len-1-x][len-1-y] = matrix[len-1-y][x];
    // 임시변수값을 왼쪽으로 옮김
    matrix[len-1-y][x] = temp;
    }
    }
    }

Comment and share

3장 자바스크립트 숫자

  • 자바스크립트에서는 숫자에 대해 64비트 부동소수점 표현을 사용한다
    → 부호 1비트 + 지수 11비트 + 소수 52비트
  • 십진분수로 인해 반올림 오류를 일으킬 수 있다 0.1 + 0.2 === 0.3; // false
  • Number 객체에 내장된 속성들
    1. 정수 반올림
      1
      2
      3
      Math.floor // 내림
      Math.round // 반올림
      Math.ceil // 올림
    2. Number.EPSILON : 두 개의 표현 가능한 숫자 사이의 가장 작은 간격을 리턴함
    3. 가장 큰 정수 : Number.MAX_SAFE_INTEGER
      Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2 // true
    4. 가장 작은 정수 : Number.MIN_SAFE_INTEGER
      Number.MIN_SAFE_INTEGER - 1 === Number.MIN_SAFE_INTEGER - 2 // true
    5. 양의 무한(Infinity)값은 Number.MAX_SAFE_INTEGER보다 큰 유일한 값이며 음의 무한(-Infinity)값은 Number.MIN_SAFE_INTEGER보다 작은 유일한 값이다
  • 숫자 알고리즘
    1. 소수 테스트
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      function isPrime(n) {
      if (n <= 1) return false;

      for (let i = 2 i < n; i++){
      if (n%i == 0) {
      return false
      }
      }
      return true
      }
      // 시간 복잡도 O(n)
      1-1. 개선된 소수 테스트
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      // 소수는 6k±1의 형태를 지님
      // ex) 5 = 6-1, 7 = ((1*6) + 1), 13 = ((2*6) + 1) 등

      function isPrimeAdvanced(n) {
      if (n <= 1) return false
      if (n <= 3) return true

      if (n%2 == 0 || n%3 == 0) return false;

      for (let i = 5; i * i <= n; i=i+6){
      if (n%i == 0 || n % (i+2) == 0) return false;
      }
      return true;
      }
      // 시간 복잡도 O(sqrt(n))
    2. 소인수분해
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      function primeFactors(n) {
      while (n%2 == 0) {
      console.log(2);
      n = n/2
      }
      // n%2가 0이 아닐때까지 나눠 n은 이 시점에서 홀수가 됨
      for (let i = 3; i * i <= n; i = i+2){
      // i가 n을 나눌 수 있는 한 계속해서 i가 출력되고 n을 i로 나눈다
      while (n%i == 0) {
      n = n/i
      }

      if (n > 2) {
      console.log(n);
      }
      }
      }
      primeFactors(10) // 5, 2를 출력
      // 시간 복잡도 O(sqrt(n))
    3. 무작위 수 생성
      1
      Math.random() * n + x // x부터 (n+x)까지의 랜덤한 정수를 생성한다
    4. 모듈러 제곱거듭(modular exponentiation) : 단순한 계산법
      1
      2
      3
      const modularExponentiation = (x, y, p) => {
      return Math.pow(x, y) % p
      }
      4-1. 모듈러 제곱거듭(modular exponentiation) : 큰 지수도 다룰 수 있는 함수
      → 임의의 a와 b에 대해 c % m = [(a % m)(b % m)] % m이 성립
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      const modularExponentiation = (base, exponent, modulus) => {
      if (modulus == 1) return 0;

      // 1. 값 = 1로 설정한다. 지수는 0이다
      let value = 1;

      // 2. 현재 지수를 1만큼 증가시킨다
      for (let i = 0; i < exponent; i++) {
      // 3. 현재 지수가 목표의 지수가 될 때까지
      // '값 = (값 x 기저) % 모듈러'를 반복한다
      value = (value * base) % modulus;
      }
      return value;
      }
      ```
      5. n보다 작은 소수 찾기
      ```javascript
      const allPrimesLessThanN = n => {
      for (let i = 0; i < n; i++){
      if (isPrime(i)) console.log(i);
      }
      }

      const isPrime = n => {
      if (n <= 1) return false
      if (n <= 3) return true

      if (n%2 == 0 || n%3 == 0) return false;

      for (let i = 5; i * i <= n; i=i+6){
      if (n%i == 0 || n % (i+2) == 0) return false;
      }
      return true;
      }
    5. 소인수 집합 확인
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      // 모든 소인수는 2와 3과 5로 나눠질 수 있다
      const maxDivide = (number, divisor) => {
      while (number % divisor === 0) number /= divisor;
      return number
      } // 시간 복잡도는 O(divisor를 밑으로 하는 log (number))

      const isPrimeFactor = number => {
      number = maxDivide(number, 2)
      number = maxDivide(number, 3)
      number = maxDivide(number, 5)
      return number === 1;
      } // 시간 복잡도는 O(log2(n))

      const primeFactorArr = n => {
      let count = 0, currNum = 1, primeNums = [];

      while (count != n) {
      if (isPrimeFactor(currNum)) {
      count++;
      primeNums.push(currNum)
      }
      currNum++
      }
      return primeNums;
      } // 시간 복잡도는 O(n(log2(n))) O(n번 * isPrimeFacor의 BigO 값)

      4장 자바스크립트 문자열

  • 자바스크립트의 기본 자료형인 String에는 다양한 함수가 존재
  1. 문자에 접근할 때엔 .charAt()을 사용한다 'dog'.charAt(1) // "o"
    1-1. substring(startIdx, endIdx)을 사용하면 지정된 값 사이의 문자열의 리턴이 가능하다
    1-2. 이때 endIdx를 입력하지 않는다면 자동으로 문자열 마지막까지 리턴한다
  2. 문자열을 비교할 때에는 < 나 > 기호를 통해 쉽게 비교할 수 있다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    let a = 'a'
    let b = 'b'
    console.log(a < b) // true
    /*------------------------------*/
    // 길이가 다른 문자열을 비교한다면 문자열의 시작부터 비교하여
    // 더 짧은 길이의 문자열길이만큼만 비교한다
    a = 'add'
    b = 'b'
    console.log(a < b) // true
    /*------------------------------*/
    a = 'add'
    b = 'ab'
    console.log(a < b) // false
  3. 문자열 내 특정 문자열을 찾기 위해 .indexOf(searchValue[, fromIndex])를 사용할 수 있다
    3-1. 검색하고자하는 문자열을 매개변수로 받으며 선택적으로 검색 시작의 인덱스를 받을 수 있다
    3-2. 이 함수는 일치하는 문자열의 위치를 리턴하며 대소문자를 구분한다
    3-3. 없다면 -1을 리턴하므로 -1을 반환하는지 아닌지를 확인하면 된다
  4. 문자열을 분해할 때에는 .split(separator)를 사용할 수 있으며 분리자 매개변수를 받아 부분 문자열 배열을 생성한다
  5. .replace(string, replaceString)함수는 문자열 내 특정 문자열을 대체하는 함수이다
  6. 정규 표현식은 검색 패턴을 정의한 문자열의 집합으로 기본 객체 RegExp를 사용할 수 있다
    6-1. RegExp의 생성자는 정규 표현식과 일치 관련 설정의 두 가지 매개변수를 받는다
    6-2. i는 대소문자를 구분하지 않고 일치하는 문자열을 검색하며, g는 전역적으로 ((모든) 문자열을 검색하며, m은 다중열 문자열에 대해서도 일치하는 문자열을 검색한다
    6-3. RegExp는 search()와 match() 함수가 존재하며 search()는 찾은 문자열의 인덱스를, match()는 모든 일치하는 문자열을 리턴한다
    6-4. String 객체에서 RegExp를 매개변수로 받는 함수는 일치하는 문자열을 찾는 exec()와 문자열을 찾아 boolean값을 리턴하는 test()가 있다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <--- 기본 정규 표현식 --->
    ^ :문자열/줄의 시작을 나타냄
    \d : 모든 숫자를 찾는다
    [abc]/[^abc] : 괄호 내 모든 문자를 찾는다 / 제외한 문자열을 찾는다
    [0-9]/[^0-9] : 괄호 내 모든 숫자를 찾는다 / 제외한 숫자를 찾는다
    (x|y) : x나 y 문자열을 찾는다
    <--- 자주 사용되는 정규 표현식 --->
    /\d+/ : 숫자를 포함하는 문자
    /^\d+$/ : 숫자만 포함하는 문자
    /^[0-9]*.[0-9]*[1-9]+$/ : 부동 소수점 문자
    /[a-zA-Z0-9]/ : 숫자와 알파벳만 포함하는 문자
    /([^?=&])(=([^&]*))/ : 질의 문자열
  7. 자바스크립트의 base() 함수는 문자열로부터 Base64 인코딩된 ASCII 문자열을 생성하며 atob() 함수는 Base64 인코딩된 문자열을 디코딩하는 함수이다
    7-1. 단축 URL은 이 base() 함수를 응용하여 사용하는 것이다
  8. RSA 암호화는 키 생성, 암호화, 복호화의 3단계가 있으며 매우 큰 소수를 사용하여 소인수분해(4096비트 소수 곱을 사용)의 역계산을 어렵게 한다

Comment and share

1장 빅오 표기법

O(1)은 신성하다 - 하미드 티주쉬

  • 빅오 표기법 기초

    • 빅오 표기법에서 n은 입력의 개수를 나타낸다
    • “n이 무한으로 접근할 때 무슨 일이 일어날까?”
      일반적인 빅오 복잡도 그래프
  • 계수 법칙 : “상수를 제거하라”

  • *

    상수 k > 0인 경우 f(n)이 O(g(n)이면 kf(n)은 O(g(n))이다
    **

    1
    2
    3
    4
    5
    6
    function Coefficient(n) {
    let count = 0;
    for (let i = 0; i < n; i++) count += 1
    return count;
    }
    // Coefficient(n)의 복잡도는 O(n)
  • 합의 법칙 : “빅오를 더하라”

  • *

    f(n)이 O(h(n))이고 g(n)이 O(p(n))이라면 f(n)+g(n)은 O(h(n)+p(n))이다
    **

    1
    2
    3
    4
    5
    6
    7
    function SumBigO(n) {
    let count = 0;
    for (let i = 0; i < n; i++) count += 1;
    for (let i = 0; i < 5*n; i++) count += 1;
    return count;
    }
    // SumBigO(n)의 복잡도는 O(n + 5n) = O(n)이 된다
  • 곱의 법칙 : “빅오를 곱하라”

  • *

    f(n)이 O(h(n))이고 g(n)이 O(p(n))이라면 f(n)g(n)은 O(h(n)p(n))이다
    **

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function MultipleBigO(n) {
    let count = 0;
    for (let i = 0; i < n; i++) {
    count += 1;
    for (let i = 0; i < 5*n; i++)
    count += 1;
    }
    return count;
    }
    // MultipleBigO(n)의 복잡도는 O(n^2)
  • 다항 법칙 : “빅오의 k승”

  • *

    f(n)이 k차 다항식이라면 f(n)은 O(n^k)이다
    **

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function PolynomialBigO(n) {
    let count = 0;
    for (let i = 0; i < n*n; i++) {
    count += 1;

    }
    return count;
    }
    // PolynomialBigO(n)의 복잡도는 O(n^2)

    2장 자바스크립트의 독특한 특징

  • 자바스크립트는 동적 인터프리터 프로그래밍 언어이기 때문에 다른 객체지향 프로그래밍 언어들과는 구문이 다름

  • 자바스크립트의 범위(scope)

    • 전역 선언: 전역 범위
      → 전역 선언은 안좋은 방법이므로 되도록 사용하지 말 것
    • var를 사용해 선언 : 함수 범위
      → var를 사용하면 변수를 어디에 선언하더라도 함수의 맨 앞으로 이동한다
      → 이를 변수 호이스팅(variable hoisting)이라고도 한다
      1
      2
      3
      4
      5
      6
      7
      8
      9
      function scope1() {
      var top = "top"
      bottom = "bottom";
      console.log(bottom)

      var bottom;
      }
      // bottom은 맨 마지막에 선언되었지만
      // console.log() 앞으로 hoisting되며 오류 없이 실행된다
    • var로 선언된 해당 변수의 범위가 가장 가까운 함수 범위가 된다
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      function scope2(print) {
      if (print) {
      var insideIf = '12';
      }
      console.log(insideIf);
      }
      scope2(true);

      function scope2_copy(print) {
      var insideIf;
      if (print) {
      insideIf = '12';
      }
      console.log(insideIf);
      }
      scope2_copy(true);
      // scope2와 scope2_copy는 동일하게 실행된다
    • let를 사용해 선언 : 블록 범위
      → 변수가 선언된 블록 범위({})내에서 유효하다
      1
      2
      3
      4
      5
      6
      7
      8
      9
      function scope3(print) {
      if(print) {
      let insideIf = '12'
      }
      console.log(insideIf);
      }
      scope3(true);
      // 오류가 발생함 let은 블록 범위에서만 유효하기 때문에
      // if문을 벗어나면 해당 변수를 읽을 수 없다
  • 등가와 형

    • 자바스크립트에는 boolean, number, string, undefined, object, function, symbol과 같은 일곱가지의 기본 자료형이 존재함
    • 값이 선언만 되고 값이 할당되지 않으면 undefined가 됨
    • 타 언어에서 대개 if문 내 매개변수는 boolean형이어야 하지만 자바스크립트에서는 해당 변수가 비었거나 null이거나 undefined라면 false로 평가된다
    • false로 인식되는 값은 false 외에 0, ‘’(혹은 “”), NaN, undefined, null가 있고 0이 아닌 숫자, 비어있지 않은 문자열이나 객체를 true로 평가한다
    • 자바스크립트는 스크립트 언어이기 때문에 코드가 실행될 때 해당 변수의 형이 해석된다
    • ==는 값만 확인하지만 ===는 형과 값을 모두 확인 한다
      "5" == 5 // true vs "5" === 5 // false
    • 자바스크립트에서 동일한 속성과 값을 갖는 두 객체가 동일한지 확인하고자 ==를 사용한다면 변수의 메모리 주소가 다르기 때문에 false를 리턴할 것이다
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      function isEquivalent(a,b) {
      var aProps = Object.getOwnPropertyNames(a);
      var bProps = Object.getOwnPropertyNames(b);

      // 길이가 다르다면 두 객체는 다른 객체임
      if (aProps.length != bProps.length) return false
      for (var i = 0; i < aProps.length; i++) {
      var propName = aProps[i];

      // 속성값이 다른 경우 두 객체는 같지 않다
      if(a[propName] !== b[propName]) return false
      }

      return true;
      }
      isEquivalent({'hi':12},{'hi':12}); // true

Comment and share

소 저녁 식사 주기

  • N(1~15)마리의 소들을 순서대로 세워놓고 각 소들 사이에 +, -, .의 셋 중 한가지가 쓰여진 냅킨을 배치해서 최종 결과가 0이 되도록 하는 문제
  • .은 숫자를 연결하는 부호가 됨 (예: 10.11 => 1011)
  • 소의 수 N이 입력되었을 때 처음 20줄에 대해 가능한 20가지 답을 출력하는데 사전 순으로 앞선 것을 출력한다
  • +가 가장 앞서고 -, . 순서이다

N = 7
1 + 2 - 3 + 4 - 5 - 6 + 7
1 + 2 + 3 + 4 - 5 + 6 - 7
1 - 2 + 3 + 4 - 5 + 6 - 7
1 - 2 - 3 - 4 - 5 + 6 + 7
1 - 2 . 3 + 4 + 5 + 6 + 7
1 - 2 . 3 - 4 . 5 + 6 . 7
6 가지

  • 수와 수 사이에 사용할 수 있는 연산자는 모두 3종류
  • 수와 수 사이의 개수는 N-1개이므로 가능한 경우의 수는 3의 N-1승 가지 이다
  • N의 최대값이 15이므로 3의 14승 = 4,782,969 가지가 최대치가 됨
  • 두 수 사이에 +나 -를 사용하는 경우에는 dfs(step, a)로 해결
  • 하지만 .을 고려한다면 dfs(step, a, b)로 구현해야 함
  • 여기서 a는 직전에 등장한 + 또는 - 이전까지의 결과이고 b는 직전에 등장한 + 또는 - 이후부터 0개 이상의 .을 계산하여 하나의 수로 만든 결과
  • 새로운 +나 -가 나오면 a에 b에 더하는 연산만 한다 (-일 때엔 b를 음수로 변환)
  • 마지막 단계에서 a + b === 0 인지를 확인
  • N의 최대값이 15이므로 만들 수 있는 가장 큰 수는 123456789101112131415이지만 결과값이 0이어야 하므로 12345678910 이상의 수는 고려하지 않아도 된다 (나머지인 1112131415가 12345678910보다 작기 때문)

Comment and share

단어 세기

  • 해당 강의에서는 C++의 string.h 라이브러리 내 strtok() 함수를 사용하여 해결하였으나 자바스크립트에서는 널 포인트를 사용하지 않기 때문에 따로 구현하고 넘어감
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const countWords = p => {
let divide = p.split('\n').map(el => el.split(' '))
for(let i = 0; i < divide.length; i++) {
let result = [];
for (let j = 0; j < divide[i].length; j++){
let fCondition = divide[i][j];
// 객체 내의 원소를 받고 해당 원소로 배열을 필터
// 필터된 배열의 길이가 해당 아이템의 숫자가 됨
let cnt = divide[i].filter(el => el === fCondition).length;
let element = `${fCondition} : ${cnt}`
if(!result.includes(element))
result.push(element)
}
result = result.sort((a,b) => a.charCodeAt() - b.charCodeAt())
console.log(result);
}
}
  • 출력 예제와 다른 점 : sort()함수를 사용하여 ASCII 값을 비교하여 정렬하였으나 출력 예제의 ‘A : 1’, ‘AM : 2’, ‘DOG : 4’, ‘I : 2’와는 달리 ‘AM : 2’, ‘A : 1’, ‘DOG : 4’, ‘I : 2’로 출력되었다
  • 어떻게 해결하면 될까?를 고민해봐야 함

Comment and share

진법을 변환하기

  • A진법 수 N을 입력 받아 B진법 수로 변환하는 문제
  • Horner’s Method

    → 해시 문제나 긴자리수 진법 변환 문제에 사용됨
  • 2진수를 10진수로 변환하기
  • 예1) 2진법 11010을 8진법으로 바꾸어보자
    → char str[] = “11010”
    (의미는 없지만 JS에서는 let str = Number(10110).toString()로 표현가능함)

d = d * 2 + ‘1’ - ‘0’; // d(3) = 1 * 2 + 1
d = 02 + 1 = 1
d = 1
2 + 1 = 3
d = 32 + 0 = 13
d = 6
2 + 1 = 13
d = 13*2 + 1 = 26

  • 10진법을 먼저 구하고 10진법 26을 8진법으로 바꾸기

    38 + 2 ``26/88 + 26%8(0*8 +**3**) * 8 + **2**(3/88 + 3%8)*8 + 26%8``
    *
    32** (Octet)

  • 예2) 10진법 2543을 16진법으로 바꾸기

    158 * 16 + ‘F’ 2543/16 * 16 + 2543%16 // 158*16 + 15
    (916 + ‘E’) * 16 + ‘F’ ``(158/16)*16 + 158%16 + ‘F’ // 916 + 14 + ‘F’``
    ((016 + 9) * 16 + *‘E’*) * 16 + *‘F’** = 9EF (Hexadecimal)

자바스크립트의 진법 변환은 매우 간단하기 때문에 별도의 코드는 첨부하지 않음

1
2
3
4
let value = 10;
value.toString(2) // 2진법으로 변경
let binary = 1010;
Number.parseInt(binary, 2) // 10진법으로 변경

앞뒤 같은 제곱

  • N(1≤N≤300)중에서 N의 제곱값을 B(2≤B≤20)진수로 바꿨을 때 앞뒤가 같은 수가 되는 N값과 N의 제곱값을 B진수로 바꾸는 문제 (단, 10보다 큰 숫자는 A~J로 표현)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const changeNumBase = powNum => {
let arr = [];
for (let i = 1; i <= 300; i++) {
arr.push(Math.pow(i, powNum))
}
arr = arr.filter(num => {
return num.toString() === num.toString().split('').reverse().join('');
})

return arr;
}

const solution = () => {
let arr = [];
for (let i = 2; i <= 20; i++) {
arr.push(changeNumBase(i));
}
return arr.filter(el => el.length > 1)
}

/* 출력 예시
[
[ 1, 4, 9,
121, 484, 676,
10201, 12321, 14641,
40804, 44944, 69696
],
[ 1, 8, 343, 1331, 1030301, 1367631 ],
[ 1, 14641, 104060401 ]
]
*/

Comment and share

Harry Kim

author.bio


author.job