DevYoon

[JS] 배열과 포인터 본문

언어/Javascript

[JS] 배열과 포인터

gimewn 2022. 7. 24. 23:53

배열

Contents

1️⃣ 자바스크립트의 배열은 자료구조의 배열과 같나요?

2️⃣ 배열의 메서드는 어떤 종류가 있나요?

3️⃣ 고차함수에 대해서 아나요?

4️⃣ forEach 메서드와 map 메서드의 차이점에 대해 알고 있나요?

1️⃣ 자바스크립트의 배열 == 자료구조의 배열?

자료구조의 배열

  • 동일한 크기의 메모리 공간이
  • 빈틈없이 연속적으로 나열된 자료구조

​ ➡️ 밀집 배열(Dense Array)

장점

  • 단 한 번의 연산으로 임의의 요소에 접근 가능 ➡️ 임의 접근(random access)

  • 시간복잡도 : O(1)

    ex) 메모리 주소가 1000에서 시작하고 각 요소의 크기가 8byte인 배열이라면

    ​ 인덱스가 0인 요소의 메모리 주소는 ➡️ 1000 + 0*8 = 1000

    ​ 인덱스가 1인 요소의 메모리 주소는 ➡️ 1000 + 1*8 = 1008

단점

  • 정렬되지 않은 배열에서 특정한 값 탐색 ➡️ 모든 배열 요소를 차례대로 탐색 (선형 탐색, Linear Search) ➡️ 시간복잡도 O(n)

    ➡️ 배열 [1, 5, 3, 9]가 있다고 가정했을 때, 3을 찾으려면 반복문을 통해 순회해야 함

  • 배열에 요소를 삽입 및 삭제할 경우 배열을 연속적으로 유지하기 위해 요소를 이동시켜야 함

    ➡️ 배열 [1, 5, 3, 9] 중 5를 삭제할 경우 3이 인덱스 1, 9가 인덱스 2의 자리로 이동해야 함

자바스크립트의 배열

  • 각각의 메모리 공간은 동일한 크기를 가지지 않아도 되며
  • 연속적으로 이어져 있지 않을 수도 있음

​ ➡️ 희소 배열(Sparse Array)

​ ➡️ 자바스크립트의 배열은 일반적인 배열의 동작을 흉내낸 특수한 객체

console.log(Object.getOwnPropertyDescriptors([1, 2, 3]));

/*
{
  '0': { value: 1, writable: true, enumerable: true, configurable: true },
  '1': { value: 2, writable: true, enumerable: true, configurable: true },
  '2': { value: 3, writable: true, enumerable: true, configurable: true },
  length: { value: 3, writable: true, enumerable: false, configurable: false }
}
*/

➡️ 요소를 프로퍼티, 인덱스를 프로퍼티 값으로 가지고, length 프로퍼티를 가지는 객체

장점

  • 특정 요소를 탐색하거나 요소를 삽입 및 삭제하는 경우, 자료구조의 배열보다 빠름

단점

  • 인덱스로 배열 요소에 접근하는 경우, 자료구조의 배열보다 느림

2️⃣ 배열의 메서드 종류

isArray()

  • 주어진 인수가 배열인지 아닌지 확인
  • isArray(arg) 에서 arg가 배열이면 true, 아니면 false 반환

from()

  • ES6에서 새롭게 도입
  • 특정 객체를 변환하여 새로운 배열 생성
    • 유사 배열 객체 : length 프로퍼티와 인덱스된 요소를 가지고 있는 객체
    • 이터러블 객체 : 해당 요소를 개별적으로 선택할 수 있는 객체 (Map, Set, 문자열 등)
// 문자열은 이터러블이다.
const arr1 = Array.from('Hello');
console.log(arr1); // [ 'H', 'e', 'l', 'l', 'o' ]

// 유사 배열 객체를 새로운 배열을 변환하여 반환한다.
const arr2 = Array.from({ length: 2, 0: 'a', 1: 'b' });
console.log(arr2); // [ 'a', 'b' ]

