본문 바로가기

개념/javaScript

[javaScript]내장 고차 함수 filter,map,reduce

javaScript의 함수는 일급객체

뭔가 간지나는 용어 일급객체, 뭐 대단한 의미는 없고, 자바스크립트에서 함수는 아래와 같이 세가지가 특이하기 때문에, '일급객체' 라고 큭별히 분류한다.

  • 변수에 할당(assignment) 할 수 있다.
  • 다른 함수의 전달인자(argument)로 전달될 수 있다.
  • 다른 함수의 결과로서 리턴될 수 있다.

고차 함수란

고차함수란 함수를 전달인자로 받을 수 있거나 함수를 리턴할 수 있는 함수를 말한다.

-하나라도 만족하면 고차함수-

✔️ 함수를 전달인자로 받는가?

✔️함수를 리턴할 수 있는가?


콜백함수란

 

콜백함수란 다른 함수의 전달인자로 전달되는 함수를 말한다.

 

따라서 고차함수는 콜백함수를 전달받는 함수이다. (콜백함수는 실행될수도, 안될수도, 특정 조건에서만 실행될 수도 있다.)

 


커링함수란

 

함수를 리턴하는 함수를 말한다.

 

클로저와 거의 개념이 유사하다. 클로저가 좁은 의미론, 함수 내부에서 외부함수의 변수를 참조할 수 있는 내부함수를 말하는 반면, 커링함수는 그 외부 함수를 말한다는 점에서 면밀히 따지자면 조금 다르지만, 어쨌든, 큰범위에서 흔히 클로저는 외부함수 내부함수 포함한 환경을 부르므로, 커링함수와 똑같다고 생각해도 된다.

 

컬리함수는 클로저에 비해 자주 쓰이지 않는 용어라고 한다.

 

쨌든 커링함수는 함수를 리턴하기 때문에, 고차함수에 포함된다고 볼 수 있다.

 


javaScript의 내장고차 함수:  map, filter, reduce

 

javaScript엔 우리가 만들지 않아도 우리의 편리함을 위해 기본적으로 들어가 있는 내장 고차 함수가 여러가지 있다. 그 중 배열 메서드 중 일부가 대표적인 고차 함수이다. (물론 고차함수는 배열 메서드 말고도 많지만, 일단 배열에 관련된 것 먼저 학습한다.)

 

-자바스크립트의 배열 내장 고차함수 -

  • map
  • filter
  • reduce

배열 내장고차함수 1 : filter

배열의 요소를 순회해서 원하는 값만 필터링된 새 배열을 반환한다.

이때 filtering할 함수를 인자로 전달해주므로, '함수를 전달받는 함수',즉 고차함수이다.

 

-filter의 작동 순서-

  1. 배열의 각 요소를 돌며
  2. 특정 논리(전달인자 함수)에 따르면, 사실(true)일 때
  3. 따로 분류(filter)해서 최종적으로 새로운 배열 만듬
const isOdd = function (num) {
  return num % 2 === 1; 
};

let arr = [1, 2, 3, 4, 5, 6, 7];
let output = arr.filter(isOdd); 
//arr의 요소 하나하나씩 돌면서
//isOdd에 인자로 요소를 전달해 true가 리턴된다면
//해당 arr요소를 따로 분류
console.log(output);
//[1,3,5,7]

🛑이때 filter함수 내부에 꼭 boolean값이 리턴될 수 있도록 한다.


배열 내장고차함수 2: map

 

배열의 모든 요소들에게 동일한 행위를 한 결과값을 묶어 반환한다.

이때 요소들에게 적용할 함수를 인자로 전달하기때문에 고차함수이며, 기존 배열을 수정하지않고 새 배열을 반환하는 immutable한 함수이다.

 

-map의 작동 순서-

  1. 배열의 각 요소를 돌며
  2. 특정 논리(전달인자 함수)에 의해 
  3. 가공된 새로운 값이 요소로 순서대로 들어가 새로운 배열 만듬
const double = function (num) {
  return num * 2;
};

let arr = [1, 2, 3, 4, 5, 6, 7];
let output = arr.map(double);
//arr의 요소를 하나하나 돌면서 
//double함수를 요소에 실행시켜 나온 값을
//모아서 새로운 배열 output으로 할당
console.log(output); 
//[2, 4, 6, 8, 10, 12, 14]

배열 내장고차함수 3 : reduce

배열의 각 요소를 특정방법(함수)에 따라 원하는 하나의 형태로 응축(reduction)한다.

초기값을 정하지 않으면 배열의 첫번째 요소가 초기값이 된다.

문자열이나 숫자를 합치거나 뺄 수도 있고, 대소동등을 비교할 수도 있고, 배열이외에 다른 형태로도 만들 수 있다.

 

-reduce의 작동 순서-

  1. 배열의 각 요소를 돌며
  2. 특정 방법(전달인자 함수)에 따라서
  3. 원하는 형태로 누적 가공해서(응축)
  4. 하나의 값으로 만듬

여기서 초기값을 어떻게 설정하냐에 따라 값이 달라지는 모습을 아래의 두 코드로 비교해볼 수 있다.

🛑보통 배열의 응축된 값(accumulation) 현재요소(current)을 줄여서 acc와 cur로 변수를 설정한다.

