FAQ

Sangkon Han

단순 정렬 FAQ

null은 예상과 다르게 작동합니다.

nullundefined 타입은 일반적으로 값이 비어있거나 없음을 나타냅니다. 안타깝게도, null 값 타입의 typeof 결과는 예상과 다릅니다. "null" 대신 다음과 같은 결과가 나옵니다.

typeof null;            // "object"

이것이 null이 특별한 종류의 객체라는 의미는 아닙니다. 이것은 자바스크립트 초창기의 유산일 뿐이며, 세상에 존재하는 수많은 코드를 망가뜨릴 수 있기 때문에 변경될 수 없는 부분입니다.

undefined은 과도하게 넓은 범위에 적용됩니다.

undefined 타입은 명시적인 undefined 값뿐만 아니라, 값이 없는 것처럼 보이는 모든 곳에서 보고됩니다.

typeof undefined;               // "undefined"
var whatever;
typeof whatever;                // "undefined"
typeof nonExistent;             // "undefined"
whatever = {};
typeof whatever.missingProp;    // "undefined"
whatever = [];
typeof whatever[10];            // "undefined"

Note: typeof nonExistent 표현식은 선언되지 않은 변수 nonExistent를 참조합니다. 일반적으로 선언되지 않은 변수를 참조하면 예외가 발생하지만, typeof 연산자는 존재하지 않는 식별자에도 안전하게 접근하여 예외를 발생시키는 대신 침착하게 "undefined"를 반환하는 특별한 능력이 있습니다.

Empty Values

그러나 각각의 “빈” 타입은 정확히 하나의 값, 즉 타입과 같은 이름의 값을 가집니다. 따라서 nullnull 값 타입의 유일한 값이며, undefinedundefined 값 타입의 유일한 값입니다. 의미상으로, nullundefined 타입은 모두 일반적인 비어있음, 또는 다른 긍정적이고 의미 있는 값의 부재를 나타냅니다.

Note: null이나 undefined를 만났을 때 동일하게 동작하는 JS 연산을 “null’ish” (또는 “nullish”)라고 합니다. 아마도 “undefined’ish”는 보기에도 발음하기에도 너무 이상했을 것입니다.

많은 JS 코드, 특히 개발자들이 작성하는 코드에서 이 두 개의 nullish 값은 서로 교환하여 사용할 수 있습니다. 특정 시나리오에서 의도적으로 null 또는 undefined를 사용할지 할당할지는 상황에 따라 다르며 개발자에게 달려있습니다.

nullish를 위한 여러 연산자

따라서 이런 상황에서 코드를 안정적으로 수정하기 위해서 추가된 기능이 ?? (nullish 병합) 연산자입니다.

who = myName ?? "User";

// 다음과 동일합니다.
who = (myName != null) ? myName : "User";

삼항 연산자로 표현된 등가식에서 볼 수 있듯이, ??myName이 nullish가 아닌지 확인하고, 그렇다면 그 값을 반환합니다. 그렇지 않다면 다른 피연산자(여기서는 "User")를 반환합니다.

??와 함께, JS는 ?. (nullish 조건부 체이닝, 옵셔널 체이닝) 연산자도 추가했습니다.

record = {
    shippingAddress: {
        street: "123 JS Lane",
        city: "Browserville",
        state: "XY"
    }
};

console.log( record?.shippingAddress?.street ); // 123 JS Lane
console.log( record?.billingAddress?.street ); // undefined

?. 연산자는 바로 앞(왼쪽)의 값을 확인하고, 만약 nullish라면 연산자는 멈추고 undefined 값을 반환합니다. 그렇지 않다면, 해당 값에 대해 . 속성 접근을 수행하고 표현식을 계속 진행합니다.

명확히 하자면, record?.는 “. 속성 접근 전에 record가 nullish인지 확인하라”는 의미입니다. 마찬가지로, billingAddress?.는 “. 속성 접근 전에 billingAddress가 nullish인지 확인하라”는 의미입니다.

