배열...2편 (마지막임)

가은·2022년 9월 24일
0

숨참고 딥 다이브

목록 보기
28/31

배열 고차 함수

❓ 고차 함수

함수를 인수로 전달받거나 함수를 반환하는 함수다.

고차 함수는 외부 상태의 변경이나 가변 데이터를 피하고 불변성을 지향하는 함수형 프로그래밍에 기반을 두고 있다.

❓ 그러면 함수형 프로그래밍은 무엇인가

순수 함수와 보조 함수의 조합을 통해 로직 내에 존재하는 조건문과 반복문을 제거하여 복잡성을 해결하고 변수의 사용을 억제하여 상태 변경을 피하려는 프로그래밍 패러다임이다.

▪️ 조건문과 반복문은 로직의 흐름을 이해하기 어렵게 하고, 변수는 언제든지 변경될 수 있는 위험을 만든다.

결국 함수형 프로그래밍은 순수 함수를 통해 부수 효과를 최대한 억제하기 위한 것이다.

Array.prototype.sort

배열의 요소를 정렬한다. (기본값: 오름차순)

⭕ 원본 배열을 직접 변경하며 정렬된 배열을 반환한다.

const fruits = ['Banana', 'Orange', 'Apple'];

// 오름차순(ascending) 정렬
fruits.sort();

// sort 메서드는 원본 배열을 직접 변경한다.
console.log(fruits); // ['Apple', 'Banana', 'Orange']

// 내림차순(descending) 정렬
fruits.reverse();

// reverse 메서드도 원본 배열을 직접 변경한다.
console.log(fruits); // ['Orange', 'Banana', 'Apple']

▪️ sort 메서드는 기본적으로 오름차순으로 정렬하기 때문에 내림차순으로 요소를 정렬하려면 sort를 사용하고 reverse를 다시 사용하여 요소 순서를 뒤집어야 한다.

sort 메서드의 기본 정렬 순서는 유니코드 코드 포인트 순서를 따른다.
→ 배열의 요소를 일시적으로 문자열로 변환한 후 정렬하므로 숫자 배열에서는 정확하게 반환되지 않을 수 있다.

const points = [40, 100, 1, 5, 2, 25, 10];

points.sort();

// 숫자 요소들로 이루어진 배열은 의도한 대로 정렬되지 않는다.
console.log(points); // [1, 10, 100, 2, 25, 40, 5]

⚠️ 따라서 숫자 요소를 정렬할 때는 sort 메서드에 정렬 순서를 정의하는 비교 함수를 인자로 전달해야 한다.

const points = [40, 100, 1, 5, 2, 25, 10];

// 숫자 배열의 오름차순 정렬. 비교 함수의 반환값이 0보다 작으면 a를 우선하여 정렬한다.
points.sort((a, b) => a - b);
console.log(points); // [1, 2, 5, 10, 25, 40, 100]

// 숫자 배열에서 최소/최대값 취득
console.log(points[0], points[points.length]); // 1

// 숫자 배열의 내림차순 정렬. 비교 함수의 반환값이 0보다 작으면 b를 우선하여 정렬한다.
points.sort((a, b) => b - a);
console.log(points); // [100, 40, 25, 10, 5, 2, 1]

// 숫자 배열에서 최대값 취득
console.log(points[0]); // 100

객체를 요소로 갖는 배열을 정렬할 수 있다.
→ 이때는 삼항 조건 연산자를 사용하여 비교 연산을 해준다

Array.prototype.forEach

자신의 내부에서 반복문을 실행한다.
forEach 메서드는 반복문을 추상화한 고차함수로서 내부에서 반복문을 통해 자신을 호출한 배열을 순회하면서 수행해야 할 처리를 콜백 함수로 전달받아 반복 호출한다.

forEach의 콜백 함수는 forEach를 호출한 배열의 요소값과 인덱스, forEach를 호출한 배열 자체(this)를 순차적으로 전달받을 수 있다.

❌ 원본 배열(this)을 변경하지 않는다. 하지만 콜백 함수를 통해 원본 배열을 변경할 수는 있다.

const numbers = [1, 2, 3];

