본문 바로가기

JS - reduce 메서드 / 논리연산자 / 단축평가 본문

개발/JavaScript

JS - reduce 메서드 / 논리연산자 / 단축평가

자전하는명왕성 2022. 12. 31. 16:22

어제 풀던 문제를 다시 돌아본다

문제 내용은 이렇다 

'임의의 문자열을 입력받아, 문자열에 각 단어가 몇 번 등장하는지 기록한 객체를 리턴하라.'

* 대소문자는 구분하지 않아야 하며, 단어가 존재하지 않는 경우 빈 객체를 리턴.

* trim 메서드의 사용은 금지.

function wordFinder(str) {
    // 여기에 코드를 작성하세요
    let sssAtr = str.toLowerCase().split(" ")
    console.log(sssAtr)    
    let sameArr = []
    
    for(let i=0; i<sssAtr.length ; i++){
        let a = sssAtr[i]
        for(let j = i+1 ; j < sssAtr.length; j++){
            let b = sssAtr[j]
            console.log("a:", a,"b:", b, i, j)
            if(a == b){
                sameArr.push(a)
            }
        }
    }
}

 

처음 해당 문제에는 이중반복문으로 접근했다.

먼저, 대소문자와 단어를 구분하기 위한 문자열 변수를 만들어내고,

이중 반복문을 통해 반복되는 문장이 있을 경우, 빈 배열에 푸쉬해주는 형태였다.

 

하지만, 위와 같은 방법에는 치명적인 문제가 있었다.

똑같은 단어가 3개 이상 존재하는 경우 중복으로 세어버리는 경우가 존재한다는 것.

더군다나, 중복으로 세어진 단어를 구분하기 위해 또 다른 처리작업이 필요했더랬다.

 

그러던 중 구글에서 찾은 게, reduce 메서드 / 논리연산자 / 단축 평가와 관련된 개념이었다.

 

reduce

배열이름.reduce((누산값, 현재요소값, 현재요소의index, 현재배열) => {
  return 다음누산값;
}, 초기누산값);

reduce 메서드는 위와 같은 형태를 띄며, forEach / map 과 같은 메서드처럼 배열을 순회한다.

다만, forEach / map 과는 달리 특별한 부분이 있다. 

forEach / map 메서드의 경우 단순히 배열 한가지에 하나의 일을 처리한다고 한다면,

reduce 메서드는 과거의 값에 관여를 한다고 볼 수 있다. (때문에, '누산값' 존재)

어려운 말로는 콜백 함수가 동작할 때 return 하는 값이 다음 콜백 함수의 첫 번째 파라미터로 전달한다고 볼 수 있겠다.

 

예시

const numbers = [0,1,2,3,4]

numbers.reduce((acc ,cur ,i) => {
    console.log(`${i}번째 순회`)
    console.log(`acc : ${acc} // cur : ${cur}`)
    console.log(`acc + cur : ${acc + cur}`)

    return acc+cur
}, 0
)

reduce 함수말미에 초기값으로 0으로 설정했기 때문에, 첫 acc 에는 0이 들어오고,

0 번째 순회에는 numbers array의 첫번째 값인 0을 cur로 가져온다.

그리고 첫번째 acc + cur 값으로 받아온 0이 1번째 순회의 acc에 대입된다.

이게 반복되어 마지막에는 numbers array에 모든 값이 합쳐진다.

말 그대로 누산한다는 개념이다.

 

이 개념을 응용하면 중복된 값을 객체에 넣어줄 수 있다. (우선은 구글에서 떼온 풀이 방법이다)

const result = array.reduce((acc, cur, i) => {
        console.log(`${i}번째 순회`);
        console.log(`acc : ${acc}`)
        console.log(`cur : ${cur}`)

        acc[cur] = (acc[cur] || 0) + 1;
        console.log('acc[cur]', acc[cur])
        return acc;
    }, {} );
    console.log(result)

주의 깊게 봐야 할 것

  • 초기값의 설정은 '중괄호 { }', 즉 빈 객체. 만약 초기값이 '대괄호 []'로 설정되어 있으면 배열에 저장됨.
  • 객체의 'key 값'을 동적으로 넣기 위해서는 대괄호를 활용해야 함.
  • 그렇다면 연산에 사용된 acc[cur] = (acc[cur] || 0) + 1 은 대체 무엇인가.

 

논리연산자 (|| , &&), 단축평가

논리연산자는 if문에서 종종 사용하곤 해서, 그 모습이 낯설지는 않다.

