진호우 2025. 1. 20. 22:59

배열 개념

같은 타입의 원소들을 관리할 수 있는 기본 자료형.

순서가 있고, 중복이 허용된다.

 

배열 선언

1. 리터럴

const arr = [1, 2, 3, 4, 5, 6];

 

2. 생성자

const arr1 = new Array(6); // [undefined, undefined, ...]
const arr2 = [...new Array(6)].map((_, i) => i + 1); // [1, 2, 3, 4, 5, 6]

 

3. Array.fill()

const arr = new Array(6).fill(0); // [0, 0, 0, 0, 0, 0]

 

배열의 index 는 0부터 시작한다. 3번째 데이터에 접근하려면 arr[2]로 접근하면 도니다.

 

배열과 차원

다차원의 배열도 실제로는 1차원 공간에 저장한다. -> 배열은 차원과는 무관하게 메모리에 연속할당한다.

 

2차원 배열

// 2차원 배열을 리터럴로 표현
const arr = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]];

// arr[2][3]에 저장된 값을 출력
console.log(arr[2][3]); // 12

// arr[2][3]에 저장된 값을 15로 변경
arr[2][3] = 15;

//변경된 값을 출력
console.log(arr[2][3]); // 15

 

 

배열의 효율성

배열의 시간 복잡도

배열은 임의 접근이라는 방법으로 배열의 모든 위치에 있는 데이터에 단 한 번에 접근할 수 있다. O(1)

또 데이터를 어디에 저장하느냐에 따라 시간 복잡도가 달라진다.

 

맨 뒤에 삽입할 경우

바로 접근할 수 있으며 다른 데이터 위치에 영향을 주지 않는다. 

O(1)

 

맨 앞에 삽입할 경우

기존 데이터를 한 칸씩 밀어야한다. 

O(N)

 

중간에 삽입할 경우

삽입한 데이터 뒤에 있는 개수만큼 밀어야한다.

O(N)

 

배열을 선택할 때 고려할 점

데이터에 자주 접근하거나 읽어야 하는 경우 배열을 사용하면 좋은 성능을 낼 수 있다.

 

자주 활용하는 배열의 기법

배열에 데이터 추가 제거

push(): 배열 끝에 요소 추가

const arr = [1, 2];
arr.push(3); // [1, 2, 3]

 

pop(): 배열 끝의 요소 제거

 

const arr = [1, 2, 3];
arr.pop(); // [1, 2]

 

unshift(): 배열 시작에 요소 추가

const arr = [1, 2];
arr.unshift(0); // [0, 1, 2]

 

shift(): 배열 시작의 요소 제거

const arr = [0, 1, 2];
arr.shift(); // [1, 2]

 

탐색

indexOf(): 특정 요소의 첫 번째 인덱스 반환

const arr = [1, 2, 3, 2];
arr.indexOf(2); // 1

 

lastIndexOf(): 특정 요소의 마지막 인덱스 반환

arr.lastIndexOf(2); // 3

 

includes(): 배열에 특정 요소가 있는지 확인

arr.includes(3); // true

 

순회

forEach(): 배열의 각 요소에 대해 실행

const arr = [1, 2, 3];
arr.forEach(el => console.log(el)); // 1, 2, 3

 

변환

map(): 각 요소를 변환하여 새로운 배열 반환

const arr = [1, 2, 3];
const doubled = arr.map(el => el * 2); // [2, 4, 6]

 

filter(): 조건에 맞는 요소만 추출한 배열 반환

const arr = [1, 2, 3, 4];
const evens = arr.filter(el => el % 2 === 0); // [2, 4]

 

reduce(): 배열 요소를 누적하여 단일 값 반환

const arr = [1, 2, 3];
const sum = arr.reduce((acc, el) => acc + el, 0); // 6

 

정렬 및 반전

sort(): 배열을 정렬 (기본적으로 문자열로 정렬)

const arr = [3, 1, 2];
arr.sort(); // [1, 2, 3]
arr.sort((a, b) => b - a); // [3, 2, 1]

 

reverse(): 배열 순서를 반전

arr.reverse(); // [1, 2, 3] -> [3, 2, 1]

 

병합

concat(): 배열 병합

const arr1 = [1, 2];
const arr2 = [3, 4];
const merged = arr1.concat(arr2); // [1, 2, 3, 4]

 

spread 연산자

const merged = [...arr1, ...arr2]; // [1, 2, 3, 4]

 

추출

slice(): 배열 일부를 복사하여 새로운 배열 반환

const arr = [1, 2, 3, 4];
const part = arr.slice(1, 3); // [2, 3]

 

splice(): 배열에서 요소를 제거하거나 추가

const arr = [1, 2, 3, 4];
arr.splice(1, 2); // [1, 4]
arr.splice(1, 0, 2, 3); // [1, 2, 3, 4]

 

유용한 고급 메서드

flat(): 중첩 배열을 평탄화

const arr = [1, [2, [3, 4]]];
arr.flat(2); // [1, 2, 3, 4]

 

find(): 조건을 만족하는 첫 번째 요소 반환

const arr = [1, 2, 3, 4];
const found = arr.find(x => x > 2); // 3

 

findIndex(): 조건을 만족하는 첫 번째 요소의 인덱스 반환

const index = arr.findIndex(x => x > 2); // 2

 

some(): 조건을 만족하는 요소가 하나라도 있으면 true

arr.some(x => x > 3); // true

 

every(): 모든 요소가 조건을 만족하면 true

arr.every(x => x > 0); // true

 

배열을 다룰 때 성능 이슈를 피하려면 필요한 경우에만 메서드를 사용하며, 대규모 데이터에 대해서는 for문 또는 효율적인 알고리즘을 사용하는 것이 좋다.

 

배열 메서드의 성능 문제

  1. 메서드의 반복 호출로 인한 성능 저하
    • map, filter, reduce 같은 메서드는 새로운 배열을 생성하므로 메모리를 추가로 사용한다.
    • 여러 메서드를 연속으로 호출하면 중간 결과 배열들이 계속 생성되어 성능이 떨어질 수 있다.
  2. 콜백 함수 호출
    • 배열 메서드는 각 요소마다 콜백을 실행하므로, 배열 크기가 클수록 많은 함수 호출이 이루어집니다. 이는 단순 반복문(for)에 비해 비용이 크다.
  3. JavaScript 엔진 최적화
    • 대부분의 배열 메서드는 내부적으로 복잡한 로직을 포함하며, 일부는 느릴 수 있습니다. 단순한 반복 작업에는 for나 while 루프가 더 빠를 때가 많다.
const arr = [1, 2, 3, 4, 5];
const result = arr.filter(x => x % 2 === 0).map(x => x * 2); // filter와 map 모두 새 배열 생성

 

효율적인 배열 처리 방법

1. 단일 반복문으로 작업 합치기

  • 여러 메서드를 체이닝하는 대신, 단일 반복문으로 작업을 결합하면 중간 배열 생성을 피할 수 있다.
const arr = [1, 2, 3, 4, 5];
const result = [];
for (let i = 0; i < arr.length; i++) {
  if (arr[i] % 2 === 0) {
    result.push(arr[i] * 2);
  }
}

 

2. 데이터 구조 선택

  • 배열 대신 목적에 맞는 데이터 구조를 사용하는 것이 효율적일 수 있다.
    • 빈번한 삽입/삭제: 배열 대신 LinkedList.
    • 고속 조회: 배열 대신 Set 또는 Map.