// Array.from의 두번째 매개변수에게 배열의 모든 요소에 대해 호출할 함수를 전달할 수 있다.
// 이 함수는 첫번째 매개변수에게 전달된 인수로 생성된 배열의 모든 요소를 인수로 전달받아 호출된다.
const arr3 = Array.from({ length: 5 }, function (v, i) { return i; });
console.log(arr3); // [ 0, 1, 2, 3, 4 ]

of()

  • ES6에서 새롭게 도입
  • 전달된 인수를 요소로 갖는 배열을 생성
// 전달된 인수가 1개이고 숫자이더라도 인수를 요소로 갖는 배열을 생성한다.
const arr1 = Array.of(1);
console.log(arr1); // // [1]

const arr2 = Array.of(1, 2, 3);
console.log(arr2); // [1, 2, 3]

fill()

  • 배열의 시작 인덱스부터 끝 인덱스 이전까지 하나의 값으로 채워주는 메서드
  • 원본 배열 직접 변경, 변경한 배열을 리턴해줌
  • fill(value, start, end)
    • value : 배열 채울 값
    • start : 시작인덱스, 기본값 0
    • end : 끝 인덱스, 기본값 arr.length
// 길이가 6이고 배열의 원소가 모두 0인 배열을 만들기

// 리터럴 방법
const zeroArray = [0, 0, 0, 0, 0, 0];

// fill()메소드 방법
const zeroArray2 = Array(6).fill(0);
console.log(nullArray); // [0, 0, 0, 0, 0, 0]

[1, 2, 3].fill(4)                // [4, 4, 4] 모두 4로 채룸
[1, 2, 3].fill(4, 1)             // [1, 4, 4] 1번째 인덱스 부터 쭉 4로 채움
[1, 2, 3].fill(4, 1, 2)          // [1, 4, 3] 1번째에서 2번째 인덱스 전까지 4로 채움

// fill ()을 사용하여 모두 1의 행렬(2차원 배열) 만들기
const arr = new Array(3);

for (let i=0; i<arr.length; i++) {
  arr[i] = new Array(4).fill(1); // 크기가 4이고 1로 채워진 배열 생성
}

console.log(arr[0][0]); // 1
console.log(arr[1][0]); // 1
console.log(arr[2][0]); // 1

indexOf()

  • 원본 배열에서 인수로 전달된 요소를 검색하여 인덱스 반환
  • 중복되는 요소 있는 경우 첫번째 인덱스 반환
  • 해당 요소 없는 경우 -1 반환
  • indexOf(searchElement, fromIndex)
    • searchElement : 검색할 요소
    • fromIndex : 검색 시작할 요소, 생략 시 처음부터 검색
const foods = ['apple', 'banana', 'orange'];

// foods 배열에 'orange' 요소가 존재하는지 확인
if (foods.indexOf('orange') === -1) { // == if (!foods.includes('orange')) { } 
  foods.push('orange');
}

console.log(foods); // ["apple", "banana", "orange"]

concat()

  • 인수로 전달된 값들(배열 또는 값)을 원본 배열의 마지막 요소로 추가한 새로운 배열을 반환
  • 원본 배열 변경되지 않음
const arr1 = [1, 2];
const arr2 = [3, 4];

// 배열 arr2를 원본 배열 arr1의 마지막 요소로 추가한 새로운 배열을 반환
// 인수로 전달한 값이 배열인 경우, 배열을 해체하여 새로운 배열의 요소로 추가한다.
let result = arr1.concat(arr2);
console.log(result); // [1, 2, 3, 4]

// 숫자를 원본 배열 arr1의 마지막 요소로 추가한 새로운 배열을 반환
result = arr1.concat(3);
console.log(result); // [1, 2, 3]

//  배열 arr2와 숫자를 원본 배열 arr1의 마지막 요소로 추가한 새로운 배열을 반환
result = arr1.concat(arr2, 5);
console.log(result); // [1, 2, 3, 4, 5]

