본문 바로가기

코드스테이츠(Pre)

JavaScript Reduce 자세히 알아보기

reduce 메소드 사용해서 평균값 찾기

합계를 로깅하기 전에, 합계를 array의 길이값으로 나눈 뒤에 리턴한다.

 for-loop와 같이 index의 수만큼 reduce가 array를 반복하게 해준다. 

const euros = [29.76, 41.85, 46.5];

const average = euros.reduce((total, amount, index, array) => {
	total += amount;
    if(index === array.length-1){
		return total/array.length;
    } else {
    	return total;
    }
});

average // 39.37

 

Map 그리고 Filter로써의 reductions

일반적으로 array안의 수만큼 반복할 것이고, 단일 값을 리턴한다.

하지만, 항상 단일 값을 리턴하진 않는다. array를 새로운 array로 reduce 할 수 있다.

예를 들어, 모든 값을 두배로 만들길 원하는 array가 있다면 accumulatorinitial value를 empty array로 만들어 주면 된다.

total 파라미터의 Initial value는 reduction이 시작될 때 값이다.

const average = euros.reduce((total, amount, index, array) => {
	total += amount
    return total/array.length
}, 0);

이전의 예시에서, initial value는 0 이었기에 생략했다.
init value를 생략함으로써, total은 어레이의 첫번째 값이 될것이다.

init value를 빈 array로 설정하게되면, 각 total의 값을 push 할 수 있다.

const euros = [29.76, 41.85, 46.5];

const doubled = euros.reduce((total, amount) => {
	total.push(amout * 2);
    return total;
}, []);

doubled // [59.52, 83.7, 93]

모든 값의 두배인 새로운 array를 만들었다. 

또, reducer 안에 두배를 원하지 않는 조건을 설정해 필터 처리를 할 수 도있다.

const euro = [29.76, 41.85, 46.5];

const above30 = euro.reduce((total, amount) => {
	if(amount > 30) {
    	total.push(amount);
    }
    return total;
}, []);

above30 // [41.85, 46.5]

위 코드는 map과 filter 메소드를 reduce 메소드로 다시 작성한것이다.

여기서는 map이나 filter가 사용하기 더 간단하지만,

많은 양의 데이터를 사용하거나, map과 filter를 같이 사용할 경우 reduce를 사용하는것이 유용하다.

map 과 filter를 연결해서 사용하는 경우, 모든 값을 필터처리를 한후 남겨진 값들을 map하게 된다.

reduce를 사용하는경우, reduce로 filter와 map 한번에 처리가능하다.

 

Reduce 메소드로 계산서(?) 만들기

const fruitBasket = ['banana', 'cherry', 'orange', 'apple', 'cherry', 'orange', 'apple', 'banana', 'cherry', 'orange', 'fig' ];

const count = fruitBasket.reduce( (tally, fruit) => {
  tally[fruit] = (tally[fruit] || 0) + 1 ;
  return tally;
} , {})

count // { banana: 2, cherry: 3, orange: 3, apple: 2, fig: 1 }

init value 는 빈 array가 아닌 key-value값 저장을 위해 빈 object로 만들어져야 한다.

fruitBasket.reduce((tally, fruit) => {
	tally[fruit] = 1;
    return tally;
}, {})

첫번째 키의 이름을 current value로  값은 1로 준다.

이렇게 되면, 모든 과일들이 객체의 key로 값은 1이 된다.

이제 각 과일에 반복되는 수만큼 값을 증가시켜야한다.

이것을 위해 두번째 반복문이 reducer의 현재 과일이 키를 포함했는지 여부를 확인하게 된다.

포함되어 있지 않다면 만들어내고, 만약에 포함되있다면 하나만큼 증가시킨다.

fruitBasket.reduce((tally, fruit) => {
  if (!tally[fruit]) {
    tally[fruit] = 1;
  } else {
    tally[fruit] = tally[fruit] + 1;
  }
  return tally;
}, {});

 

Reduce 메소드 사용해서 array of arrays Flattening하기 

const data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];

const flat = data.reduce((total, amount) => {
  return total.concat(amount);
}, []);

flat // [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

더 복잡한 예를 보자면,

const data = [
  {a: 'happy', b: 'robin', c: ['blue','green']}, 
  {a: 'tired', b: 'panther', c: ['green','black','orange','blue']}, 
  {a: 'sad', b: 'goldfish', c: ['green','red']}
];

각각의 객체에서 색깔을 빼내야된다.

이를 위해서 array 안에서 amount.c for each를 해준다.

그러면 forEach 반복을통해 중첩된 array에서 total로 모든 값들이 push된다.

const colors = data.reduce((total, amount) => {
  amount.c.forEach( color => {
      total.push(color);
  })
  return total;
}, [])

colors //['blue','green','green','black','orange','blue','green','red']