// forEach 메서드는 원본 배열을 변경하지 않지만 콜백 함수를 통해 원본 배열을 변경할 수는 있다.
// 콜백 함수의 세 번째 매개변수 arr은 원본 배열 numbers를 가리킨다.
// 따라서 콜백 함수의 세 번째 매개변수 arr을 직접 변경하면 원본 배열 numbers가 변경된다.
numbers.forEach((item, index, arr) => { arr[index] = item ** 2; });
console.log(numbers); // [1, 4, 9]

▪️ forEach 메서드의 반환값은 언제나 undefined 이다.
▪️ for문과 다르게 break, continue문을 사용할 수 없다. → 배열의 모든 요소를 빠짐없이 모두 순회하며 중단할 수 없다.
▪️ 희소 배열의 경우 존재하지 않는 요소는 순회 대상에서 제외된다.

Array.prototype.map

자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출한다. 그리고 콜백 함수의 반환값들로 구성된 새로운 배열을 반환한다.

❌ 원본 배열은 변경되지 않는다.

const numbers = [1, 4, 9];

// map 메서드는 numbers 배열의 모든 요소를 순회하면서 콜백 함수를 반복 호출한다.
// 그리고 콜백 함수의 반환값들로 구성된 새로운 배열을 반환한다.
const roots = numbers.map(item => Math.sqrt(item));

// 위 코드는 다음과 같다.
// const roots = numbers.map(Math.sqrt);

// map 메서드는 새로운 배열을 반환한다
console.log(roots);   // [ 1, 2, 3 ]
// map 메서드는 원본 배열을 변경하지 않는다
console.log(numbers); // [ 1, 4, 9 ]

map 메서드가 생성하여 반환하는 새로운 배열의 length 프로퍼티 값은 map 메서드를 호출한 배열의 length 프로퍼티 값과 일치한다.
→ 1 : 1 매핑

map 메서드의 콜백 함수는 map 메서드를 호출한 배열의 요소값과 인덱스, map 메서드를 호출한 배열 자체(this)를 순차적으로 전달받을 수 있다.
▪️ map 메서드의 두 번째 인수로 map 메서드의 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있다.

Array.prototype.filter

자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복호출한다. 그리고 콜백 함수 반환값이 true인 요소로만 구성된 새로운 배열을 반환한다.

❌ 원본 배열은 변경되지 않는다.

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

// filter 메서드는 numbers 배열의 모든 요소를 순회하면서 콜백 함수를 반복 호출한다.
// 그리고 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환한다.
// 다음의 경우 numbers 배열에서 홀수인 요소만을 필터링한다(1은 true로 평가된다).
const odds = numbers.filter(item => item % 2);
console.log(odds); // [1, 3, 5]

▪️ filter 메서드는 자신을 호출한 배열에서 필터링 조건을 만족하는 특정 요소만 추출하여 새로운 배열을 만들고 싶을 때 사용한다.

filter 메서드가 생성하여 반환한 새로운 배열의 length 프로퍼티 값은 filter 메서드를 호출한 배열의 length 프로퍼티 값과 같거나 작다.

filter 메서드의 콜백 함수는 filter 메서드를 호출한 배열의 요소값과 인덱스, filter 메서드를 호출한 배열 자체(this)를 순차적으로 전달한다.

▪️ 자신을 호출한 배열에서 특정 요소를 제거하기 위해 사용할 수도 있다.

class Users {
  constructor() {
    this.users = [
      { id: 1, name: 'Lee' },
      { id: 2, name: 'Kim' }
    ];
  }

  // 요소 추출
  findById(id) {
    // id가 일치하는 사용자만 반환한다.
    return this.users.filter(user => user.id === id);
  }

  // 요소 제거
  remove(id) {
    // id가 일치하지 않는 사용자를 제거한다.
    this.users = this.users.filter(user => user.id !== id);
  }
}

const users = new Users();

let user = users.findById(1);
console.log(user); // [{ id: 1, name: 'Lee' }]

// id가 1인 사용자를 제거한다.
users.remove(1);

user = users.findById(1);
console.log(user); // []

Array.prototype.reduce

자신을 호출한 배열을 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출한다. 그리고 콜백 함수의 반환값을 다음 순회 시에 콜백 함수의 첫 번째 인수로 전달하면서 콜백 함수를 호출하여 하나의 결과값을 만들어 반환한다.

❌ 원본은 변경되지 않는다.

reduce 메서드는 첫 번째 인수로 콜백 함수, 두 번째 인수로 초기값을 전달받는다.