// 원본 배열은 변경되지 않는다.
console.log(arr1); // [1, 2]

push()

  • 인수로 전달받은 모든 값을 원본 배열의 마지막 요소로 추가
  • 변경된 length 값 반환
const arr1 = [1, 2];
// push 메소드는 원본 배열을 직접 변경한다.
arr1.push(3, 4);
console.log(arr1); // [1, 2, 3, 4]

const arr2 = [1, 2];
// concat 메소드는 원본 배열을 변경하지 않고 새로운 배열을 반환한다.
const result = arr2.concat(3, 4);
console.log(result); // [1, 2, 3, 4]

const arr1 = [1, 2];
// 인수로 전달받은 배열을 그대로 원본 배열의 마지막 요소로 추가한다
arr1.push([3, 4]);
console.log(arr1); // [1, 2, [3, 4]]

const arr2 = [1, 2];
// 인수로 전달받은 배열을 해체하여 새로운 배열의 마지막 요소로 추가한다
const result = arr2.concat([3, 4]);
console.log(result); // [1, 2, 3, 4]

pop()

  • 원본 배열에서 마지막 요소를 제거하고 제거한 요소를 반환
  • 원본 배열이 빈 배열일 경우 undefined를 반환
  • 원본 배열을 직접 변경
const a = ['a', 'b', 'c'];
const c = a.pop();

// 원본 배열이 변경된다.
console.log(a); // a --> ['a', 'b']
console.log(c); // c --> 'c'

shift()

  • 배열에서 첫 요소를 제거하고, 제거한 요소를 반환
  • 빈 배열일 경우 undefined를 반환
  • 대상 배열을 직접 변경
const arr = [];

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

arr.shift(); // [2, 3]
arr.shift(); // [3]
arr.shift(); // []

unshift()

  • 요소를 배열의 첫 인덱스로 삽입
cost arr = [1, 2, 3];
arr.shift();
console.log(arr) // [2, 3]
arr.unshift(4);
console.log(arr) // [4, 2, 3]

join()

  • 배열을 문자열로 변환
  • 구분자 넣지 않으면 기본값으로 콤마(,)가 포함되어 합쳐짐
const arr = ['h', 'i']
let res;
res = arr.join(); // 'h,i'
res = arr.join(' '); // 'h i'

split()

  • 문자열을 배열로 변환
  • 구분자를 넣지 않으면 문자열이 배열의 아이템 1개로 들어감
const arr = 'h, i';
let toArr;
toArr = arr.split(); // ['h,i']
toArr = arr.split(','); // ['h', 'i']

reverse()

  • 배열의 순서를 뒤집음
arr = [1, 2, 3];
arr.reverse(); // [3, 2, 1]

splice()

  • 인덱스로 배열의 아이템을 삭제 및 추가
  • arr.splice(인덱스, 삭제할 개수, 추가할 아이템...);
const arr = [1, 2, 3];
arr.splice(1, 1) // [1, 3]
arr.splice(1, 1, 5) // [1, 5, 3]

slice()

  • 배열의 특정한 부분을 리턴
  • arr.slice(시작인덱스, 끝인덱스)
  • 끝 인덱스의 앞 부분까지만 포함
const arr = [1, 2, 3, 4, 5]
arr.slice(1, 4) // [2, 3, 4]

3️⃣ 배열 고차함수

고차함수 : 함수를 파라미터로 전달받거나 연산의 결과로 반환해주는 메서드

find()

  • 주어진 배열을 순회하면서 콜백함수 실행의 반환값이 true에 해당하는 첫번째 요소를 반환

  • find((element, index, array) => {...}, thisArg)

    • element : 현재 배열요소값
    • index : 배열 인덱스
    • array : 참조한 배열
    • thisArg : 콜백함수에서 this로 사용할 값
  • 찾은 요소의 타입을 return, 없으면 undefinded를 return

const numberArr = [1, 3, 3, 5, 7];
const objectArr = [
    { name: 'Harry', age: 20 },
    { name: 'Kim', age: 30 },
    { name: 'Steve', age: 40 }
];