여기서 특정한 색상만 필요하다면 push 전에 존재여부를 확인할 수 있다.

const uniqueColors = data.reduce((total, amount) => {
  amount.c.forEach( color => {
    if (total.indexOf(color) === -1){
     total.push(color);
    }
  });
  return total;
}, []);

uniqueColors // [ 'blue', 'red', 'green', 'black', 'orange']

 

Piping with Reduce

흥미로운 부분은 reduce 메소드는 함수 뿐만아니라 숫자나 문자열에서 또한 가능하다.

function increment(input) { return input + 1;}
function decrement(input) { return input — 1; }
function double(input) { return input * 2; }
function halve(input) { return input / 2; }

더하고, 나누고, 곱하고, 빼고 이런 연산들을 반복해야할수록 문제가 생긴다. 

매번 이 함수들을 쓸 수 없기에 reduce를 사용해 pipeline을 만들어보고자한다.

pipeline은 init value을 최종값으로 변환시키기 위한 함수목록에 사용되는 용어이다.

여기서 파이프 라인은 사용을 원하는 세가지 함수로 구성된다.

let pipeline = [increment, double, decrement];

array의 값의 reducing 하는 대신에 파이프라인의 함수를 reduce 한다.

변화를 원하는 init value를 설정해주면 이것은 잘 작동하게 된다.

const result = pipeline.reduce(function(total, func) {
  return func(total);
}, 1);
result // 3

왜냐면 파이프라인은 array이고, 쉽게 수정가능하다. 함수를 조금 수정하고 싶다면 파이프라인만 간단히 바꾸면 된다.

 

Reduce 메소드에서 하기 쉬운 실수들

초기값(Initial value)을 설정하지 않는 경우, reduce는 array의 첫번째 값을 초기값으로 추정할 것이다.

초반부에 본 예시들은 괜찮겠지만,

과일들을 계산하는 예시에서 초기값을 설정하지 않는다면 뭔가 잘못된 것을 알것이다.

초기값을 설정하지않는 실수는 디버깅할때 가장 먼저 확인해야 되는 것 중 하나이다.

다른 실수 하나는 return total(accumulator)하는것을 잊어버리는 것이다.

reduce함수에서 원하는 값을 return받는지를 확실히 체크 해야한다.

 

Examples:
    var arr = [{name: 'Elie'}, {name: 'Tim'}, {name: 'Matt'}, {name: 'Colt'}]
    extractValue(arr,'name') // ['Elie', 'Tim', 'Matt', 'Colt']
*/

function extractValue(arr, key){
    return arr.reduce(function(accumulator, name){
        return accumulator[key];
    })
}


/*
Write a function called vowelCount which accepts a string and returns an object with the keys as the vowel and the values as the number of times the vowel appears in the string. This function should be case insensitive so a lowercase letter and uppercase letter should count

Examples:
    vowelCount('Elie') // {e:2,i:1};
    vowelCount('Tim') // {i:1};
    vowelCount('Matt') // {a:1})
    vowelCount('hmmm') // {};
    vowelCount('I Am awesome and so are you') // {i: 1, a: 4, e: 3, o: 3, u: 1};
*/

function vowelCount(str){
   var vowels="aeiou";
   return str.toLowerCase().split("").reduce(function(accumulator, nextValue){
       if(vowels in accumulator){
           
       }
   })
}

/*
Write a function called addKeyAndValue which accepts an array of objects and returns the array of objects passed to it with each object now including the key and value passed to the function.

Examples:
    var arr = [{name: 'Elie'}, {name: 'Tim'}, {name: 'Matt'}, {name: 'Colt'}];
    
    addKeyAndValue(arr, 'title', 'Instructor') // 
      [
        {title: 'Instructor', name: 'Elie'}, 
        {title: 'Instructor', name: 'Tim'}, 
        {title: 'Instructor', name: 'Matt'}, 
        {title: 'Instructor', name: 'Colt'}
       ]
*/

function addKeyAndValue(arr, key, value){
    
}


/*
Write a function called partition which accepts an array and a callback and returns an array with two arrays inside of it. The partition function should run the callback function on each value in the array and if the result of the callback function at that specific value is true, the value should be placed in the first subarray. If the result of the callback function at that specific value is false, the value should be placed in the second subarray. 

Examples:
    
    function isEven(val){
        return val % 2 === 0;
    }
    
    var arr = [1,2,3,4,5,6,7,8];
    
    partition(arr, isEven) // [[2,4,6,8], [1,3,5,7]];
    
    function isLongerThanThreeCharacters(val){
        return val.length > 3;
    }
    
    var names = ['Elie', 'Colt', 'Tim', 'Matt'];
    
    partition(names, isLongerThanThreeCharacters) // [['Elie', 'Colt', 'Matt'], ['Tim']]
*/

function partition(arr, callback){
    
}