ECMAScript 5에서 다른 배열 메서드들과 함께 소개된 reduce()는 배열을 단일 값으로 변환하는 유일하고 강력한 방법을 제공합니다.

이 글에서는 reduce() 메서드에 대해 알아보겠습니다. 이는 무엇인지, 문법은 무엇인지 이해하고, 이를 효과적으로 사용할 수 있는 사용 사례를 최종적으로 살펴보게 됩니다.

모든 소스 코드는 여기서 얻을 수 있습니다.

목차

  • reduce()의 기본 이해하기
  • 마치 점토를 조각하는 것처럼 생각하기
  • reduce()의 사용 사례
  • 결론

reduce()의 기본 이해하기

본질적으로 reduce()는 배열의 각 요소를 순회하며 사용자 정의 함수(적절하게 “리듀서”라고 함)를 현재 요소와 누산기 값 모두에 적용합니다.

이 누산기는 제공하는 초기 값(또는 첫 번째 배열 요소로 기본 설정)으로 시작하고, 각 반복에서 리듀서의 반환 값으로 업데이트됩니다.

결국, 누산기의 최종 상태가 reduce()에 의해 반환된 단일 값이 됩니다.

마치 점토를 조각하는 것처럼 생각하기

점토 조각을 상상해보세요. 덩어리로 시작하여 반복적으로 압력과 방향을 적용하여 원하는 형태로 변형시킵니다.

비슷하게, reduce()는 초기 값을 (점토) 취하고, 사용자 정의 리듀서 함수(조각하는 손)를 통해 최종 결과로 형태를 변형시킵니다.

reduce()의 사용 사례

이제 reduce()가 빛나는 몇 가지 시나리오를 살펴보겠습니다:

총합 계산하기

시나리오: 제품을 나타내는 객체 배열이 있고, 모든 제품의 총 가격을 계산하려고 합니다.

전통적인 루프 사용 접근 방식:

const products = [
  { name: "Shirt", price: 20 },
  { name: "Shoes", price: 50 },
  { name: "Hat", price: 15 }
];

// totalPrice를 0으로 초기화
let totalPrice = 0;

// 각 제품을 순회하며 totalPrice에 가격을 더함
for (const product of products) {
  totalPrice += product.price;
}

console.log("Total price (loop):", totalPrice); // 출력: Total price (loop): 85

전통적인 방법은 totalPrice 변수를 0으로 초기화한 다음 for…of 루프를 사용하여 products 배열의 각 제품을 반복합니다.

루프 내에서 현재 제품의 가격 속성을 totalPrice에 추가합니다.

모든 제품을 반복한 후 최종 totalPrice(85)가 콘솔에 출력됩니다.

reduce() 사용:

const products = [
  { name: "Shirt", price: 20 },
  { name: "Shoes", price: 50 },
  { name: "Hat", price: 15 }
];

// totalPrice를 위한 초기 값 0으로 reduce() 사용
const totalPriceReduce = products.reduce((sum, product) => sum + product.price, 0);

console.log("Total price (reduce):", totalPriceReduce); // 출력: Total price (reduce): 85

최소값 또는 최대값 찾기

시나리오: 온도 배열이 있고 최고 및 최저 온도를 찾고 싶습니다.

일반적인 접근 방식(루프 사용):

const temperatures = [25, 18, 32, 20, 15];

// maxTemp와 minTemp를 현실과 동떨어진 값으로 초기화
let maxTemp = -Infinity;
let minTemp = Infinity;

// 각 온도를 순회하며 maxTemp와 minTemp 갱신
for (const temp of temperatures) {
  maxTemp = Math.max(maxTemp, temp);
  minTemp = Math.min(minTemp, temp);
}

console.log("최고 온도(루프):", maxTemp); // 출력: 최고 온도(루프): 32
console.log("최저 온도(루프):", minTemp); // 출력: 최저 온도(루프): 15

일반적인 접근 방식은 maxTemp를 음의 무한대로, minTemp를 양의 무한대로 초기화하여 첫 번째로 마주친 온도로 갱신되도록 합니다.

