TS: 타입스크립트
2025-12-08
TypeScript의 필요성은 코드 안정성 확보, 유지보수 및 협업 효율 증대, 생산성 및 개발 품질 향상이라는 세 가지 핵심 이유로 설명할 수 있으며, TypeScript 도입이 급증하는 가장 근본적인 이유입니다.
정적 타입 시스템을 사용해서 안정적인 코드를 작성명확한 타입 정의와 인터페이스 덕분에 빠르게 이해하고 수정타입 추론, 자동완성, 강력한 리팩토링 도구 등 IDE 지원이 강화TypeScript의 Type
number: 숫자 타입 \(\rightarrow\) let a: number = 10;string: 문자열 타입 \(\rightarrow\) let b: string = "hello";boolean: 불리언 타입 \(\rightarrow\) let c: boolean = true;array: 배열 타입 \(\rightarrow\) let arr: number[] = [1, 2, 3];tuple: 고정된 길이와 타입을 가지는 배열 \(\rightarrow\) let tuple: [string, number] = ["age", 30];union: 여러 타입 중 하나 \(\rightarrow\) let value: string | number = "hi";literal: 특정 값만 허용 \(\rightarrow\) let dir: "left" | "right" = "left";object: 객체 타입 \(\rightarrow\) let obj: { name: string; age: number } = { name: "Kim", age: 25 };enum: 열거형 타입 \(\rightarrow\) enum Color { Red, Green, Blue }unknown: 타입을 알 수 없을 때 사용 (any보다 안전) \(\rightarrow\) let value: unknown = "hi";any: 아무 타입이나 허용 (가능하면 사용 지양) \(\rightarrow\) let value: any = "hi";void: 반환값이 없는 함수에서 사용 \(\rightarrow\) function log(msg: string): void { ... }null / undefined: 각각 null, undefined 값 \(\rightarrow\) let a: null = null;, let b: undefined = undefined;아무 값도 존재하지 않음을 나타냄기본 초기값실제 동작(메소드)과 데이터를 가진 객체의 인스턴스를 생성할 때(객체지향 패턴)상태와 로직을 함께 관리할 때interface 구조 보장객체 이외의 타입 표현이 필요할 때유연성 높음여러 곳에서 동일한 객체 타입을 약속할 때 주로 사용정렬(Sorting)이란, 주어진 데이터(배열, 리스트 등)를 일정한 기준(오름차순, 내림차순 등)에 따라 순서대로 나열하는 알고리즘입니다.
정렬은 데이터 탐색, 중복 제거, 효율적인 데이터 관리 등 다양한 컴퓨터 과학 문제에서 매우 중요한 역할을 합니다. 대표적인 정렬 알고리즘으로는 버블 정렬, 선택 정렬, 삽입 정렬, 퀵 정렬, 병합 정렬 등이 있으며, 각각의 알고리즘은 시간 복잡도와 공간 복잡도, 구현 난이도에 차이가 있습니다.
src/_tests_/sort.test.tsimport { simpleSort } from '@/sort';
import '@jest/globals';
describe('숫자 정렬 함수', () => {
test('기본적인 정렬 동작', () => {
expect(simpleSort([3, 1, 2])).toEqual([1, 2, 3]);
expect(simpleSort([10, -5, 0, 2])).toEqual([-5, 0, 2, 10]);
expect(simpleSort([1])).toEqual([1]);
expect(simpleSort([])).toEqual([]);
expect(simpleSort([0, -1, 1, -2, 2])).toEqual([-2, -1, 0, 1, 2]);
expect(simpleSort([1, 2, 3, 4, 5])).toEqual([1, 2, 3, 4, 5]);
expect(simpleSort([2, 3, 2, 1, 1])).toEqual([1, 1, 2, 2, 3]);
});
});src/sort.tsexport function simpleSort(arr: number[]): number[] {
const n = arr.length;
const result = [...arr];
for (let i = 0; i < n - 1; i++) {
for (let j = 0; j < n - i - 1; j++) {
if (result[j] > result[j + 1]) {
const temp = result[j];
result[j] = result[j + 1];
result[j + 1] = temp;
}
}
}
return result;
}import { simpleSort } from '@/sort';
import '@jest/globals';
describe('숫자 정렬 함수', () => {
test('기본적인 정렬 동작', () => {
expect(simpleSort([3, 1, 2])).toEqual([1, 2, 3]);
expect(simpleSort([10, -5, 0, 2])).toEqual([-5, 0, 2, 10]);
expect(simpleSort([1])).toEqual([1]);
expect(simpleSort([])).toEqual([]);
expect(simpleSort([0, -1, 1, -2, 2])).toEqual([-2, -1, 0, 1, 2]);
expect(simpleSort([1, 2, 3, 4, 5])).toEqual([1, 2, 3, 4, 5]);
expect(simpleSort([2, 3, 2, 1, 1])).toEqual([1, 1, 2, 2, 3]);
});
});
describe('숫자 내림차순 정렬 함수', () => {
test('기본적인 정렬 동작', () => {
expect(simpleSort([3, 1, 2], 'desc')).toEqual([3, 2, 1]);
expect(simpleSort([10, -5, 0, 2], 'desc')).toEqual([10, 2, 0, -5]);
expect(simpleSort([1], 'desc')).toEqual([1]);
expect(simpleSort([], 'desc')).toEqual([]);
expect(simpleSort([0, -1, 1, -2, 2], 'desc')).toEqual([2, 1, 0, -1, -2]);
expect(simpleSort([5, 4, 3, 2, 1], 'desc')).toEqual([5, 4, 3, 2, 1]);
expect(simpleSort([3, 2, 2, 1, 1], 'desc')).toEqual([3, 2, 2, 1, 1]);
});
});export function simpleSort(arr: number[], ord = 'asc'): number[] {
const n = arr.length;
const result = [...arr];
for (let i = 0; i < n - 1; i++) {
for (let j = 0; j < n - i - 1; j++) {
let shouldSwap = false;
if (ord === 'desc') {
// 내림차순: 앞의 요소가 뒤의 요소보다 작으면 교체
if (result[j] < result[j + 1]) {
shouldSwap = true;
}
} else {
// 오름차순: 앞의 요소가 뒤의 요소보다 크면 교체
if (result[j] > result[j + 1]) {
shouldSwap = true;
}
}
if (shouldSwap) {
const temp = result[j];
result[j] = result[j + 1];
result[j + 1] = temp;
}
}
}
return result;
}type SortOrder = 'asc' | 'desc'; // 정렬 방향(순서)
type CompareFn = (a: number, b: number) => number; // 비교 함수
const createCompareFunction = (order: SortOrder): CompareFn => { // 비교 함수 생성
return order === 'desc'
? (a: number, b: number) => b - a
: (a: number, b: number) => a - b;
};
export function simpleSort(arr: number[], ord: SortOrder = 'asc'): number[] {
if (arr.length <= 1) {
return [...arr];
}
const result = [...arr];
const compare = createCompareFunction(ord);
const n = result.length;
for (let i = 0; i < n - 1; i++) {
let swapped = false;
for (let j = 0; j < n - i - 1; j++) {
if (compare(result[j], result[j + 1]) > 0) {
[result[j], result[j + 1]] = [result[j + 1], result[j]];
swapped = true;
}
}
if (!swapped) {
break;
}
}
return result;
}import { simpleSort } from '@/sort';
import '@jest/globals';
describe('숫자 정렬 함수', () => {
test('기본적인 정렬 동작', () => {
expect(simpleSort([3, 1, 2])).toEqual([1, 2, 3]);
expect(simpleSort([10, -5, 0, 2])).toEqual([-5, 0, 2, 10]);
expect(simpleSort([1])).toEqual([1]);
expect(simpleSort([])).toEqual([]);
expect(simpleSort([0, -1, 1, -2, 2])).toEqual([-2, -1, 0, 1, 2]);
expect(simpleSort([1, 2, 3, 4, 5])).toEqual([1, 2, 3, 4, 5]);
expect(simpleSort([2, 3, 2, 1, 1])).toEqual([1, 1, 2, 2, 3]);
});
});
describe('숫자 내림차순 정렬 함수', () => {
test('기본적인 정렬 동작', () => {
expect(simpleSort([3, 1, 2], 'desc')).toEqual([3, 2, 1]);
expect(simpleSort([10, -5, 0, 2], 'desc')).toEqual([10, 2, 0, -5]);
expect(simpleSort([1], 'desc')).toEqual([1]);
expect(simpleSort([], 'desc')).toEqual([]);
expect(simpleSort([0, -1, 1, -2, 2], 'desc')).toEqual([2, 1, 0, -1, -2]);
expect(simpleSort([5, 4, 3, 2, 1], 'desc')).toEqual([5, 4, 3, 2, 1]);
expect(simpleSort([3, 2, 2, 1, 1], 'desc')).toEqual([3, 2, 2, 1, 1]);
});
});
describe('제네릭 정렬 함수', () => {
test('숫자 정렬', () => {
expect(simpleSort([3, 1, 2])).toEqual([1, 2, 3]);
expect(simpleSort([10, -5, 0, 2])).toEqual([-5, 0, 2, 10]);
expect(simpleSort(['c', 'a', 'b'])).toEqual(['a', 'b', 'c']);
expect(simpleSort(['banana', 'apple', 'cherry'])).toEqual(['apple', 'banana', 'cherry']);
expect(simpleSort([1, 2, 3], 'desc')).toEqual([3, 2, 1]);
expect(simpleSort(['a', 'b', 'c'], 'desc')).toEqual(['c', 'b', 'a']);
expect(simpleSort([])).toEqual([]);
});
});type SortOrder = 'asc' | 'desc';
type CompareFn<T> = (a: T, b: T) => number;
function createCompareFunction<T>(order: SortOrder): CompareFn<T> {
if (order === 'desc') {
return function(a: T, b: T): number {
if (a < b) return 1;
if (a > b) return -1;
return 0;
};
} else {
return function(a: T, b: T): number {
if (a > b) return 1;
if (a < b) return -1;
return 0;
};
}
}
export function simpleSort<T>(arr: T[], ord: SortOrder = 'asc'): T[] {
if (arr.length <= 1) {
return [...arr];
}
const result = [...arr];
const compare = createCompareFunction<T>(ord);
const n = result.length;
for (let i = 0; i < n - 1; i++) {
let swapped = false;
for (let j = 0; j < n - i - 1; j++) {
if (compare(result[j], result[j + 1]) > 0) {
const temp = result[j];
result[j] = result[j + 1];
result[j + 1] = temp;
swapped = true;
}
}
if (!swapped) {
break; // 최적화: 교체가 없으면 이미 정렬된 상태
}
}
return result;
}연결리스트는 각 노드가 데이터와 다음(또는 이전) 노드에 대한 참조를 가지는 자료구조로, 동적으로 크기가 변하는 선형 구조입니다.
data와 이전/다음 노드에 대한 참조 prev, nexthead, tail, _size와 같은 내부 상태를 private으로 선언size(), isEmpty(), clear() 등 기본 메서드를 구현addFirst(data), addLast(data)removeFirst(), removeLast()search(data), printList(), printListReverse()getFirst(), getLast()const, let: block scopevar: function scope
var 변수는 할당이 이루어지기 전까지는 undefined(큰 의미는 없음)readonly를 적절히 활용하여 인스턴스 할당을 방지class Node<T> {
public readonly data: T;
public prev: Node<T> | null = null;
public next: Node<T> | null = null;
constructor(data: T) {
this.data = data;
}
}
export class LinkedList<T> {
private head: Node<T> | null = null;
private tail: Node<T> | null = null;
private _size: number = 0;
public size(): number {
return this._size;
}
public append(data: T): void {
const newNode = new Node<T>(data);
if (this.head === null) {
this.head = newNode;
this.tail = newNode;
} else {
if (this.tail !== null) {
this.tail.next = newNode;
newNode.prev = this.tail;
this.tail = newNode;
}
}
this._size++;
}
public 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.removeNode(current);
return true;
}
current = current.next;
}
return false;
}
private removeNode(nodeToRemove: Node<T>): void {
if (nodeToRemove === this.head && nodeToRemove === this.tail) {
this.head = null;
this.tail = null;
}
else if (nodeToRemove === this.head) {
this.head = nodeToRemove.next;
if (this.head !== null) {
this.head.prev = null;
}
}
else if (nodeToRemove === this.tail) {
this.tail = nodeToRemove.prev;
if (this.tail !== null) {
this.tail.next = null;
}
}
else {
if (nodeToRemove.prev !== null) {
nodeToRemove.prev.next = nodeToRemove.next;
}
if (nodeToRemove.next !== null) {
nodeToRemove.next.prev = nodeToRemove.prev;
}
}
this._size--;
}
public search(data: T): T | null {
let current: Node<T> | null = this.head;
while (current !== null) {
if (current.data === data) {
return current.data;
}
current = current.next;
}
return null;
}
public printList(): readonly T[] {
const result: T[] = [];
let current: Node<T> | null = this.head;
while (current !== null) {
result.push(current.data);
current = current.next;
}
return result;
}
public printListReverse(): readonly T[] {
const result: T[] = [];
let current: Node<T> | null = this.tail;
while (current !== null) {
result.push(current.data);
current = current.prev;
}
return result;
}
public getFirst(): T | null {
return this.head?.data ?? null;
}
public getLast(): T | null {
return this.tail?.data ?? null;
}
public isEmpty(): boolean {
return this._size === 0;
}
public clear(): void {
this.head = null;
this.tail = null;
this._size = 0;
}
}큐(Queue)는 선입선출(FIFO: First In First Out) 방식으로 작동하는 자료구조입니다. 데이터가 들어온 순서대로 처리되며, 가장 먼저 들어온 데이터가 가장 먼저 나갑니다.
<T>: 다양한 타입의 데이터를 저장 가능enqueue로 추가, dequeue로 제거push, pop, top 메서드로 LIFO 방식도 지원export class Queue<T> {
private items: T[] = [];
// 큐 메서드
public enqueue(item: T): void
public dequeue(): T | undefined
public front(): T | undefined
public rear(): T | undefined
// 스택 메서드
public push(item: T): void
public pop(): T | undefined
public top(): T | undefined
// 유틸리티 메서드
public isEmpty(): boolean
public get size(): number
public clear(): void
}export class Queue<T> {
private items: T[] = [];
public enqueue(item: T): void {
this.items.push(item);
}
public dequeue(): T | undefined {
return this.items.shift();
}
public front(): T | undefined {
return this.items.length > 0 ? this.items[0] : undefined;
}
public rear(): T | undefined {
const length = this.items.length;
return length > 0 ? this.items[length - 1] : undefined;
}
public push(item: T): void {
this.items.push(item);
}
public pop(): T | undefined {
return this.items.pop();
}
public top(): T | undefined {
const length = this.items.length;
return length > 0 ? this.items[length - 1] : undefined;
}
public isEmpty(): boolean {
return this.items.length === 0;
}
public get size(): number {
return this.items.length;
}
public set size(_value: number) {
throw new Error("Size cannot be modified directly");
}
public clear(): void {
this.items = [];
}
}이진 탐색 트리(Binary Search Tree, BST)는 각 노드가 최대 두 개의 자식을 가지는 트리 자료구조이며, 왼쪽 자식은 부모보다 작고, 오른쪽 자식은 부모보다 큰 값을 가지는 특성을 가집니다.
<T>: 다양한 타입의 데이터를 저장 가능data, left, right 속성을 가짐각 노드는 데이터와 왼쪽/오른쪽 자식 노드에 대한 참조를 가집니다.
비교 함수는 두 값을 비교하여 음수(a < b), 0(a === b), 양수(a > b)를 반환합니다.
생성자에서 비교 함수를 선택적으로 받으며, 제공되지 않으면 기본 타입 비교 함수를 사용합니다.
export class BinaryTree<T> {
private root: TreeNode<T> | null = null;
private readonly compareFn: CompareFn<T>;
constructor(compareFn?: CompareFn<T>) {
if (compareFn !== undefined) {
this.compareFn = compareFn;
} else {
this.compareFn = defaultCompare as CompareFn<T>;
}
}
// 주요 메서드
public insert(value: T): void
public search(value: T): T | null
public remove(value: T): void
public inOrderTraversal(): T[]
public preOrderTraversal(): T[]
public postOrderTraversal(): T[]
public levelOrderTraversal(): T[]
}트리에 값을 삽입합니다. 재귀적으로 노드를 탐색하여 적절한 위치에 삽입합니다.
public insert(value: T): void {
this.root = this.insertNode(this.root, value);
}
private insertNode(node: TreeNode<T> | null, value: T): TreeNode<T> {
if (node === null) {
return new TreeNode(value);
}
const comparison = this.compareFn(value, node.data);
if (comparison < 0) {
node.left = this.insertNode(node.left, value);
} else if (comparison > 0) {
node.right = this.insertNode(node.right, value);
}
return node;
}트리에서 값을 검색합니다. 이진 탐색의 특성을 활용하여 효율적으로 탐색합니다.
public search(value: T): T | null {
return this.searchNode(this.root, value);
}
private searchNode(node: TreeNode<T> | null, value: T): T | null {
if (node === null) {
return null;
}
const comparison = this.compareFn(value, node.data);
if (comparison === 0) {
return node.data;
} else if (comparison < 0) {
return this.searchNode(node.left, value);
} else {
return this.searchNode(node.right, value);
}
}트리에서 값을 삭제합니다. 세 가지 경우를 처리합니다.
public remove(value: T): void {
this.root = this.removeNode(this.root, value);
}
private removeNode(node: TreeNode<T> | null, value: T): TreeNode<T> | null {
if (node === null) {
return null;
}
const comparison = this.compareFn(value, node.data);
if (comparison < 0) {
node.left = this.removeNode(node.left, value);
} else if (comparison > 0) {
node.right = this.removeNode(node.right, value);
} else {
// 삭제할 노드를 찾은 경우
if (node.left === null) {
return node.right;
} else if (node.right === null) {
return node.left;
} else {
// 오른쪽 서브트리의 최소값을 찾아서 옮김
const minValue = this.findMin(node.right);
node.data = minValue;
node.right = this.removeNode(node.right, minValue);
}
}
return node;
}
private findMin(node: TreeNode<T>): T {
while (node.left !== null) {
node = node.left;
}
return node.data;
}왼쪽 자식 → 루트 → 오른쪽 자식 순서로 방문합니다. 이진 탐색 트리에서는 오름차순으로 정렬된 결과를 얻을 수 있습니다.
루트 → 왼쪽 자식 → 오른쪽 자식 순서로 방문합니다.
왼쪽 자식 → 오른쪽 자식 → 루트 순서로 방문합니다.
레벨별로 왼쪽에서 오른쪽으로 방문합니다. 큐를 사용하여 BFS 방식으로 구현합니다.
public levelOrderTraversal(): T[] {
const result: T[] = [];
const root = this.root;
if (root === null) {
return result;
}
const queue: TreeNode<T>[] = [root];
let index = 0;
while (index < queue.length) {
const node = queue[index];
index++;
result.push(node.data);
const left = node.left;
const right = node.right;
if (left !== null) {
queue.push(left);
}
if (right !== null) {
queue.push(right);
}
}
return result;
}class TreeNode<T> {
public data: T;
public left: TreeNode<T> | null = null;
public right: TreeNode<T> | null = null;
constructor(data: T) {
this.data = data;
}
}
export type CompareFn<T> = (a: T, b: T) => number;
function defaultCompare<T extends string | number>(a: T, b: T): number {
if (a < b) return -1;
if (a > b) return 1;
return 0;
}
export class BinaryTree<T> {
private root: TreeNode<T> | null = null;
private readonly compareFn: CompareFn<T>;
constructor(compareFn?: CompareFn<T>) {
if (compareFn !== undefined) {
this.compareFn = compareFn;
} else {
this.compareFn = defaultCompare as CompareFn<T>;
}
}
public insert(value: T): void {
this.root = this.insertNode(this.root, value);
}
private insertNode(node: TreeNode<T> | null, value: T): TreeNode<T> {
if (node === null) {
return new TreeNode(value);
}
const comparison = this.compareFn(value, node.data);
if (comparison < 0) {
node.left = this.insertNode(node.left, value);
} else if (comparison > 0) {
node.right = this.insertNode(node.right, value);
}
return node;
}
public search(value: T): T | null {
return this.searchNode(this.root, value);
}
private searchNode(node: TreeNode<T> | null, value: T): T | null {
if (node === null) {
return null;
}
const comparison = this.compareFn(value, node.data);
if (comparison === 0) {
return node.data;
} else if (comparison < 0) {
return this.searchNode(node.left, value);
} else {
return this.searchNode(node.right, value);
}
}
public remove(value: T): void {
this.root = this.removeNode(this.root, value);
}
private removeNode(node: TreeNode<T> | null, value: T): TreeNode<T> | null {
if (node === null) {
return null;
}
const comparison = this.compareFn(value, node.data);
if (comparison < 0) {
node.left = this.removeNode(node.left, value);
} else if (comparison > 0) {
node.right = this.removeNode(node.right, value);
} else {
if (node.left === null) {
return node.right;
} else if (node.right === null) {
return node.left;
} else {
const minValue = this.findMin(node.right);
node.data = minValue;
node.right = this.removeNode(node.right, minValue);
}
}
return node;
}
private findMin(node: TreeNode<T>): T {
while (node.left !== null) {
node = node.left;
}
return node.data;
}
public inOrderTraversal(): T[] {
const result: T[] = [];
this.inOrder(this.root, result);
return result;
}
private inOrder(node: TreeNode<T> | null, result: T[]): void {
if (node !== null) {
this.inOrder(node.left, result);
result.push(node.data);
this.inOrder(node.right, result);
}
}
public preOrderTraversal(): T[] {
const result: T[] = [];
this.preOrder(this.root, result);
return result;
}
private preOrder(node: TreeNode<T> | null, result: T[]): void {
if (node !== null) {
result.push(node.data);
this.preOrder(node.left, result);
this.preOrder(node.right, result);
}
}
public postOrderTraversal(): T[] {
const result: T[] = [];
this.postOrder(this.root, result);
return result;
}
private postOrder(node: TreeNode<T> | null, result: T[]): void {
if (node !== null) {
this.postOrder(node.left, result);
this.postOrder(node.right, result);
result.push(node.data);
}
}
public levelOrderTraversal(): T[] {
const result: T[] = [];
const root = this.root;
if (root === null) {
return result;
}
const queue: TreeNode<T>[] = [root];
let index = 0;
while (index < queue.length) {
const node = queue[index];
index++;
result.push(node.data);
const left = node.left;
const right = node.right;
if (left !== null) {
queue.push(left);
}
if (right !== null) {
queue.push(right);
}
}
return result;
}
}| 버전 | 날짜 | 변경 내용 |
|---|---|---|
| v20251103 | 2025-11-03 | BinaryTree 자료구조 추가 |
| v20251013 | 2025-10-13 | Queue 자료구조 추가 |
| v20250929 | 2025-09-29 | LinkedList 추가 |
| v20250922 | 2025-09-22 | Type 관련 내용으로 재편집 |