let arr = [1,2,3,4,5]

arr.reduce((acc,cur)=>{
	return acc + cur
})
//15

//설명
//초기값을 설정하지 않으면 배열의 첫번째 요소가 acc,
//두번째 요소부터 cur이 시작

//1. acc:1 cur:2 return 1+2=3
//2. acc:3 cur:3 return 3+3=6
//3. acc:6 cur:4 return 6+4=10
//4. acc:10 cur:5 return 10+5=15

 

let arr = [1,2,3,4,5]

arr.reduce((acc,cur)=>{
	return acc + cur
},10)
//25

//설명
//초기값을 10으로 설정, 첫 acc가 10으로 시작,
//두번째 요소부터 cur이 시작

//1. acc:10 cur:1 return 10+1=11
//2. acc:11 cur:2 return 11+2=13
//3. acc:13 cur:3 return 13+3=16
//4. acc:16 cur:4 return 16+4=20
//5. acc:20 cur:5 return 20+5=25
//25

🛑초기값이 없을 경우 빈배열이 들어왔을 경우 에러가 뜨는 경우가 있으므로, (비록 안넣었을때와 평소는 차이가 없을지라도 값에 따라) 0이나 빈배열, 빈 문자열이라도 웬만하면 초기값을 줘야한다.

 


이제 고차함수 파트에서 헷갈렸던 문제 몇가지를 첨부해본다!


reduce 문제

 

다음은 reduce가 일반적으로 연산 + - * 등만 생각하기 쉬운데,

부등호를 통한 비교로 for문없이 최대값, 혹은 최소 값을 남기는 데에도 사용할 수 있다는 것을 보여주는 예시 문제이다.

 

Q. 문자열을 요소로 갖는 배열을 입력받아 배열에서 가장 긴 문자열을 리턴해야 합니다
  • 반복문(for, while) 사용은 금지됩니다.
  • 가장 긴 문자열이 중복이 될 경우, 앞 쪽에 있는 요소를 리턴해야 합니다.
  • 빈 배열을 입력받은 경우, 빈 문자열을 리턴해야 합니다.
입출력예시
let output = getLongestElement(['one', 'two', 'three']);
console.log(output); // --> 'three'

output = getLongestElement(['one', 'two', 'wow']);
console.log(output); // --> 'one'

 

A.
function getLongestElement(arr) {
  return arr.reduce(function (a, b) {
    if (a.length >= b.length) {
      return a;
    } else {
      return b;
    }
  }, '');
}

filter + map + reduce 종합문제 01

 

Q. 학생의 정보가 담긴 객체를 요소로 갖는 배열을 입력받아 아래 조건에 맞게 변형된 배열을 리턴해야 합니다.
남학생들의 정보는 리턴하는 배열에서 제외합니다.'grades' 속성값은 평균값(number 타입)으로 바꿉니다.
  • 반복문(for, while) 사용은 금지됩니다.
  • 배열을 리턴해야 합니다.
  • 배열로 되어 있는 학생들의 성적을 number 타입의 평균값으로 변형해야 합니다.
입출력 예시
let studentList = [
  {
    name: 'Anna',
    gender: 'female',
    grades: [4.5, 3.5, 4],
  },
  {
    name: 'Dennis',
    gender: 'male',
    country: 'Germany',
    grades: [5, 1.5, 4],
  },
  {
    name: 'Martha',
    gender: 'female',
    grades: [5, 4, 4, 3],
  },
  {
    name: 'Brock',
    gender: 'male',
    grades: [4, 3, 2],
  },
];

let output = studentReports(studentList);

console.log(output); // -->
[
  { name: 'Anna', gender: 'female', grades: 4 },
  { name: 'Martha', gender: 'female', grades: 4 },
];
A
function studentReports(students) {
  const femaleArr = students.filter((obj)=>{
    return obj.gender === 'female'
  })

  return femaleArr.map((femaleObj)=>{
    const sum = femaleObj.grades.reduce((acc,cur)=>{
      return acc + cur
    },0)
    const avg = sum / femaleObj.grades.length
    femaleObj.grades = avg
    return femaleObj
  })
}

filter + map + reduce 종합문제 02

Q. 2차원 배열(배열을 요소로 갖는 배열)을 입력받아 모든 수(number)의 합을 리턴해야 합니다.
  • 합을 구할 때 number 타입만 고려해야 합니다.
입출력예시
let output = sumOfArraysInArray([
  [1, 2],
  [undefined, 4, '5'],
  [9, 'hello'],
]);
console.log(output); // --> 16
A
function sumOfArraysInArray(arr) {
  const mapArr = arr.map((innerArr)=>{
    return innerArr.filter((el)=>{
      return typeof el === 'number'
    })
  })

  const sumArr = mapArr.map((arr)=>{
    return arr.reduce((acc,cur)=>{
      return acc + cur
    },0)
  })

  return sumArr.reduce((acc,map)=>{
    return acc + map
  })
}

reduce map filter 문제에서 가장 빼먹기 쉬운것

🛑 return을 다 넣어줬나 확인을 한다!!

 

맞는 것 같은데 안되네? 싶으면 return을 확인해보자!