[JavaScript] 배열 고차함수

Rachaen·2023년 3월 14일

배열의 고차함수

  • higher order function 메서드
  • 다른 함수(콜백함수)를 인자로 받음
  • 함수형 프로그래밍
    • 변수 사용 없이 순회 작업들을 코딩

forEach

  • 각 요소를 인자로 콜백함수 실행
    • for문의 좋은 대체제
    • 단점: 예외를 던지지 않으면 종료할 수 없음 (break, continue 사용 불가)
  • 인자들
    • 콜백함수 - 인자: (현재 값, 현재 값의 인덱스, 해당 배열)
    • thisArg
const arr = [1, 2, 3, 4, 5];

const result = arr.forEach(itm => {
  console.log(itm);
});	// 1 2 3 4 5

// 💡 결과로는 undefined 반환 => 실행 자체를 위한 메서드
console.log('반환값:', result);	// 반환값: undefined
const arr = [1, 2, 3, 4, 5];

// 현존하는 함수를 인자로 - 💡 결과 살펴볼 것
arr.forEach(console.log);
/*
현재값, 인덱스, 배열
1 0 [1, 2, 3, 4, 5]
2 1 [1, 2, 3, 4, 5]
3 2 [1, 2, 3, 4, 5]
4 3 [1, 2, 3, 4, 5]
5 4 [1, 2, 3, 4, 5]
*/
const arr = [10, 20, 30, 40, 50];

// 콜백함수의 인자가 둘일 때
arr.forEach((itm, idx) => {
  console.log(itm, idx);
});
/*
10 0
20 1
30 2
40 3
50 4
*/
const logWithIndex = (itm, idx) => {
  console.log(`[${idx}]: ${itm}`);
}

arr.forEach(logWithIndex);
/*
[0]: 10
[1]: 20
[2]: 30
[3]: 40
[4]: 50
*/
const arr = [1, 2, 3, 4, 5];

// 콜백함수의 인자가 셋일 때
arr.forEach((itm, idx, arr) => {
  // 💡 세 번째 인자는 원본 배열의 참조임
  arr[idx]++;
  console.log(itm);
});	// 1 2 3 4 5
// itm은 복사해온 값 arr은 실제 배열

// 이런 식으로 원본을 수정해버릴 수 있음
console.log(arr);	//  [2, 3, 4, 5, 6]

map

  • 각 요소를 주어진 콜백함수로 처리한 새 배열 반환
  • 인자들
    • 콜백함수 - 인자: (현재값, 현재의 인덱스, 해당 배열)
    • thisArg
const orgArr = [1, 2, 3, 4, 5];

// ⭐️ 각 콜백함수는 어떤 값을 반환해야 함
const arr1 = orgArr.map(i => i + 1);
const arr2 = orgArr.map(i => i * i);
const arr3 = orgArr.map(i => i % 2 ? '홀수' : '짝수');

console.log(arr1);	// [2, 3, 4, 5, 6]
console.log(arr2);	// [1, 4, 9, 16, 25]
console.log(arr3);	// ['홀수', '짝수', '홀수', '짝수', '홀수']
const orgArr = [
  { name: '사과', cat: '과일', price: 3000 },
  { name: '오이', cat: '채소', price: 1500 },
  { name: '당근', cat: '채소', price: 2000 },
  { name: '살구', cat: '과일', price: 2500 },
  { name: '피망', cat: '채소', price: 2500 },
  { name: '딸기', cat: '과일', price: 5000 }
];


const arr1 = orgArr.map(itm => {
  // 💡 블록 안에서는 return 문 필요함
  return {
    name: itm.name,
    cat: itm.cat
  }
});

console.log(arr1);
/*
{name: '사과', cat: '과일'}
{name: '오이', cat: '채소'}
{name: '당근', cat: '채소'}
{name: '살구', cat: '과일'}
{name: '피망', cat: '채소'}
{name: '딸기', cat: '과일'}
*/
// 디스트럭쳐링 사용 (편의에 따라 적절히)
const arr2 = orgArr.map(({name, cat}) => {
  return { name, cat }
});

console.log(arr2);
/*
{name: '사과', cat: '과일'}
{name: '오이', cat: '채소'}
{name: '당근', cat: '채소'}
{name: '살구', cat: '과일'}
{name: '피망', cat: '채소'}
{name: '딸기', cat: '과일'}
*/
const joined = orgArr
.map(({name, cat, price}, idx) => {
  return `${idx + 1}: [${cat[0]}] ${name}: ${price}`
})
.join('\n - - - - - - - - - \n');