Note: 일부 JS 개발자들은 새로운 ?..보다 우월하므로 거의 항상 . 대신 사용해야 한다고 믿습니다. 저는 그것에 동의하지 않습니다. 첫째, 이는 추가적인 시각적 혼란을 더하며, 이로 인해 이점을 얻는 경우에만 사용해야 합니다. 둘째, ?.를 사용하는 것을 정당화하려면 어떤 값의 비어있음을 인지하고 계획해야 합니다. 어떤 표현식에 항상 nullish가 아닌 값이 존재할 것으로 예상한다면, 해당 값의 속성에 접근하기 위해 ?.를 사용하는 것은 불필요하고 낭비일 뿐만 아니라, 값의 존재에 대한 당신의 가정이 틀렸을 때 ?.가 이를 덮어버려 잠재적인 미래의 버그를 숨길 수 있습니다. JS의 대부분 기능과 마찬가지로, .는 가장 적절한 곳에 사용하고, ?.는 가장 적절한 곳에 사용하십시오. 하나가 더 적절할 때 다른 것으로 대체해서는 안 됩니다.

. 접근 대신 [ .. ] 스타일 접근이 필요할 때 사용하는, ?[가 아닌 다소 이상한 형태의 ?.[ 연산자도 있습니다.

record?.["shipping" + "Address"]?.state; // XY

Note: 연산자의 형태에 주의하세요. 이런 형태의 연산자를 시각적으로 구별하기 위해서는 좋은 폰트를 사용해야 합니다. 개발에 사용하고 있는 폰트를 점검하세요.

“옵셔널 호출(optional-call)”이라고 하는 또 다른 변형은 ?.(이며, 값이 nullish가 아닐 경우 조건부로 함수를 호출할 때 사용됩니다.

// if (someFunc) someFunc(42);
// someFunc && someFunc(42);
someFunc?.(42);

?.( 연산자는 someFunc(..)가 호출될 수 있는 유효한 함수인지 확인하는 것처럼 보입니다. 하지만 그렇지 않습니다. 이 연산자는 단지 값을 호출하기 전에 nullish가 아닌지 확인만 할 뿐입니다. 만약 값이 nullish가 아니면서 함수도 아닌 다른 타입이라면, 실행 시도는 여전히 TypeError 예외와 함께 실패할 것입니다.

Note: 바로 그 함정 때문에, 이 연산자 형태에 주의를 기울여야 합니다. 누구에게든 신중하게 사용하라고 경고합니다. 이것은 득보다 실(JS 자체와 프로그램에)이 많은, 스펙상 약간의 논쟁이 있는 기능이라고 생각합니다. 이렇게 까지 주의해야 할 기능이 몇개가 있는지 모르겠지만, 이것은 나쁜 부분(bad parts) 중 하나입니다.

같지만 다른게 아니라 전혀 다릅니다.

nullundefined는 실제로 별개의 타입이며, 따라서 nullundefined와 눈에 띄게 다를 수 있다는 점을 명심하는 것이 중요합니다. 신중하게, 이 둘을 거의 구별할 수 없는 것으로 취급하는 프로그램을 구성할 수는 있습니다. 하지만 이는 개발자의 주의와 규율을 필요로 합니다. JS의 관점에서 보면, 이 둘은 대개 구별됩니다.

nullundefined가 언어에 의해 다른 동작을 유발하는 경우가 있으며, 이를 염두에 두는 것이 중요합니다. 여기서 모든 경우를 전부 다루지는 않겠지만, 한 가지 예는 다음과 같습니다.

function greet(msg = "Hello") {
    console.log(msg);
}

greet();            // Hello
greet(undefined);   // Hello
greet("Hi");        // Hi
greet(null);        // null

매개변수에 있는 = .. 절은 “매개변수 기본값(parameter default)”이라고 합니다. 이 기능은 해당 위치의 인자가 없거나, 정확히 undefined 값일 경우에만 작동하여 기본값을 매개변수에 할당합니다. 만약 null을 전달하면, 이 절은 작동하지 않으며 null이 매개변수에 할당됩니다.

결론: 주의를 기울이고, 신중하게 설계하세요.

프로그램에서 null이나 undefined를 사용하는 데 있어 옳고 그른 방법은 없습니다. 따라서 이 값들 중 하나를 선택할 때 신중해야 합니다. 그리고 만약 이들을 서로 교환하여 사용하고 있다면, 특히 더 주의해야 합니다.

undefined 막는 방법

! 대신 undefined를 막는 4가지 안전한 방법

방법 1: 옵셔널 체이닝과 nullish coalescing 사용

export function simpleSort(arr: number[], order = "asc"): number[] {
  const sortedArr = [...arr];

  for (let i = 0; i < sortedArr.length - 1; i++) {
    for (let j = 0; j < sortedArr.length - 1 - i; j++) {
      const a = sortedArr[j] ?? 0; // undefined면 0으로 대체
      const b = sortedArr[j + 1] ?? 0;
      const swap = order === "asc" ? a > b : a < b;

      if (swap) {
        [sortedArr[j], sortedArr[j + 1]] = [b, a];
      }
    }
  }
  return sortedArr;
}

방법 2: 조건부 체크 사용

export function simpleSort(arr: number[], order = "asc"): number[] {
  const sortedArr = [...arr];

  for (let i = 0; i < sortedArr.length - 1; i++) {
    for (let j = 0; j < sortedArr.length - 1 - i; j++) {
      const a = sortedArr[j];
      const b = sortedArr[j + 1];
      
      // undefined 체크
      if (a === undefined || b === undefined) {
        continue; // 또는 throw new Error("배열에 undefined 값이 있습니다")
      }
      
      const swap = order === "asc" ? a > b : a < b;

      if (swap) {
        [sortedArr[j], sortedArr[j + 1]] = [b, a];
      }
    }
  }
  return sortedArr;
}

방법 3: 타입 가드 함수 사용

function isDefined<T>(value: T | undefined): value is T {
  return value !== undefined;
}

export function simpleSort(arr: number[], order = "asc"): number[] {
  const sortedArr = [...arr];

  for (let i = 0; i < sortedArr.length - 1; i++) {
    for (let j = 0; j < sortedArr.length - 1 - i; j++) {
      const a = sortedArr[j];
      const b = sortedArr[j + 1];
      
      if (!isDefined(a) || !isDefined(b)) {
        continue;
      }
      
      const swap = order === "asc" ? a > b : a < b;

      if (swap) {
        [sortedArr[j], sortedArr[j + 1]] = [b, a];
      }
    }
  }
  return sortedArr;
}

방법 4: 배열 필터링 사용

export function simpleSort(arr: number[], order = "asc"): number[] {
  const filteredArr = arr.filter((item): item is number => item !== undefined);
  const sortedArr = [...filteredArr];

  for (let i = 0; i < sortedArr.length - 1; i++) {
    for (let j = 0; j < sortedArr.length - 1 - i; j++) {
      const a = sortedArr[j]; // 이제 확실히 number 타입
      const b = sortedArr[j + 1];
      const swap = order === "asc" ? a > b : a < b;

      if (swap) {
        [sortedArr[j], sortedArr[j + 1]] = [b, a];
      }
    }
  }
  return sortedArr;
}

Argument of type ‘string’ is not assignable to parameter of type ‘Order’

  • order 매개변수의 기본값이 “asc”이지만, TypeScript는 이를 string 타입으로 추론
  • getCompareFunction은 Order 타입(“asc” | “desc”)을 요구
  • string은 “asc” | “desc”보다 넓은 타입이므로 할당할 수 없음
export function simpleSort(arr: number[], order = "asc"): number[] {
    type Order = "asc" | "desc";
    function getCompareFunction(order: Order) {
      // 생략
    } 

총평

  1. 타입스크립트는 타입 체크를 우선시합니다.
  2. null과 undefined는 타입 체크에서 주의를 기울여야 합니다.

LinkedList FAQ

널 참조 에러 (XXXXX5822님)

  • 원인 분석
    • p.prevp.nextnull일 수 있는데, null.nextnull.prev에 접근하려고 해서 에러 발생
    • 이중 연결 리스트에서 중간 노드를 삭제할 때, 이전 노드와 다음 노드의 연결을 재구성하는 과정에서 null 체크가 누락됨
// 64-65번째 줄에서 발생하는 문제
p.prev.next = p.next;
p.next.prev = p.prev;

해결방안

delete(value: T): void {
    let p = this.head;

    while (p !== null) {
        if (p.value === value) {
            if (this.length === 1) {
                this.head = null;
                this.tail = null;
            }
            else if (p === this.head) {
                this.head = p.next;
                if (this.head !== null) {
                    this.head.prev = null;
                }
            }
            else if (p === this.tail) {
                this.tail = p.prev;
                if (this.tail !== null) {
                    this.tail.next = null;
                }
            }
            else {
                // null 체크 추가
                if (p.prev !== null) {
                    p.prev.next = p.next;
                }
                if (p.next !== null) {
                    p.next.prev = p.prev;
                }
            }
            this.length--;
            return;
        }
        p = p.next;
    }
}

타입 추론 에러 (XXXXX5374님)

  • 원인 분석
    • TypeScript의 엄격한 null 체크로 인해 current.nextNode<T> | null 타입이므로 할당 시 타입 에러 발생
    • currentNode<T>로 타입이 좁혀졌지만, current.next는 여전히 Node<T> | null 타입
// 205번째 줄에서 발생하는 문제
current = current.next; // TypeScript가 current.next가 null일 수 있다고 경고

해결방안

delete(data: T): boolean {
    if (this.head === null) {
        return false;
    }
    
    let current: Node<T> | null = this.head; // 타입을 명시적으로 지정
    
    while (current !== null) {
        if (current.data === data) {
            // 삭제 로직...
            this.length--;
            return true;
        }
        current = current.next; // 이제 에러 없이 작동
    }
    return false;
}

테스트 미통과 (XXXXX5387님, XXXXX5462님)

  • 변수명 오류: length++ 대신 this.length++ 사용해야 함
  • 비교 연산자 오류: i.value = value 대신 i.value === value 사용해야 함

해결방안

append(value: T): void {
    const newNode = new Node(value);

    if (!this.head) {
        this.head = newNode;
        this.tail = newNode;
    } else {
        if (this.tail) {
            this.tail.next = newNode;
            newNode.prev = this.tail;
            this.tail = newNode;
        }
    }
    this.length++; // this. 추가
}

delete(value: T): void {
    if (!this.head) {
        return;
    } 

    let i: Node<T> | null = this.head;
    while (i) {
        if (i.value === value) { // === 사용
            // 올바른 삭제 로직
            if (i === this.head && i === this.tail) {
                this.head = null;
                this.tail = null;
            } else if (i === this.head) {
                this.head = i.next;
                if (this.head) this.head.prev = null;
            } else if (i === this.tail) {
                this.tail = i.prev;
                if (this.tail) this.tail.next = null;
            } else {
                if (i.prev) i.prev.next = i.next;
                if (i.next) i.next.prev = i.prev;
            }
            this.length--;
            return;
        }
        i = i.next;
    }
}

search(value: T): T | null {
    if (!this.head) return null;

    let i: Node<T> | null = this.head;
    while (i) {
        if (i.value === value) return i.value; // === 사용
        i = i.next;
    }
    return null;
}

printList(): T[] {
    if (!this.head) return []; // this.tail 대신 this.head 체크

    let result: T[] = [];
    let i: Node<T> | null = this.head;
    while (i) {
        result.push(i.value);
        i = i.next;
    }
    return result;
}

append 에러 (XXXXX5432님)

  • 할당 연산자 오류: if(this.head = null) 대신 if(this.head === null) 사용해야 함

해결방안

append(value: T): void {
    const node = new ListNode(value);
    
    if (this.head === null) { // === 사용
        this.head = this.tail = node;
    } else {
        // 올바른 연결 순서
        if (this.tail) {
            this.tail.next = node; // 먼저 기존 tail의 next 설정
            node.prev = this.tail; // 새 노드의 prev 설정
        }
        this.tail = node; // 마지막에 tail 업데이트
    }
    this.nodesize++;
}

this, this?, this!

class LinkedList<T> {
    private length: number = 0;  // 클래스의 인스턴스 변수
    append(value: T): void {
        // 만약 이렇게 작성하면...
        let length = 0;  // 지역 변수 length 생성
        length++;        // 지역 변수를 증가시킴 (인스턴스 변수는 변경되지 않음)
        // this.length++;  // 이렇게 해야 인스턴스 변수가 증가됨
    }
}

실제 예시로 비교

class LinkedList<T> {
    private length: number = 0;
    
    // 잘못된 방법
    append(value: T): void {
        const newNode = new Node(value);
        // ... 노드 연결 로직 ...
        length++;  // 지역 변수 length를 찾으려고 하지만 정의되지 않음 → 에러!
    }
    
    // 올바른 방법  
    append(value: T): void {
        const newNode = new Node(value);
        // ... 노드 연결 로직 ...
        this.length++;  // 인스턴스 변수 length를 증가시킴
    }
}

JavaScript/TypeScript의 변수 탐색 순서

class LinkedList<T> {
    private length: number = 0;    
    append(value: T): void {
        // 1. 지역 스코프에서 length 찾기 → 없음
        // 2. 상위 스코프에서 length 찾기 → 없음  
        // 3. 전역 스코프에서 length 찾기 → 없음
        // → ReferenceError: length is not defined        
        length++;  // 에러 발생!        
        this.length++;  // this를 통해 인스턴스 변수에 접근
    }
}

메모리 관점에서의 차이

const list1 = new LinkedList<number>();
const list2 = new LinkedList<number>();

// list1과 list2는 각각 독립적인 length 값을 가짐
list1.append(1);  // list1.length = 1
list2.append(2);  // list2.length = 1

// 만약 this 없이 length++를 사용했다면?
// → 각 인스턴스의 length가 제대로 관리되지 않음

실제 동작 비교

class LinkedList<T> {
    private length: number = 0;
    
    // 잘못된 구현
    append(value: T): void {
        const newNode = new Node(value);
        if (!this.head) {
            this.head = this.tail = newNode;
        } else {
            this.tail!.next = newNode;
            newNode.prev = this.tail;
            this.tail = newNode;
        }
        length++;  // ReferenceError 발생!
    }
    
    // 올바른 구현
    append(value: T): void {
        const newNode = new Node(value);
        if (!this.head) {
            this.head = this.tail = newNode;
        } else {
            this.tail!.next = newNode;
            newNode.prev = this.tail;
            this.tail = newNode;
        }
        this.length++;  // 인스턴스 변수 증가
    }
}

this 키워드 범위

  • length++: 지역 변수 length를 찾으려고 하지만 정의되지 않아서 에러 발생
  • this.length++: 현재 인스턴스의 length 속성에 접근하여 값을 증가시킴

this는 “현재 객체의 속성에 접근하라”는 의미로, 클래스 기반 프로그래밍에서 인스턴스 변수와 지역 변수를 구분하는 핵심 키워드입니다.

총평

  1. Null 안전성 확보
  • 모든 포인터 접근 전에 null 체크 필수
  • TypeScript의 엄격한 null 체크 활용
  1. 연산자 사용 주의
  • 할당(=)과 비교(===) 연산자 구분
  • 논리 연산자(&&, ||) 적절한 사용
  1. 변수 스코프 관리
  • this. 키워드로 인스턴스 변수 접근
  • 지역 변수와 인스턴스 변수 구분

BinaryTree

이진트리의 인덱스(Array, List, Vector) 기반 구현에 대해서

  • 이진트리는 List, Node, Array 등으로 구현 가능
  • 제출된 테스트 케이스는 Node, Array(List) 를 사용하여 구현 가능한지 확인 후 제출
// import { BinaryTree } from '../binaryTree';
import { BinaryTree } from '../binaryTreeArray';

// 삭제 연산 시 오른쪽 서브트리의 최소값을 옮기는 것으로 가정함

describe('이진트리 테스트', () => {
    //... 생략 ...
}

구조체에 따른 구현의 차이점이 있나요?

Node vs Index

  • 메모리 효율성: Node 기반이 일반적으로 더 효율적
  • 삭제 연산 복잡도: Node 기반이 더 단순하고 빠름, Array 기반은 삭제 시 서브트리 재배치가 필요
  • 순회 복잡도: Node 기반은 재귀적으로 순회, Array 기반은 반복문으로 순회

Generic Type Error

  • Generic Type T<, >를 직접 사용할 수 없습니다.
    • if (value < current.value) \(\to\) 타입 에러
  • 제네릭 타입 비교 연산자 사용 불가하기 때문에 CompareFn 타입과 비교 함수를 추가하여 비교 연산을 수행하도록 변경
// Error
if (value < current.value) { ... }
if (value > node.value) { ... }
// CompareFn 타입 정의
export type CompareFn<T> = (a: T, b: T) => number;
const comparison = this.compareFn(value, node.value);
if (comparison < 0) { 
    //...
}

node.right가 null이 아니어도, TypeScript는 current가 null일 수 있다고 판단

  • node.right!로 타입 단언을 사용하거나 null이 아님을 명시해야 함
let current = node.right;
while (current.left !== null) {
    current = current.left;
}
let successor: TreeNode<T> = current; // current가 null일 수 있음

shift()는 undefined를 반환할 수 있음

let node = queue.shift(); // shift()는 undefined를 반환할 수 있음
if (node === undefined) { // undefined일 수 있음
    break;
}

TypeScript의 인덱스 접근 시 타입 추론

TypeScript는 배열 인덱스 접근(array[index])을 안전하지 않은 연산으로 간주합니다.

private tree: (T | null)[] = [];
// TypeScript의 타입 추론
this.tree[minIdx]  // 타입: T | null | undefined
this.tree[index]   // 타입: T | null | undefined (할당 시)

(TS는 도대체 왜) 인덱스 접근에 undefined을 포함시키는가?

  1. 배열 범위를 보장할 수 없음(런타임 에러 발생 가능)

  2. TypeScript의 보수적 타입 추론

// TypeScript의 관점
let arr = [1, 2, 3];
arr[100]  // undefined를 반환할 수 있음!
arr[-1]   // undefined를 반환할 수 있음!
  1. 타입 불일치
this.tree[index] = this.tree[minIdx];
//     ↑                    ↑
// T | null 타입        T | null | undefined 타입
// 
// undefined를 T | null에 할당할 수 없음!

코드 분석

if (right < this.size) {
    let minIdx = right;
    while (2 * minIdx + 1 < this.size) {
        minIdx = 2 * minIdx + 1;
    }
    // minIdx는 this.size보다 작다는 것을 보장할 수 있지만,
    // TypeScript는 this.tree[minIdx]가 undefined일 수 있다고 판단
    this.tree[index] = this.tree[minIdx]; // Error
    //                        ↑
    //              T | null | undefined
}

해결 방법(a): 타입 가드 사용 (가장 안전)

  • 장점: 런타임 안전성 보장
  • 단점: 코드가 약간 길어짐
let minVal = this.tree[minIdx];
if (minVal !== undefined) {
    this.tree[index] = minVal;
}

해결 방법(b): Non-null assertion 사용 (간결)

  • 장점: 코드가 간결함
  • 단점: minIdx가 유효하다는 것을 개발자가 보장해야 함(런타임 에러 발생 가능)
this.tree[index] = this.tree[minIdx]!;

이전 과제들에서 발생한 문제점

배열 인덱스 접근은 항상 undefined를 포함할 수 있는 타입으로 추론됩니다.

// 모든 배열 인덱스 접근은 동일한 문제
arr[i]           // T | undefined
this.tree[index] // T | null | undefined
obj[key]         // value | undefined

TypeScript의 설계 철학

TypeScript는 안전성을 우선시합니다.

  • 가능한 모든 경우를 고려
  • 배열 인덱스는 범위를 벗어날 수 있음
  • 따라서 undefined를 포함한 타입으로 추론

실제 예시

let arr: number[] = [1, 2, 3];
let value = arr[10];  // TypeScript: number | undefined, 실제 런타임: undefined 반환
// TypeScript는 항상 undefined 가능성을 고려
arr[0] = arr[10];  // Error: undefined를 number에 할당 불가

결론

배열 인덱스 접근은 TypeScript가 범위를 보장할 수 없어 undefined를 포함한 타입으로 추론됩니다.

  • 타입 가드 사용
  • Non-null assertion(!)을 사용
  • Nullish coalescing(??)을 사용

this.tree[j] < min 경고 이유

// 20XX4XX22님
for (let i = idx * 2 + 1; i <= this.size(); i *= 2) {
    for (let j = i; j <= Math.min(Math.pow(2, lv) - 1, this.size()); j++) {
        if (this.tree[j] !== null && this.tree[j] !== undefined) {
            if (this.tree[j] < min)    // [질문] 경고 발생 지점
                min = this.tree[j];
        }
    }
    lv++;
}

가장 손쉬운 해결 방법

for (let i = idx * 2 + 1; i <= this.size(); i *= 2) {
    for (let j = i; j <= Math.min(Math.pow(2, lv) - 1, this.size()); j++) {
        const value = this.tree[j]; 
        if (value !== null && value !== undefined) {
            if (value < min)
                min = value;
        }
    }
    lv++;
}

이 또한 (HashTable 과제 코드 리뷰 중)

// 20XX5XX52님
for (let i = 0; i < this.table[index].length; i++) {
    if (this.table[index][i][0] === key) { // 에러
    this.table[index][i][1] = value; // 에러
    return;
    }
}

for (let i = 0; i < this.table[index].length; i++) {
    const current = this.table[index][i];
    if (current && current[0] === key) {
        current[1] = value;
        return;
    }
}

인덱스 서명

인덱스 서명(Index Signature)은 TypeScript에서 객체 또는 배열의 속성을 동적으로 다룰 때, 그 속성의 키와 값 타입을 명시할 수 있는 타입스크립트 문법입니다. 객체나 배열의 키가 사전에 정확히 정해져 있지 않고, 동적으로 생성될 수 있을 때 사용합니다.

  • 여러 사용자에게 각각 점수를 저장할 때 userId가 키가 되고 점수가 값이 되는 구조에 쓸 수 있는데, 인덱스 서명 사용시, 해당 타입의 객체는 지정한 키 타입에 대해 지정한 값 타입을 가짐을 보장
  • 존재하지 않는 키에 접근하면 런타임상 undefined가 나오지만, 타입 에러는 발생하지 않음(타입스크립트는 타입만 체크)

인덱스 서명(b)

일반 객체 타입(예: {name: string; age: number;})은 사전에 정의된 속성만 가질 수 있습니다. 인덱스 서명이 있으면, 정의되지 않은 임의의 키에도 값을 할당하고 타입 검사를 받을 수 있습니다.

  • 객체는 값이 확실하게 지정된 속성만 사용할 수 있도록 타입이 제한
  • 배열은 index 시그니처가 암묵적으로 허용, 만약 배열에 숫자가 아닌 문자열로 접근하면, 타입 시스템상 허용되지 않거나, 의도하지 않은 동작이 발생할 수 있음 (런타임 에러 발생 가능)

업데이트 이력

버전 변경 내용
v.20250922 정렬 과제 코드 리뷰
v.20250929 연결리스트 과제 코드 리뷰
v.20251117 이진트리 및 해시테이블 과제 코드 리뷰