▪️ reduece 메서드는 자신을 호출한 배열의 모든 요소를 순회하며 하나의 결과값을 구해야 하는 경우에 사용한다.

// [1, 2, 3, 4]의 모든 요소의 누적을 구한다.
const sum = [1, 2, 3, 4].reduce((accumulator, currentValue, index, array) => accumulator + currentValue, 0);

console.log(sum); // 10
구분/인수accumlatorcurrentValueindexarray콜백함수 반환값
순회 10 (초기값)10[1,2,3,4]1 (accumlator+currentValue)
순회 2121[1,2,3,4]3 (accumlator+currentValue)
순회 3332[1,2,3,4]6 (accumlator+currentValue)
순회 4643[1,2,3,4]10 (accumlator+currentValue)

▪️ reduce 메서드를 호출할 때는 언제나 초기값을 전달하는 것이 안전하다.

Array.prototype.some

자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출한다.
이때 콜백 함수의 반환값이 단 한 번이라고 참이면 true, 모두 거짓이면 false를 반환한다.

▪️ 배열의 요소 중에 콜백 함수를 통해 정의한 조건을 만족하는 요소가 1개 이상 존재하는지 확인하여 불리언 타입으로 반환한다.
⚠️ 호출한 배열이 빈 배열이면 언제나 false를 반환한다.

// 배열의 요소 중에 10보다 큰 요소가 1개 이상 존재하는지 확인
[5, 10, 15].some(item => item > 10); // -> true

// 배열의 요소 중에 0보다 작은 요소가 1개 이상 존재하는지 확인
[5, 10, 15].some(item => item < 0); // -> false

// 배열의 요소 중에 'banana'가 1개 이상 존재하는지 확인
['apple', 'banana', 'mango'].some(item => item === 'banana'); // -> true

// some 메서드를 호출한 배열이 빈 배열인 경우 언제나 false를 반환한다.
[].some(item => item > 3); // -> false

Array.prototype.every

자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출한다.
이때 콜백 함수의 반환값이 모두 참이면 true, 단 한 번이라도 거짓이면 false를 반환한다.

▪️ 배열의 모든 요소가 콜백 함수를 통해 정의한 조건을 모두 만족하는지 확인하여 불리언 타입으로 반환한다.
⚠️ 호출한 배열이 빈 배열인 경우 언제가 true를 반환한다.

// 배열의 모든 요소가 3보다 큰지 확인
[5, 10, 15].every(item => item > 3); // -> true

// 배열의 모든 요소가 10보다 큰지 확인
[5, 10, 15].every(item => item > 10); // -> false

// every 메서드를 호출한 배열이 빈 배열인 경우 언제나 true를 반환한다.
[].every(item => item > 3); // -> true

Array.prototype.find

자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출하여 반환값이 true인 첫 번째 요소를 반환한다.

▪️ 콜백 함수의 반환값이 true인 요소가 존재하지 않으면 undefined를 반환한다.
▪️ find 메서드는 콜백 함수의 반환값이 true인 첫 번째 요소를 반환하므로 결과값은 배열이 아닌 해당 요소 값이다.

// Array#filter는 배열을 반환한다.
[1, 2, 2, 3].filter(item => item === 2); // -> [2, 2]

// Array#find는 요소를 반환한다.
[1, 2, 2, 3].find(item => item === 2); // -> 2

발견된 첫 번째 요소만 반환하기 때문에 그 뒤에 같은 값의 요소가 있어도 반환할 수 없는 단점이 있다.

Array.prototype.findIndex

자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출하여 반환값이 true인 첫 번째 요소의 인덱스를 반환한다.

▪️ 콜백 함수의 반환값이 true인 요소가 존재하지 않는다면 -1을 반환한다.

const users = [
  { id: 1, name: 'Lee' },
  { id: 2, name: 'Kim' },
  { id: 2, name: 'Choi' },
  { id: 3, 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

Array.prototype.flatMap

map 메서드를 통해 생성된 새로운 배열을 평탄화한다.
→ map 메서드와 flat 메서드를 순차적으로 실행한다.

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 메서드는 인수를 전달하여 평탄화 깊이를 지정할 수 없고 1단계만 평탄화하기 때문에 map 메서드를 통해 생성된 중첩 배열의 깊이를 지정해야 한다면 map과 flat 메서드를 각각 호출해야 한다.

profile
일이 재밌게 진행 되겠는걸?

0개의 댓글