그런 다음 temperatures 배열의 각 온도를 for…of 루프를 사용하여 반복합니다. 루프 내부에서 현재의 maxTemp와 현재 온도를 비교하기 위해 Math.max()를 사용하고 필요한 경우 maxTemp를 갱신합니다.

비슷하게 Math.min()을 사용하여 현재의 minTemp를 비교하고 필요한 경우 갱신합니다. 모든 온도를 반복한 후 최종 maxTemp(32)와 minTemp(15)가 콘솔에 출력됩니다.

reduce() 사용:

const temperatures = [25, 18, 32, 20, 15];

// reduce()를 사용하여 초기값을 -Infinity와 Infinity로 설정
const maxTempReduce = temperatures.reduce((max, temp) => Math.max(max, temp), -Infinity);
const minTempReduce = temperatures.reduce((min, temp) => Math.min(min, temp), Infinity);

console.log("최고 온도(reduce):", maxTempReduce); // 출력: 최고 온도(reduce): 32
console.log("최저 온도(reduce):", minTempReduce); // 출력: 최저 온도(reduce): 15

reduce() 메소드는 콜백 함수와 초기값(이 경우 음의 무한대와 양의 무한대)을 받습니다. 콜백 함수는 두 개의 인자를 받습니다: max 또는 min으로, 초기값으로 시작하는 누적자, 그리고 temp로 현재 온도입니다.

콜백 함수 내부에서 현재 온도와 누적자를 비교하기 위해 Math.max() 또는 Math.min()을 사용하고 그에 따라 갱신합니다.

모든 온도를 반복한 후 최종 최대 및 최소 온도가 반환됩니다.

비교:

두 접근법 모두 같은 결과를 달성하지만, reduce()는 최대 및 최소 값을 찾는 더 간결하고 기능적인 방법을 제공합니다. 이는 콜백 함수 내에서 값을 직접 비교하고 갱신하는 누적자 개념을 활용합니다.

복잡한 객체 구축하기

시나리오: 학생 배열이 있고 하나의 객체로 그룹화된 학생들을 과목별로 분류하고 싶습니다.

일반적인 접근 방식(루프 사용):

const students = [
  { name: "Alice", age: 25, subject: "Math" },
  { name: "Bob", age: 30, subject: "Science" },
  { name: "Charlie", age: 28, subject: "History" },
];

// 과목별 그룹을 저장할 빈 객체 초기화
const subjectMap = {};

// 각 학생을 순회하며 각각의 과목 그룹에 추가
for (const student of students) {
  const subject = student.subject;
  if (!subjectMap[subject]) {
    subjectMap[subject] = [];
  }
  subjectMap[subject].push(student);
}

console.log("과목 맵(루프):", subjectMap); // 출력: { Math: [...], Science: [...], History: [...] }

이 방식은 과목별 그룹화된 학생들을 저장하기 위해 빈 객체 subjectMap을 초기화합니다. 그런 다음 students 배열의 각 학생을 for…of 루프를 사용하여 반복합니다.

루프 내부에서 학생의 과목을 검색합니다. subjectMap에서 해당 과목의 키가 존재하지 않으면 해당 과목을 위한 새 배열이 생성됩니다. 현재 학생 객체가 subjectMap 내에 해당 과목 배열로 push됩니다.

모든 학생을 반복한 후 최종 subjectMap 객체는 과목별로 그룹화된 학생들을 포함합니다.

reduce() 사용:

const students = [
  { name: "Alice", age: 25, subject: "Math" },
  { name: "Bob", age: 30, subject: "Science" },
  { name: "Charlie", age: 28, subject: "History" },
];

// 과목 맵 객체를 구축하기 위해 reduce() 사용
const subjectMapReduce = students.reduce((map, student) => {
  const subject = student.subject;
  map[subject] = map[subject] || [];
  map[subject].push(student);
  return map;
}, {});

console.log("과목 맵(reduce):", subjectMapReduce); // 출력: { Math: [...], Science: [...], History: [...] }

