Space
article thumbnail
Published 2023. 6. 5. 17:55
[JS] 고차 함수 (1) JavaScript
반응형

고차 함수를 사용하는 이유

(HOF : Higher-Order Function)

 

추상화를 통한 효율성 증대 때문이다.

 

 

map, filter, reduce는

자주 사용되므로 반드시 익히자!!

 


★ map

// map((el) => el * 2);
// map(el, index, arr);

// 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출하고,
// 호출된 콜백함수의 반환값들로 구성된 새로운 배열을 반환한다.
// (= 배열의 처음 요소부터 마지막 요소까지 하나씩 매개변수에 입력하여
//    모든 요소를 조건에 따른 결과값을 새로운 배열에 모아서 리턴한다.)
// 원본 배열을 변경하지 않는다.

// 매핑(mapping) : 배열의 요소를 map 메서드을 통하여 다른 값으로 적용하여 새로운 배열을 생성하는 것
//                 = 배열에 map 메서드를 적용한 것

const num = [1, 4, 9];

const roots = num.map(el => Math.sqrt(el));
console.log(roots);      // [1, 2, 3]  
console.log(num);        // [1, 4, 9]

const multiply3 = num.map(el => el * 3);
console.log(multiply3);  // [3, 12, 27]  
console.log(num);        // [1, 4, 9]

// map 메서드가 생성하여 반환하는 새로운 배열의 length 값은
// map 메서드를 호출한 배열의 length 값과 반드시 일치한다.
// (= beforeMapArr === afterMapArr)
// map 메서드는 콜백 함수를 호출하면서 3개(el, index, arr)의 인수를 순차적으로 전달한다. 
// (요소값, 인덱스값, 배열) (= filter, forEach, every, some, find, findIndex)

[1, 2, 3].map((el, index, arr) => {
	console.log(`요소값: ${el}, 인덱스: ${index}, this: ${JSON.stringify(arr)}`);
});
// 요소값: 1, 인덱스: 0, this: [1,2,3]
// 요소값: 2, 인덱스: 1, this: [1,2,3]
// 요소값: 3, 인덱스: 2, this: [1,2,3]
반응형

 filter

// filter (el => el * 2);
// filter (el, index, arr);

// 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백함수를 반복 호출하고,
// 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환한다.
// (= 배열의 각 요소에 콜백 함수를 적용시켰을 때,
//    true를 리턴하는 요소들만 모은 새로운 배열을 리턴한다.)
// 원본 배열을 변경하지 않는다.

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

const odds = numbers.filter(el => el % 2);
console.log(odds);  // [1, 3, 5]

// filter 메서드가 생성하여 반환한 새로운 배열의 length 값은 
// filter 메서드를 호출한 배열의 length 값과 같거나 작다.
// (= beforeFilteredArr.length >= afterFilteredArr.length)
// filter 메서드는 콜백 함수를 호출하면서 3개(el, index, arr)의 인수를 순차적으로 전달한다. 
// (요소값, 인덱스값, 배열) (= map, forEach, every, some, find, findIndex)

[1, 2, 3].filter((el, index, arr) => {
	console.log(`요소값: ${el}, 인덱스: ${index}, this: ${JSON.stringify(arr)}`);
    return el % 2;
});
// 요소값: 1, 인덱스: 0, this: [1,2,3]
// 요소값: 2, 인덱스: 1, this: [1,2,3]
// 요소값: 3, 인덱스: 2, this: [1,2,3]
class Users {
    constructor() {
    	this.users = [
            {id: 1, name: 'Lee'},
            {id: 1, name: 'Kim'}
        ];
    }
    
    // 요소 추출
    findById(id) {
    	// id가 일치하는 사용자만 반환한다.
        return this.users.filter(el => el.id === id);
    }
    