console.log(joined);
/*
1: [과] 사과: 3000원
 - - - - - - - - - 
2: [채] 오이: 1500원
 - - - - - - - - - 
3: [채] 당근: 2000원
 - - - - - - - - - 
4: [과] 살구: 2500원
 - - - - - - - - - 
5: [채] 피망: 2500원
 - - - - - - - - - 
6: [과] 딸기: 5000원
*/

3. find, findLast, findIndex, findLastIndex

  • 주어진 기준으로 검색
  • 콜백함수에 인자로 넣었을 때 true를 반환하는
    • find: 첫 번째 값 반환
    • findLast: 마지막 값 반환
    • findIndex: 첫 번째 값의 인덱스 반환
    • findLastIndex: 마지막 값의 반환
  • 공통 인자들
    • 콜백함수 - 인자: (현재 값, 현재 값의 인덱스, 해당 배열)
    • thisArg
const arr = [1, 2, '삼', 4, 5, 6, '칠', 8, 9];

const isString = i => typeof i === 'string';
const isBoolean = i => typeof i === 'boolean';

console.log(
  arr.find(isString),
  arr.findLast(isString),
  arr.findIndex(isString),
  arr.findLastIndex(isString)
);	// 삼 칠 2 6
// 없을 시 값은 undefined, 인덱스는 -1 반환
console.log(
  arr.find(isBoolean),
  arr.findLast(isBoolean),
  arr.findIndex(isBoolean),
  arr.findLastIndex(isBoolean)
);	// undefined undefined -1 -1
const arr = [
  { name: '사과', cat: '과일', price: 3000 },
  { name: '오이', cat: '채소', price: 1500 },
  { name: '당근', cat: '채소', price: 2000 },
  { name: '살구', cat: '과일', price: 2500 },
  { name: '피망', cat: '채소', price: 3500 },
  { name: '딸기', cat: '과일', price: 5000 }
];

const isCheapFruit = i => {
  return i.cat === '과일' && i.price < 3000;
}

console.log(
  arr.find(({cat}) => cat === '채소').name,
  arr.findLast(isCheapFruit).name
);	// 오이 살구

4. some, every

  • 어떤/모든 요소가 기준을 충족하는지 확인
  • 콜백함수에 인자로 넣은
    • some: 요소들 중 하나라도 true를 반환하는가 여부 반환
    • every: 모든 요소가 true를 반환하는가 여부 반환
  • 인자들
    • 콜백함수 - 인자: (현재 값, 현재 값의 인덱스, 해당 배열)
    • thisArg
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];

console.log(
  arr.some(i => i % 2),
  arr.every(i => i % 2),
  arr.some(i => i < 0),
  arr.every(i => i < 10)
);	// true false false true
const arr = [
  { name: '사과', cat: '과일', price: 3000 },
  { name: '오이', cat: '채소', price: 1500 },
  { name: '당근', cat: '채소', price: 2000 },
  { name: '살구', cat: '과일', price: 2500 },
  { name: '피망', cat: '채소', price: 3500 },
  { name: '딸기', cat: '과일', price: 5000 }
];

const isCheapVege = i => {
  return i.cat === '채소' && i.price < 2000;
}
const isPlant = ({cat}) => {
  return ['과일', '채소'].includes(cat);
}

console.log(
  arr.some(isCheapVege),
  arr.every(isCheapVege),
  arr.some(isPlant),
  arr.every(isPlant)
);	// true false true true

5. filter

  • 주어진 기준을 충족하는 요소들로 새 배열 만들어 반환
  • 원본 배열을 수정하지 않음
  • 인자들
    • 콜백함수 - 인자: (현재 값, 현재 값의 인덱스, 해당 배열)
    • thisArg
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];

const odds = arr.filter(i => i % 2);
const evens = arr.filter(i => !(i % 2));
const largerThan3 = arr.filter(i => i > 3);

console.log(odds);	// [1, 3, 5, 7, 9]
console.log(evens);	// [2, 4, 6, 8]
console.log(largerThan3);	// [4, 5, 6, 7, 8, 9]
const arr = [
  { name: '사과', cat: '과일', price: 3000 },
  { name: '오이', cat: '채소', price: 1500 },
  { name: '당근', cat: '채소', price: 2000 },
  { name: '살구', cat: '과일', price: 2500 },
  { name: '피망', cat: '채소', price: 3500 },
  { name: '딸기', cat: '과일', price: 5000 }
];

console.log(
  '과일 목록:',
  arr
  .filter(({cat}) => cat === '과일')
  .map(({name}) => name)
  .join(', ')
);	// 과일 목록: 사과, 살구, 딸기

