zero-wiki Help

27장 배열

1 배열이란?

여러 개의 값을 순차적으로 나열한 자료구조이다. 자바스크립트에서는 배열을 다루기 위한 유용한 메소드를 다수 제공한다.

사실 자바스크립트에는 배열이라는 타입이 존재하지 않는다. 배열은 객체 타입이다.

구분

객체

배열

구조

프로퍼티 키와 프로퍼티 값

인덱스와 요소

값의 참조

프로퍼티 키

인덱스

값의 순서

X

O

length 프로퍼티

X

O

배열과 객체의 가장 큰 차이는 값의 순서가 있느냐, length 프로퍼티 (길이)를 가지고 있느냐 이다. 위 속성들을 가지고있어 배열의 장점은 순차적으로 요소에 접근할 수도 있고, 마지막부터 역순으로 요소에 접근할 수도 있으며, 특정 위치에 특정한 요소 (인덱스 번호를 통해) 처음부터 끝까지 검색(선형 검색)할 수도 있다는 것이다.

2 자바스크립트 배열은 배열이 아니다.

자료구조에서 말하는 배열은 메모리 공간이 빈틈없이 연속적으로 나열된 자료구조를 말한다. 이러한 배열을 밀집 배열(dense array)이라고 말한다. 이는 빈틈없이 이어져있는것을 이용해 인덱스 번호를 통해 임의의 요소에 접근할 수도 있다.

하지만 자바스크립트에서의 배열은 다르다 이 배열은 각각의 메모리 공간이 동일한 크기를 갖고있지 않아도 되며, 연속적으로 이어져 있지 않는 배열이다. 이러한 배열을 희소 배열(sparse array)라고 한다.

일반적인 배열과 자바스크립트 배열의 장단점을 정리.

  • 일반적인 배열은 인덱스로 요소에 접근할 수 있다. 하지만 요소를 삽입 또는 삭제하는 경우에는 효율적이지 않다.

  • 자바스크립트 배열은 해시 테이블로 구현된 객체이므로 인덱스로 요소에 접근하는 경우 일반적인 배열보다 성능적인 면에서 느릴수밖에 없는 구조적인 단점이 있다. 하지만 요소를 삽입 또는 삭제하는 경우에는 일반적인 배열보다 빠른 성능을 기대할 수 있다.

쉽게 말해서 인덱스 번호를 통해 찾는건 느리지만 관리에 유용하다.

3 length 프로퍼티와 희소 배열

length 프로퍼티는 요소의 개수, 즉 배열의 길이를 나타내는 0 이상의 정수를 값으로 갖는다. 빈 배열일 경우 0이며, 빈 배열이 아닌 경우 가장 큰 인덱스에 1을 더한 것과 같다.

인덱스 번호에 number 를 할당을 해줌으로서 길이를 바꾸고 공백을 추가할 수 도 있다.

const arr = [1, 2, 3, 4, 5]; // 현재 length 프로퍼티 값인 5보다 작은 숫자 값 3을 length 프로퍼티에 할당 arr.length = 3; // 배열의 길이가 5에서 3으로 줄어든다. console.log(arr); // [1,2,3] const arr2 = [1]; // 현재 length 프로퍼티 값인 1보다 큰 숫자 값 3을 length 프로퍼티에 할당 arr2.length = 3; // length 프로퍼티 값은 변경되지만 실제로 배열의 길이가 늘어나지는 않는다. console.log(arr.length); // 3 console.log(arr2.length); // [1, empty * 2]

4 배열생성

4.1 배열 리터럴

const arr = [1,2,3] console.log(arr.length); // 3
const arr = [1,,3] console.log(arr.length); // 3 console.log(arr); // [1, empty, 3]

희소배열을 생성할 수 있다.

4.2 Array 생성자 함수

const arr = new Array(10); console.log(arr); // [empty * 10] console.log(arr.length); // 10

전달된 인수가 1개이고 숫자인 경우 length 프로퍼티 값이 인수인 배열을 생성한다.

const arr = new Array(10); console.log(arr); // [empty * 10] console.log(arr.length); // 10 console.log(Object.getOwnPropertyDescriptor(arr)); // undefined