1. || (논리합 연산자) / 부제 true 찾기

|| 의 경우, 둘 중 하나만 true면 true 반환

true || false;  // true(오른쪽의 false는 볼 것도 없이, 왼쪽의 true 반환)
true || true;  // true(오른쪽의 true는 볼 것도 없이, 왼쪽의 true 반환)

만약 왼쪽 피연산자가 문자열이라면, 빈 문자열이 아니기 때문에 true를 반환

"hello" || false;  // "hello"
"hello" || true;  // "hello"

위와 같은 개념에서 추가적으로 문자열과 문자열이 양쪽 피연산자에 위치한 경우에는

문자열은 true 로 반환되기 때문에 오른쪽 피연산자는 볼 필요가 없음

"hello" || "bye" // "hello" (오른쪽 값은 볼 필요가 없음)

만약 왼쪽 피연산자가 false 라면, 오른쪽 피연산자가 frue / false 여부 결정하게 됨

false || true;  // true (오른쪽 값 반환)
false || false;  // false (오른쪽 값 반환)

// 오른쪽이 문자열일 경우
false || "hello"; // "hello" (오른쪽 값 반환)
// 앞서 말했다시피 문자열은 true로 반환됨

가벼운 정리 : 왼쪽 값이 true면 바로 반환 / 허나, 왼쪽 값이 false라면 오른쪽 값을 본 뒤 true / false 결정해서 반환

 

2. &&(논리곱 연산자)

&&의 경우, 그 의미 말마따나 둘다 true여야만 true 반환.

만약 왼쪽 피연산자가 false 인 경우, 오른쪽은 볼 필요도 없이 자연스레 false 반환.

false && true;  // false (오른쪽은 상관 없음.)
false && false;  // false (오른쪽은 상관 없음.)
false && "hello";  // false (오른쪽은 상관 없음.)

만약, 왼쪽 피연산자에 null이 오는 경우에는, null은 false로 평가됨으로, null을 그대로 반환

null && false;  // null

왼쪽 피연산자가 true값이거나 true값으로 평가되는 문자열이 들어가는 경우에는, 오른쪽 값을 반환

true && true;  // true
true && false;  // false

true && "hello";  // "hello"

"hello" && true; // true
"hello" && false; // false

"hello" && "bye" // "bye"

 

위와 같은 방식은 '단축 평가'라고 하며, 이는 아래와 같은 조건들에 사용할 수 있다고 한다.

  • null / undefined 체크
  • 함수 매개변수 기본값 설정
  • 조건부 변수값 할당

이제 그러면 acc[cur] = (acc[cur] || 0) + 1 이라는 코드에 조금이나마 접근할 수 있다.

 

(acc[cur] || 0) 의 식에서 왼쪽 피연산자가 없을 경우(false인 경우)에는 0을 반환하여 acc[cur] = 1 이 되게 됨.

즉, 초기값 객체 {}처음 acc로 반환되기 때문에, 해당 객체에 cur값을 key, 1 을 value 값으로 저장되게 되는 것임.

 

반대로 (acc[cur] || 0) 의 식에서 왼쪽 피연산자가 있을 경우(true인 경우)에는 acc[cur]을 반환, acc[cur] = acc[cur] + 1 이 됨.

즉, acc[cur] += 1 과 같은 형태로, cur 의 value값에 +1 이 추가된다는 의미임.

 

이를 적용한 예시

예시 문장 : this is the moment this is the day

순회 횟수 acc cur acc[cur] // key : value
0 { }  this { this : 1}
1 {this : 1} is { this : 1, is : 1}
2 {this : 1, is : 1} the {this : 1, is : 1, the :1}
3 {this : 1, is : 1, the :1} moment {this : 1, is : 1, the :1,
moment :1}
4 {this : 1, is : 1, the :1, 
moment :1}
this {this : 2, is : 1, the :1, 
moment :1}
5 {this : 2, is : 1, the :1, 
moment :1}
is {this : 2, is : 2, the :1, 
moment :1}
6 {this : 2, is : 2, the :1, 
moment :1}
the {this : 2, is : 2, the :2, 
moment :1}
7 {this : 2, is : 2, the :2, 
moment :1}
day {this : 2, is : 2, the :2, 
moment :1, day :1}

 

쉽지 않은 개념이었는데, 이해하고야 말았다.

응용하기까지는 시간이 걸리겠지만, 오늘도 아나스타샤.

Comments