6. reduce, reduceRight

  • 주어진 콜백함수에 따라 값을 접어 나감
  • 인자들
    • 콜백함수 - 인자: (이전값, 현재값, 현재 인덱스, 해당 배열)
    • 초기화 값
  • 초기화 값이 없을 때는 첫 번째와 두 번째 값부터
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];

console.log(
  arr.reduce((prev, curr, idx) => {
    console.log(`p: ${prev}, c: ${curr}, i: ${idx}`);
    return prev + curr;
  })
);
/*
p: 1, c: 2, i: 1
p: 3, c: 3, i: 2
p: 6, c: 4, i: 3
p: 10, c: 5, i: 4
p: 15, c: 6, i: 5
p: 21, c: 7, i: 6
p: 28, c: 8, i: 7 
p: 36, c: 9, i: 8
45
*/
  • 초기화 값이 있을 때 => i가 0부터 시작
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];

console.log(
  arr.reduce((prev, curr, idx) => {
    console.log(`p: ${prev}, c: ${curr}, i: ${idx}`);
    return prev + curr;
  }, 10)
);
/*
p: 10, c: 1, i: 0
p: 11, c: 2, i: 1
p: 13, c: 3, i: 2
p: 16, c: 4, i: 3
p: 20, c: 5, i: 4
p: 25, c: 6, i: 5
p: 31, c: 7, i: 6
p: 38, c: 8, i: 7
p: 46, c: 9, i: 8
55
*/
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];

// 곱해나가기
console.log(
  arr.reduce((prev, curr, idx) => {
    console.log(`p: ${prev}, c: ${curr}, i: ${idx}`);
    return prev * curr;
  })
);	// 362880
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];

// 더하기와 빼기 반복
console.log(
  arr.reduce((prev, curr, idx) => {
    console.log(`p: ${prev}, c: ${curr}, i: ${idx}`);
    return idx % 2 ? prev + curr : prev - curr;
  })
);
/*
p: 1, c: 2, i: 1
p: 3, c: 3, i: 2
p: 0, c: 4, i: 3
p: 4, c: 5, i: 4
p: -1, c: 6, i: 5
p: 5, c: 7, i: 6
p: -2, c: 8, i: 7
p: 6, c: 9, i: 8
-3
*/
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];

// 홀수와 짝수 갯수
console.log(
  arr.reduce((prev, curr) => {
    return {
      odd: prev.odd + curr % 2,
      even: prev.even + (1 - (curr % 2)),
    }
  }, { odd: 0, even: 0 })
);	// {odd: 5, even: 4}
  • reduce vs reduceRight
const arr = ['가', '나', '다', '라', '마', '바', '사'];

console.log(
  arr.reduce((prev, curr, idx) => {
    console.log(`p: ${prev}, c: ${curr}, i: ${idx}`);
    return prev + curr;
  })
);
/*
p: 가, c: 나, i: 1
p: 가나, c: 다, i: 2
p: 가나다, c: 라, i: 3
p: 가나다라, c: 마, i: 4
p: 가나다라마, c: 바, i: 5
p: 가나다라마바, c: 사, i: 6
가나다라마바사
*/
console.log(
  arr.reduceRight((prev, curr, idx) => {
    console.log(`p: ${prev}, c: ${curr}, i: ${idx}`);
    return prev + curr;
  })
);
/*
p: 사, c: 바, i: 5
p: 사바, c: 마, i: 4
p: 사바마, c: 라, i: 3
p: 사바마라, c: 다, i: 2
p: 사바마라다, c: 나, i: 1
p: 사바마라다나, c: 가, i: 0
사바마라다나가
*/
  • reduceRight은 인덱스도 거꾸로 진행됨
const arr = [
  { name: '사과', cat: '과일', price: 3000 },
  { name: '오이', cat: '채소', price: 1500 },
  { name: '당근', cat: '채소', price: 2000 },
  { name: '살구', cat: '과일', price: 2500 },
  { name: '피망', cat: '채소', price: 3500 },
  { name: '딸기', cat: '과일', price: 5000 }
];

['과일', '채소'].forEach(category => {
  console.log(
    `${category}의 가격의 합:`,
    arr
    .filter(({cat}) => cat === category)
    .map(({price}) => price)
    .reduce((prev, curr) => prev + curr)
  );
});
// 과일의 가격의 합: 10500 채소의 가격의 합: 7000
  • 배열 메서드와 체이닝 없이 짰다면
    • 중간 과정을 저장하기 위한 변수 또는 내용이 바뀌는 참조형 데이터들이 사용되었을 것
    • 함수형 프로그래밍: 변수들을 코드에서 감추어 부수효과로 인한 문제 방지