이때 생성된 배열은 희소 배열이다. length 프로퍼티 값은 0이 아니지만 실제로 배열의 요소는 존재하지 않는다.

new Array(); // => []

전달된 인수가 없는 경우 빈 배열을 생성한다.

4.3 Array.of

ES6에서 도입된 Array.of 메소드는 전달된 인수를 요소로 갖는 배열을 생성한다.

// 전달된 인수가 1개이고 숫자이더라도 인수를 요소로 갖는 배열을 생성한다. Array.of(1); // -> [1] Array.of(1, 2, 3); // -> [1, 2, 3] Array.of('string'); // -> ['string']

4.4 Array.from

ES6에서 도입된 Array.from 메소드는 유사 배열 객체 또는 인터러블 객체를 인수로 전달받아 배열로 변환하여 만든다.

// 유사 배열 객체를 변환하여 배열을 생성한다. Array.from({length: 2, 0: 'a', 1: 'b'}); // -> ['a', 'b'] // 이터러블을 변환하여 배열을 생성한다. 문자열은 인터러블이다. Array.from('Hello'); // -> ['H', 'e', 'l', 'l', 'o']

length를 전달해줄 경우 해당 크기의 배열이 생성이 된다.

// Array.from에 length만 존재하는 유사 배열 객체를 전달하면 undefined를 요소로 채운다. Array.from({length : 3}); // => [undefined, undefined, undefined] // Array.from은 두 번째 인수로 전달한 콜백 함수의 반환값으로 구성된 배열을 반환한다. Array.from({ length:3 }, (_, i) => i); // => [0, 1, 2]

유사 배열 객체와 이터러블 객체

유사 배열 객체(array-like object)는 마치 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고 length 프로퍼티를 갖는객체를 말한다. 유사 배열 객체는 마치 배열처럼 for문으로 순회가능하다.

// 유사 배열 객체 const arraylike = { '0': 'apple', '1': 'banana', '2': 'orange', length : 3 }; // 유사 배열 객체는 마치 배열처럼 for문으로 순회할 수도 있다. for(let i = 0; i < arraylike.length; i++){ console.log(arraylike[i]); }

이터러블 객체는 Symbol.iterator 메소드를 구현하여 for ... of 문으로 순회할 수 있으며, 스프레드 문법과 배열 디스트럭처링 할당의 대상으로 사용할 수 있는 객체를 말한다.

5 배열 요소의 참조

배열의 요소를 참조할 때에는 대괄호([ ]) 표기법을 사용한다. 존재하지않는 요소를 참조하면 undefined가 반환된다.

6 배열 요소의 추가와 갱신

존재하지 않는 인덱스를 사용해 값을 할당하면 새로운 요소가 추가된다. 이때 length 프로퍼티 값은 자동으로 갱신된다.

const arr = [0]; // 배열 요소의 추가 arr[1] = 1; console.log(arr); // [0, 1] console.log(arr.length); // 2 // 현재 배열의 length 프로퍼티 값보다 큰 인덱스로 새로운 요소를 추가하면 희소배열이 된다. arr[100] = 100; console.log(arr); // [0, 1, ... empty * 98, 100] console.log(arr.length); // 101

또한 인덱스로 요소에 접근하여 명시적으로 값을 할당하지 않았을 경우 요소는 생성되지 않는다.

인덱스는 요소의 위치를 나타내므로 반드시 0 이상의 정수를 사용해야한다. 만약 정수 이외의 값을 인덱스처럼 사용하면 요소가 생성되는 것이 아니라 프로퍼티가 생성된다. 이때 추가된 프로퍼티는 length 프로퍼티 값에 영향을 주지 않는다.

const arr = new Array(); // 배열 요소의 추가 arr[0] = 1; arr['1'] = 2; // 프로퍼티 추가 arr['foo'] = 3; arr.bar = 4; arr[1.1] = 5; arr[-1] = 6; console.log(arr); // [1, 2, foo: 3, bar: 4, '1.1' : 5, '-1' : 6] // 프로퍼티는 length에 영향을 주지 않는다. console.log(arr.length);