    // 요소 제거
    remove(id) {
    	// id가 일치하지 않는 사용자를 제거한다.
        this.users = this.users.filter(el => el.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);  // []

 reduce

// reduce ((acc, cur), init);
// reduce ((accmulator, curentValue, index, array), initialValue);

// 자신을 호출한 배열을 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출하고,
// 콜백 함수의 반환값을 다음 순회 시에 콜백 함수의 첫 번째 인수로 전달하면서
// 콜백 함수를 호출하여 하나의 결과값을 만들어 반환한다.
// (= 배열의 각 요소를 콜백 함수에 맞게 하나로 응축시킨 값으로 리턴한다.)

// 원본 배열을 변경하지 않는다.

const sum = [1, 2, 3, 4].reduce((acc, cur, index, arr) => acc + cur, 0);
console.log(sum);  // 10
// 초기값은 생략할 수 있다.
// 하지만, 초기값을 설정하는 것이 안전하다.
// 만약, 초기값을 설정하지 않으면 배열의 첫 번째 요소가 초기값이 된다.

const sum = [1, 2, 3, 4].reduce((acc, cur, index, arr) => acc + cur);
console.log(sum);  // 10

// 초기값을 전달한 경우
const sum1 = [].reduce((acc, cur) => acc + cur, 0);
console.log(sum1);  // 0

// 초기값 생략하여 Error 발생한 경우
const sum2 = [].reduce((acc, cur) => acc + cur);
// TypeError: Reduce of empty array with no initial value
// in Object
// 객체의 특정 프로퍼티 값을 합산하는 경우 반드시 초기값을 설정하여야 한다.

const products = [
    { id: 1, price: 100 },
    { id: 2, price: 200 },
    { id: 3, price: 300 }
];

// 초기값 설정 시
const priceSumUsedInit = products.reduce((acc, cur) => acc + cur.price, 0);
// 1번째 순회 : acc = 0, cur = { id: 1, price: 100 }
// 2번째 순회 : acc = 100, cur = { id: 2, price: 200 }
// 3번째 순회 : acc = 300, cur = { id: 3, price: 300 }
console.log(priceSumUsedInit);    // 600

// 초기값 미설정 시
const priceSumUnusedInit = products.reduce((acc, cur) => acc.price + cur.price);
// 1번째 순회 : acc = { id: 1, price: 100 }, cur = { id: 2, price: 200 }
// 2번째 순회 : acc = 300, cur = { id: 3, price: 300 }
// 2번째 순회 시 acc에 함수에 객체가 아닌 숫자값이 전달된다.
// 2번째 순회시 arr.price === undefined 이다. (300.price === undefined)
console.log(priceSumUnusedInit);  // NaN

 

// example 
// 평균 구하기

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

console.log(average);  // 3
// reduce보다는 반복문을 활용하자!
// example 
// 최대값 구하기

const values = [1, 2, 3, 4, 5];
const max = values.reduce((acc, cur) => (acc > cur ? acc : cur), 0);
console.log(max);  // 5

// reduce보다는 Math.max를 활용하자!
// example
// 요소의 중복 횟수 구하기

const alphabet = ['a', 'c', 'd', 'e', 'a', 'b', 'b', 'c'];
const count = alphabet.reduce((acc, cur) => {
	acc[cur] = (acc[cur] || 0) + 1;
    return acc;
}, {});
console.log(count);  // { a: 2, c: 2, d: 1, e: 1, b: 2 }

// 1번째 순회 : acc = {}, cur = 'a'
// 초기값으로 전달된 빈 객체에 요소값인 cur을 프로퍼티 ket로, 요소의 개수를 프로퍼티 값으로 할당한다.
// 만약 프로퍼티 값이 undefined(처음 등장하는 요소)이면 프로퍼티 값을 1로 초기화한다.
// example
// 배열의 평탄화

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]

// reduce 보다는 flat를 활용하자!
// example
// 중복 요소 제거

const values = [1, 2, 1, 3, 5, 4, 5, 3, 4, 4];
const result = values.reduce((unique, val, i, _values) =>
	_values.indexOf(val) === i ? [ ...unique, val ] : unique, []
);
console.log(result);  // [1, 2, 3, 5, 4]

// reduce 보다는 Set을 활용하자!

 