sort

  • 배열을 (주어진 기준대로) 정렬
  • 배열 자체의 순서를 바꿈 - 원본 수정
  • 해당 배열을 반환
  • 인자들:
    • 콜백함수(필수아님) - 인자: (앞의 값, 뒤의 값)
  1. 인자가 없을 시
const arr = ['라', '사', '다', '가', '바', '마', '나'];

arr.sort();

console.log(arr);	// ['가', '나', '다', '라', '마', '바', '사']

let randomWord = 'DBKGICAHFEJ';

console.log(
  randomWord
  .split('')
  .sort()
  // .reverse()
  .join('')
);	// ABCDEFGHIJK

console.log(randomWord);	// DBKGICAHFEJ => split으로 별개의 배열에 적용한 것이기 때문에 원본을 해치지 않음
// ⚠️ 숫자일 시 문제가 생김
const arr = [1, 2, 30, 400, 10, 100, 1000];
console.log(arr.sort());	// [1, 10, 100, 1000, 2, 30, 400]
  • 숫자를 문자열로 암묵적 변환하여 오름차순 정렬
  • 정확한 정렬을 위해 콜백 함수 사용
    • 두 인자 ab: 인접한 두 요소
    • 0보다 큰 값 반환: b를 앞으로 - 순서 뒤집음
    • 0 반환: 순서 유지 - ECMAScript 표준은 아니므로 환경마다 다를 수 있음
    • 0보다 작은 값 반환: a를 앞으로 - 사실상 순서 유지
const arr = [1, 2, 30, 400, 10, 100, 1000];

console.log(
  arr.sort((a, b) => a - b)
);	// [1, 2, 10, 30, 100, 400, 1000]
console.log(
  arr.sort((a, b) => b - a)
);	// [1000, 400, 100, 30, 10, 2, 1]
  • 숫자가 아닐 경우 직접 반환값을 명시
// NaN을 반환하므로 콜백에 사용 불가
console.log('A' - 'B');

const arr = ['F', 'E', 'I', 'A', 'H', 'C', 'D', 'J', 'G', 'B'];

console.log(
  arr.sort((a, b) => a > b ? 1 : -1)
);	// ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
  • 둘 이상의 기준 사용
const arr = [
  { name: '사과', cat: '과일', price: 3000 },
  { name: '오이', cat: '채소', price: 1500 },
  { name: '당근', cat: '채소', price: 2000 },
  { name: '살구', cat: '과일', price: 2500 },
  { name: '피망', cat: '채소', price: 3500 },
  { name: '딸기', cat: '과일', price: 5000 }
];

console.log(
  arr
  .sort((a, b) => {
    if (a.cat !== b.cat) {
      return a.cat > b.cat ? 1 : -1;
    }
    return a.price > b.price ? 1 : -1;
  })
  .map(({name, cat, price}, idx) => {
    return `${idx + 1}: [${cat[0]}] ${name}: ${price}`
  })
  .join('\n - - - - - - - - - \n')
);
/*
1: [과] 살구: 2500원
 - - - - - - - - - 
2: [과] 사과: 3000원
 - - - - - - - - - 
3: [과] 딸기: 5000원
 - - - - - - - - - 
4: [채] 오이: 1500원
 - - - - - - - - - 
5: [채] 당근: 2000원
 - - - - - - - - - 
6: [채] 피망: 3500원
*/

7. flatMap

  • map한 다음 flat 매핑해서 펼침
  • 인자들
    • 콜백함수 - 인자: (현재 값, 현재 값의 인덱스, 해당 배열)
    • thisArg
const arr = [1, 2, 3, 4, 5];

console.log(
  arr.flatMap(i => i)
);	// [1, 2, 3, 4, 5]
console.log(
  arr.flatMap(i => [i, i, i])
);	// [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5]
console.log(
  arr.flatMap(i => [i * 10, i * 100, i * 1000])
);	// [10, 100, 1000, 20, 200, 2000, 30, 300, 3000, 40, 400, 4000, 50, 500, 5000]

const word = '하나 둘 셋 넷 다섯 여섯 일곱 여덟 아홉 열';

console.log(
  word
  .split(' ')
  .flatMap(i => i.split(''))
);	// ['하', '나', '둘', '셋', '넷', '다', '섯', '여', '섯', '일', '곱', '여', '덟', '아', '홉', '열']

고차함수 메서드들
MDN Array

profile
개발을 잘하자!

0개의 댓글