7 배열 요소의 삭제

배열은 객체이기 때문에 delete 연산자를 사용할 수 있다.

const arr = [1, 2, 3]; // 배열 요소의 삭제 delete arr[1]; console.log(arr); // [ 1, <1 empty item>, 3 ] // 배열의 길이에 영향을 끼치지 않는다.

Array.prototype.splice

const arr = [1, 2, 3]; // Array.prototype.splice(삭제를 시작할 인덱스, 삭제할 요소수) // arr[1]부터 1개의 요소를 제거 arr.splice(1, 1); console.log(arr); // [ 1, 3 ] // 배열의 길이가 자동 갱신된다. console.log(arr.length); // 2

9 배열 고차함수

배열의 요소들을 순서에 맞게 정렬해주기 (Array.sort( ))

const months = ['March', 'Jan', 'Feb', 'Dec']; months.sort(); console.log(months); // Expected output: Array ["Dec", "Feb", "Jan", "March"] const array1 = [1, 30, 4, 21, 100000]; array1.sort(); console.log(array1); // Expected output: Array [1, 100000, 21, 30, 4]

객체를 요소로 갖는 배열을 정렬하기 (compare)

// 객체를 요소로 갖는 배열을 정렬하는 예제 const todos = [ { id: 4, content: "JavaScript" }, { id: 1, content: "HTML" }, { id: 2, content: "CSS" }, ]; // 비교함수. 매개변수 key는 프로퍼티 키다. function compare(key) { // 프로퍼티 값이 문자열인 경우 - 산술연산으로 비교하면 NaN이 나오므로 비교 연산을 사용할 수 없다. // 비교 함수는 양수/음수/0을 반환하면 되므로 - 산술 연산 대신 비교 연산을 사용할 수 있다. return (a, b) => (a[key] > b[key] ? 1 : a[key] < b[key] ? -1 : 0); } // id를 기준으로 오름차순 정렬 todos.sort(compare("id")); console.log(todos); /* [ { id: 1, content: 'HTML' }, { id: 2, content: 'CSS' }, { id: 4, content: 'JavaScript' } ] */ // content를 기준으로 오름차순 정렬 todos.sort(compare("content")); console.log(todos); /* [ { id: 2, content: 'CSS' }, { id: 1, content: 'HTML' }, { id: 4, content: 'JavaScript' } ] */

배열의 각각의 요소에 대해 함수를 호출하기 (Array.forEach( ))

const array1 = ['a', 'b', 'c']; array1.forEach(element => console.log(element)); // Expected output: "a" // Expected output: "b" // Expected output: "c"

배열 각각의 콜백 함수를 호출, 이후 새로운 배열로 반환하기 (Array.map( ))

const array1 = [1, 4, 9, 16]; // Pass a function to map const map1 = array1.map(x => x * 2); console.log(map1); // Expected output: Array [2, 8, 18, 32]
  • 원본의 값은 변경되지 않는다.

배열내 조건에 맞는 값들만 받아오기 (Array.filter( ))

const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present']; const result = words.filter(word => word.length > 6); console.log(result); // Expected output: Array ["exuberant", "destruction", "present"]
const number = [1, 2, 3, 4, 5]; // filter 메소드는 number 배열의 모든 요소를 순회하면서 콜백 함수를 반복 호출한다. // 그리고 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환한다. // 다음의 경우 number 배열에서 홀수인 요소만 필터링한다(1은 true로 평가된다.) const odds = number.filter((item) => item % 2); console.log(odds); //[ 1, 3, 5 ]

주어진 콜백 함수를 가산기와 배열 각각에 대해 (왼쪽 > 오른쪽) 으로 호출하여 각각의 값을 하나의 값으로 줄인 결과를 반환하기 (Array.reduce( ))

const array1 = [1, 2, 3, 4]; // 0 + 1 + 2 + 3 + 4 const initialValue = 0; const sumWithInitial = array1.reduce( (accumulator, currentValue) => accumulator + currentValue, initialValue ); console.log(sumWithInitial); // Expected output: 10