 forEach

// forEach(el => el * 2);
// forEach(el, index, arr);

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

const arrNum = [1, 3, 5];
const pows = [];

arrNum.forEach(el => pows.push(el ** 2));
console.log(pows);  // [ 1, 9, 25 ]

// 위 예제의 경우
// forEach 메서드는 arrNum 배열의 요소가 3개이므로 콜백 함수도 3번 호출된다.
// 이때 콜백 함수를 호출하는 forEach 메서드는 콜백 함수에 인수를 전달할 수 있다.

// forEach는 원본 배열을 변경하지 않는다.
// 하지만, 콜백 함수를 통해 원본 배열을 변경할 수는 있다.
arrNum.forEach((el, index, arr) => { arr[index] = el ** 2; });
console.log(arrNum);  // [ 1, 9, 25 ]
// forEach 메서드는 콜백 함수를 호출하면서 3개(el, index, arr)의 인수를 순차적으로 전달한다. 
// (요소값, 인덱스값, 배열) (= map, filter, every, some, find, findIndex)

[1, 2, 3].forEach((el, index, arr) => {
	console.log(`요소값: ${el}, 인덱스: ${index}, this: ${JSON.stringify(arr)}`);
});
// 요소값: 1, 인덱스: 0, this: [1,2,3]
// 요소값: 2, 인덱스: 1, this: [1,2,3]
// 요소값: 3, 인덱스: 2, this: [1,2,3]
// for문을 대체할 수 있는 고차함수이다.
// for문과 달리 모든 요소를 순회할 수 밖에 없기에,
// break, continue 문을 사용할 수 없다.
// 하지만 for문에 비해 가독성이 좋다.

const arr = [1, 2, 3];

arr.forEach(el => {
	console.log(el);
    if (el > 1) break;  
});  // SyntaxError: Illegal break statement

arr.forEach(el => {
	console.log(el);
    if (el > 1) continue;  
});  // SyntaxError: Illegal continue statement: no surrounding iteration statement
// forEach 메서드의 반환값은 언제나 undefined 이다.

const result = [1, 2, 3].forEach(console.log);
console.log(result);  // undefined

 


sort

// sort();
// sort((a, b) => a - b);

// 문자열로 변환시켜 오름차순으로 정렬한다.
// (기본적으로 숫자는 낮은 숫자, 영어는 알파벳, 한글은 가나다 순으로 정렬된다.)
// 원본배열을 변경한다.

// 영어
let arr0 = ['melon', 'apple', 'kiwi', 'banana'];
console.log(arr0.sort());   // ['apple', 'banana', 'kiwi', 'melon']
console.log(arr0);          // ['apple', 'banana', 'kiwi', 'melon']

// 한글
let arr1 = ['다리미', '개미', '까마귀', '가위', '수박'];
console.log(arr1.sort());   // [ '가위', '개미', '까마귀', '다리미', '수박' ]

// 숫자
// 요소가 문자열로 인식되어 정렬되기 때문에 숫자부분에서 제대로 오름차순이 정렬되지 않는다!!
let arr2 = [22, 11, 2, 9, 1, 5];
console.log(arr2.sort());    // [1, 11, 2, 22, 5, 9]
// 따라서 숫자요소를 정렬할 때에는 
// sort 메서드에 정렬 순서를 정의하는 비교 함수를 인수로 전달하여야 한다.
// 비교 함수는 양수나 음수 또는 0을 반환해야한다.

// 비교 함수의 반환값 < 0 : 비교 함수의 첫 번째 인수를 우선하여 정렬,
// 비교 함수의 반환값 = 0 : 정렬하지 않으며,
// 비교 함수의 반환값 > 0 : 두 번째 인수를 우선하여 정렬한다.

// 올바른 오름차순 방법
let arr1 = [22, 11, 2, 9, 1, 5];
console.log(arr1.sort(function (a, b){
    return a - b;  }));     // [1, 2, 5, 9, 11, 22]

let arr2 = [22, 11, 2, 9, 1, 5];
console.log(arr2.sort((a, b) => (a - b)));   // [1, 2, 5, 9, 11, 22]

// 최소, 최대값 구하기
console.log(arr1[0], arr1[arr1.length -1]);   // 1 22
// 올바른 내림차순 방법
let arr1 = [22, 11, 2, 9, 1, 5];
console.log(arr1.sort(function (a, b){
    return b - a;  }));     // [22, 11, 9, 5, 2, 1]

let arr2 = [22, 11, 2, 9, 1, 5];
console.log(arr2.sort((a, b) => (b - a)));  // [22, 11, 9, 5, 2, 1]

// 최소, 최대값 구하기
console.log(arr1[arr1.length -1], arr1[0]);   // 1 22
// 문자열 길이가 작은 순서대로 정렬하는 방법
let arr = ['melon', 'apple', 'kiwi', 'banana'];
console.log(arr.sort((a, b) => (a.length - b.length))); // [ 'kiwi', 'melon', 'apple', 'banana' ]

// cf) reverse : 요소의 순서를 뒤집는다.
// reverse도 원본 배열을 변경한다.
console.log(arr.reverse());  // [ 'banana', 'apple', 'melon', 'kiwi' ]
console.log(arr);            // [ 'banana', 'apple', 'melon', 'kiwi' ]

 


 

 

고차 함수(1) 알아보는 시간이었습니다.

틀린 내용은 댓글로 알려주시면 감사하겠습니다.

 

 

반응형
profile

Space

@Space_zero

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!