console.log(objectArr.find(item => {
   return item.age === 20 // 해당조건에 부합하면 item값 반환
}); // {name: "Harry", age: 20}
console.log(numberArr.find(item => item === 3));  // 3
console.log(numberArr.filter(item => item === 3));  // [3, 3]

findIndex()

  • find()의 리턴값이 인덱스인 버전
  • findIndex((element, index, array) => {...}, thisArg)
    • element : 현재 배열요소값
    • index : 배열 인덱스
    • array : 참조한 배열
    • thisArg : 콜백함수에서 this로 사용할 값
  • 요소가 테스트를 통과하면 배열의 인덱스 / 그렇지 않으면 -1 return
const objectArr = [
    { name: 'Harry', age: 20 },
    { name: 'Kim', age: 30 },
    { name: 'Steve', age: 40 }
];

console.log(objectArr.findIndex(item => {
   return item.age === 20 // 해당조건에 부합하면 item의 인덱스를 반환
}); // 0
console.log(objectArr.findIndex(item => item.name === 'Kim')); // 1

filter()

  • 주어진 배열을 순회하면서 콜백 함수의 반환값이 true에 해당하는 요소로만 구성된 새로운 배열을 생성하여 반환
  • filter( (element, index, array) => {......}, thisArg)
    • element : 현재 배열요소값
    • index : 배열 인덱스
    • array : 참조한 배열
    • thisArg : 콜백함수에서 this로 사용할 값
  • 테스트를 통과한 요소로 이루어진 새로운 배열 / 어떤 요소도 테스트를 통과하지 못했으면 빈 배열을 return
const numberArr = [1, 2, 3, 4, 5];
const numberFilterArr = numberArr.filter((item) => {
    return item % 2 === 0; // 해당조건에 부합으면 item을 넣어 배열 반환
});

console.log(numberArr);    // [1, 2, 3, 4, 5]
console.log(numberFilterArr); // [2, 4]

reduce()

  • 이전 콜백 함수의 실행된 반환값을 전달 받은 연산의 결과값이 반환
  • acc서부터 시작해서 배열값cur을 순회하며 acc+=cur을 실행
  • reduce( (accumulator, currentValue, index, array) => {......}, initialValue)
  • accumulator : 누산기. 순회하면서 계속 더해해서 합쳐지는 값
  • currentValue : 현재 값
  • index : 배열 인덱스
  • array : 참조한 배열
  • initialValue : 콜백 최초 호출에서 acc 누산기에 제공하는 값. 초기값을 제공하지 않으면 배열의 첫 번째 요소를 사용. 빈 배열에서 초기값 없이 호출하면 에러.
  • 누적 계산의 결과값을 return

*️⃣ PLUS

  • reduce()함수 호출시 initialValue 값이 없는 경우

    • accumulator : 배열의 첫번째 값

    • currentValue : 배열의 두번째 값

  • reduce()함수 호출시 initialValue 값이 있는 경우

    • accumulator : initialValue가 지정한 값

    • currentValue : 배열의 첫번째 값

const numberArr = [1, 2, 3, 4, 5];

const sum = numberArr.reduce((previousValue, currentValue, currentIndex, thisArray) => {
    console.log('Current Index: ' + currentIndex + ' / Previous Value: ' + previousValue 
                  + ' / Current Value: ' + currentValue);
    return previousValue + currentValue; // 연산한 결과값을 누산기previousValue에 넣어 최종값을 얻는다.
}, 0);

console.log('Sum: ' + sum);

/*
Current Index: 0 / Previous Value: 0 / Current Value: 1
Current Index: 1 / Previous Value: 1 / Current Value: 2
Current Index: 2 / Previous Value: 3 / Current Value: 3
Current Index: 3 / Previous Value: 6 / Current Value: 4
Current Index: 4 / Previous Value: 10 / Current Value: 5
Sum: 15
*/

sort()

  • 원 배열이 정렬됨
  • sort(comperFunction)
    • comperFunction : 정렬 순서를 정의하는 함수
    • 생략 시 배열의 요소들이 문자열로 취급되어 유니코드 값 순서대로 정렬됨
    • 따라서 숫자 정렬 시 설정해주어야 함

숫자 정렬

const arr = [2, 1, 3, 10];

arr.sort(function(a, b)  {
  return a - b;
});  // [1, 2, 3, 10] 오름차순

arr.sort(function(a, b)  {
  return b - a;
}); // [10, 3, 2, 1] 내림차순

문자 정렬

const arr = ['banana', 'b', 'boy'];

arr.sort(); // ['b', 'banana', 'boy']

arr.sort(function(a, b) {
  if(a < b) return 1;
  if(a > b) return -1;
  if(a === b) return 0;
});  // ['boy', 'banana', 'b'] 내림차순

some()

  • 배열의 요소들이 주어진 함수(조건)을 통과하는데 한개라도 통과되면 true, 다 아닐때에는 false를 출력
  • 빈 배열로 함수(조건)을 통과하면 무조건 false를 출력
  • some( (currentValue, index, array) => {......}, thisArg)
    • currentValue : 현재 배열요소값
    • index : 배열 인덱스
    • array : 참조한 배열
    • thisArg : 콜백함수에서 this로 사용할 값
const array = [1, 3, 5];

// checks whether an element is even
const even = (element) => element % 2 === 0;

console.log(array.some(even));
// expected output: false이다. 
// 그 이유는 array의 3개의 요소 모두 2로 나눌때 나머지가 0이 아니기 때문이다.

every

  • 배열의 요소들이 주어진 함수(조건)을 통과하는데 모두 통과되면 true, 한 개라도 통과하지 못하면 false를 출력
  • 빈 배열로 함수(조건)을 통과시키면 무조건 true를 출력
  • every( (currentValue, index, array) => {......}, thisArg)
    • currentValue : 현재 배열요소값
    • index : 배열 인덱스
    • array : 참조한 배열
    • thisArg : 콜백함수에서 this로 사용할 값
const array1 = [1, 30, 39, 29, 100, 13];

const isBelowThreshold = (currentValue) => currentValue < 40;

console.log(array1.every(isBelowThreshold));
// expected output: false이다.
// array1요소 중 100이 조건에 맞지 않기 때문이다.

forEach()

  • for문을 대체하는 고차함수
  • 내부에서 주어진 배열을 순회하며 연산 수행
  • forEach((item, index, thisArr) => {.......})
    • item : 배열요소값
    • index : 배열 인덱스
    • thisArr : 참조한 배열
const numberArr = [1, 2, 3, 4, 5];
let total = 0;

numberArr.forEach((item) => {
    total += item;
});

console.log(total); // 15

map()

  • 순회하면서 콜백함수에서의 실행결과를 리턴한 값으로 이루어진 배열을 만들어 반환
  • map((currentValue, index, array) => {}, thisArg)
    • currentValue : 현재 배열요소 값
    • index : 배열 인덱스
    • array : 참조한 배열
    • thisArg : 콜백함수에서 this로 사용할 값
  • 찾은 요소의 타입을 return, 없으면 undefinded를 return
const numberArr = [1, 2, 3, 4, 5];
const numberMapArr = numberArr.map((item) => {
    return (item % 2 === 0) ? 'even' : 'odd'; // 연산한 결과값을 넣어 배열 반환
});

console.log(numberMapArr); // ['odd', 'even', 'odd', 'even', 'odd']

3️⃣ forEach 메서드와 map 메서드의 차이점

두 메서드 모두 배열을 순회하는 것은 동일

forEach()는 각 요소를 참조한 연산이 이루어지고

map()은 각 요소를 다른 값으로 맵핑한 새로운 배열이 반환

➡️ forEach는 for을 대체해서 사용, map()은 연산의 결과로 새로운 배열을 생성하고자 할 때 사용

*️⃣ 참고글