다양한 reduce( )활용 방법

평균 구하기

// 평균 구하기 const values = [1, 2, 3, 4, 5, 6]; const average = values.reduce((acc, cur, i, { length }) => { // 마지막 순회가 아니면 누적값을 반환하고 마지막 순회면 평균구해 반환한다. return i === length - 1 ? (acc + cur) / length : acc + cur; }, 0); console.log(average);

최대값 구하기

// 최대값 구하기 const values = [1, 2, 3, 4, 5]; const max = values.reduce((acc, cur) => (acc > cur ? acc : cur), 0); console.log(max); // 최대값을 구할 때는 reduce 메소드보다 Math.max 메소드를 사용하는 방법이 직관적이다. const method_max = Math.max(...values); // var max = Math.max.apply(null, values); console.log(method_max);

요소의 중복 횟수 구하기

// 요소의 중복 횟수 구하기 const fruits = ["banana", "apple", "orange", "orange", "apple"]; const count = fruits.reduce((acc, cur) => { // 첫 번째 순회시 acc는 초기값인 {}이고 cur은 첫 번째 요소인 'banana'이다. // 초기값으로 전달받은 빈 객체에 요소값인 cur을 프로퍼티 키로, 요소의 개수를 프로퍼티 값으로 할당한다. // 만약 프로퍼티 값이 undefined(처음 등장하는 요소)이면 프로퍼티 값을 1로 초기화 한다. acc[cur] = (acc[cur] || 0) + 1; return acc; }, {}); // 콜백 함수는 총 5번 호출되고 다음과 같이 결과 값을 반환한다. /* {banana : 1} => {banana : 1, apple : 1} => {banana : 1, apple : 1, orange : 1} => {banana : 1, apple : 1, orange : 2} => {banana : 1, apple : 2, orange : 2} */ console.log(count); //

중첩 배열 평탄화

// 중첩 배열 평탄화 const values = [1, [2, 3], 4, [5, 6]]; const flatten = values.reduce((acc, cur) => acc.concat(cur), []); // [1] => [1, 2, 3] => [1, 2, 3, 4] => [1, 2, 3, 4, 5, 6] console.log(flatten); // [ 1, 2, 3, 4, 5, 6 ] // 평탄화 할때는 Array.prototype.flat 메소드가 더 직관적이고 효율적이다. [1, [2, 3, 4, 5]].flat(); // => [1, 2, 3, 4, 5] // 인수 2는 중첩 배열을 평탄화하기 위한 깊이 값이다. [1, [2, 3, [4, 5]]].flat(2); // => [1, 2, 3, 4, 5]

중복 요소 제거

// 중복 요소 제거 const values = [1, 2, 1, 3, 5, 4, 5, 3, 4, 4]; const result = values.reduce( (unique, val, i, _values) => // 현재 순회중인 요소의 인덱스 i가 val의 인덱스와 같다면 val은 처음 순회하는 요소이다. // 현재 순회중인 요소의 인덱스 i가 val의 인덱스와 다르다면 val은 중복된 요소이다. // 처음 순회하는 요소만 초기값 []가 전달된 unique 배열에 담아 반환하면 중복된 요소는 제거된다. _values.indexOf(val) === i ? [...unique, val] : unique, [] ); console.log(result); // [ 1, 2, 3, 5, 4 ] // 중복 요소를 제거할때는 filter 메소드를 사용하는 방법이 더 직관적이다. const value_1 = [1, 2, 1, 3, 5, 4, 5, 3, 4, 4]; // 현재 순회중인 요소의 인덱스 i가 val의 인덱스와 같다면 val은 처음 순회하는 요소다. 이 요소만 필터링 된다. const result_2 = value_1.filter((val, i, value_1) => value_1.indexOf(val) === i); console.log(result_2); // 중복은 허용하지 않는 Set 객체의 특성을 활용해서 중복된 요소를 제거할 수도 있다. const value_2 = [1, 2, 1, 3, 5, 4, 5, 3, 4, 4]; const result_3 = [...new Set(value_2)]; console.log(result_3);