reduce() 메소드는 콜백 함수와 초기값(이 경우 빈 객체)을 받습니다. 콜백 함수는 두 개의 인자를 받습니다: map으로, 초기 빈 객체로 시작하는 누적자, 그리고 student로 현재 학생 객체입니다.

콜백 함수 내부에서 현재 학생의 과목 속성을 검색합니다. map에서 해당 과목이 존재하지 않으면 그에 대한 빈 배열이 생성됩니다. 현재 학생 객체가 map 내에 해당 과목 배열로 push됩니다.

업데이트된 map 객체가 다음 반복을 위한 새로운 누적자로 반환됩니다. 모든 학생을 반복한 후 최종 map 객체는 과목별로 그룹화된 학생들을 포함합니다.

비교: 두 접근법은 같은 결과를 달성하지만, reduce()는 객체를 구축하는 데 더 간결하고 함수적인 방법을 제공합니다. 이는 누산기 개념을 활용하여 콜백 함수 내에서 직접 subjectMap을 구축합니다.

다차원 배열 평탄화

시나리오: 중첩된 배열 구조를 가지고 있고 단일 레벨 배열을 생성하고 싶습니다.

전통적인 접근법:

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

// 평탄화된 요소를 저장하기 위한 빈 배열 초기화
const flatArray = [];

// 각 하위 배열을 순회하며 flatArray에 그 요소를 추가
for (const subArray of multiArray) {
  for (const element of subArray) {
    flatArray.push(element);
  }
}

console.log("Flat array (loop):", flatArray); // 출력: [1, 2, 3, 4, 5]

이 접근법은 평탄화된 요소를 저장하기 위해 빈 배열 flatArray를 초기화합니다. 그 다음 multiArray의 각 하위 배열을 중첩된 for…of 루프를 이용해 순회합니다.

내부 루프에서 현재 하위 배열의 각 요소를 flatArray에 추가합니다. 모든 하위 배열을 순회한 후 최종적인 flatArray는 단일 레벨로 모든 요소를 포함합니다.

reduce() 사용하기:

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

// reduce()를 사용하여 다차원 배열을 평탄화
const flatArrayReduce = multiArray.reduce((accumulator, currentArray) => {
  return accumulator.concat(currentArray);
}, []);

console.log("Flat array (reduce):", flatArrayReduce); // 출력: [1, 2, 3, 4, 5]

reduce() 메서드는 콜백 함수와 초기값(이 경우 빈 배열 [])을 인자로 받습니다. 콜백 함수는 누산기(초기 빈 배열로 시작)와 처리 중인 현재 하위 배열인 두 인자를 받습니다.

콜백 함수 내에서, concat() 메서드를 사용하여 현재 배열을 누산기와 연결하여 배열을 평탄화합니다. concat()의 결과는 다음 반복을 위한 새로운 누산기가 됩니다.

모든 하위 배열을 순회한 후, 최종 누산기는 다차원 배열의 모든 요소를 단일 레벨로 평탄화한 것을 포함합니다.

비교: 두 접근법 모두 배열을 평탄화하는 같은 결과를 달성하지만, reduce()는 더 우아하고 간결한 솔루션을 제공합니다. 이는 누산기 개념을 활용하여 단계적으로 평탄화된 배열을 구축함으로써 중첩된 루프와 수동적인 요소 추가의 필요성을 피합니다.

결론

요약하자면, 자바스크립트의 reduce() 메서드는 배열을 단일 값이나 복잡한 데이터 구조로 변환하는 데 간결하고 강력한 방법을 제공합니다.

커스터마이징 가능한 리듀서 함수와 선택적 초기값을 통해 유연성을 제공함으로써, reduce()는 합계 계산, 극값 찾기, 객체 그룹화, 배열 평탄화 등의 일반적인 작업을 단순화합니다.

reduce()를 이해하는 것은 개발자가 더 깨끗하고 효율적인 코드를 작성하게 하며, 자바스크립트 프로젝트에서 데이터 조작의 새로운 가능성을 열어줍니다.

다른 reduce 관련 제 블로그글 을 또한 참조하시기 바랍니다.