객체의 프로퍼티 값을 합산하는 경우

// 객체의 프로퍼티 값을 합산하는 경우 반드시 초기값을 전달해야 한다. const products = [ { id: 1, price: 100 }, { id: 2, price: 200 }, { id: 3, price: 300 }, ]; /* 1번째 순회 : acc => 0, cur => { id:1, price: 100 } 1번째 순회 : acc => 100, cur => { id:2, price: 100 } 1번째 순회 : acc => 300, cur => { id:3, price: 100 } */ const priceSum = products.reduce((acc, cur) => acc + cur.price, 0); console.log(priceSum); // 600

배열내 값들이 조건을 모두 만족하는지 검사하기 (Array.prototype.every)

const isBelowThreshold = (currentValue) => currentValue < 40; const array1 = [1, 30, 39, 29, 10, 13]; console.log(array1.every(isBelowThreshold)); // Expected output: true

정해진 조건을 만족하는 첫번째 요소를 반환하기 (Array.prototype.find( ))

const array1 = [5, 12, 8, 130, 44]; const found = array1.find(element => element > 10); console.log(found); // Expected output: 12

find( )와 filter( )의 차이

// filter 메소드는 배열을 반환한다. [1,2,2,3].filter(item => item === 2); // [2, 2] // find 메소드는 요소를 반환한다. [1,2,2,3].find(item => item === 2); // 2

정해진 조건을 만족하는 첫 요소의 인덱스를 반환하기 (Array.findIndex( ))

const array1 = [5, 12, 8, 130, 44]; const isLargeNumber = (element) => element > 13; console.log(array1.findIndex(isLargeNumber)); // Expected output: 3
const users = [ { id: 1, name: "Lee" }, { id: 2, name: "Kim" }, { id: 3, name: "Choi" }, { id: 4, name: "Park" }, ]; // id가 2인 요소의 인덱스를 구한다. users.findIndex((user) => user.id === 2); // => 1 // name이 Park 인 요소의 인덱스를 구한다. users.findIndex((user) => user.name === "Park"); // => 3 // 위와 같이 프로퍼티 키와 프로퍼티 값으로 요소의 인덱스를 구하는 경우 다음과 같이 콜백 함수를 추상화 할 수 있다. function predicate(key, value) { // key와 value를 기억하는 클로저를 반환 return (item) => item[key] === value; } // id가 2인 요소의 인덱스를 구한다. users.findIndex(predicate("id", 2)); // => 1 // name가 Park인 요소의 인덱스를 구한다. users.findIndex(predicate("name", "Park")); // => 3

map 메소드를 통해 생성된 새로운 배열을 평탄화하기 (Array.flatMap())

let arr1 = ["it's Sunny in", "", "California"]; arr1.map(x => x.split(" ")); // [["it's","Sunny","in"],[""],["California"]] arr1.flatMap(x => x.split(" ")); // ["it's","Sunny","in","California"]
const arr = ["hello", "world"]; // map과 flat을 순차적으로 실행 arr.map((x) => x.split("")).flat(); // => ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'] // flatMap은 map을 통해 생성된 새로운 배열을 평탄화 한다. arr.flatMap((x) => x.split("")); // => ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']

단 flatMap( ) 메소드는 flat( ) 메소드와 달리 평탄화 깊이를 지정할 수 없어 1단계만 평탄화 한다. 평탄화 깊이를 지정하려면 map 메소드와 flat 메소드를 각각 호출한다.

const arr = ["hello", "world"]; // flatMap은 1단계만 평탄화 한다. arr.flatMap((str, index) => [index, [str, str.length]]); // -> [[0, ['hello', 5]], [1, ['world', 5]]] => [0, ['hello', 5], 1, ['world', 5]] // 평탄화 깊이를 지정해야 하면 flatMap 메소드를 사용하지 말고 map 메소드와 flat 메소드를 각각 호출한다. arr.map((str, index) => [index, [str, str.length]]).flat(2); // -> [[0, ['hello', 5]], [1, ['world', 5]]] =>[0, 'hello', 5, 1, 'world', 5]
Last modified: 31 December 2024