본문 바로가기
TIL

피하지 말고 즐기자! (22.10.12 TIL)

by winteringg 2022. 10. 12.

 오늘 오전에 코딩 도장 문제를 풀었는데, 스트림을 사용해서 풀 때 처음으로 다른 코드를 참고하지 않고 혼자서 풀어보는 데 성공했다! 코딩 도장을 할 때마다 TDD의 위엄을 느끼게 된다.. 어쨌든 그동안 나는 스트림 연산 메서드들이 두 개 이상 들어가야 하면 그냥 포기하고 for 문을 사용해서 문제를 풀었었다. 특히 reduce() 는 이해하려는 노력을 하지 않았다.

 약간 이런 짤 같은 바이브..? reduce 는 생긴 것도 괄호 안에 연산자도 두 개나 있고 너무 어렵게 생겨서,,^^ 하지만 다음 주부터는 함수형 프로그래밍 위주인 자바스크립트를 배워야 하고 코딩 도장 문제를 풀 때도 자바스크립트로 풀어야 하는 순간이 올 테니까 이젠 피하지 말자는 생각으로 풀어봤다.

 막상 사용하려니 stream 을 int 형에도 쓸 수 있는 지 모르겠어서 찾아보려는 와중에 우리 반 똑쟁이 쥬쥬 님이 오셔서 물어봤더니, IntStream을 쓰면 된다고 알려주셨다! 오늘의 코딩 도장 문제 와 내가 작성한 코드는 아래와 같다.

 일단 요구사항을 쪼개서 countDivisior()라는 메서드를 만들어 약수의 개수를 구해주었고, 이 메서드를 이용해 인자로 주어지는 left부터 right까지 돌면서 짝수면 더하고 홀수는 빼주는 식으로 최종 값을 리턴해주는 calculate()라는 메서드를 만들었다. reduce()를 통해 얻어진 값들을 모두 더해 최종 값을 리턴한다.

  • filter() : 요소들을 조건에 따라 걸러내줌. 여기서는 인자로 들어온 number 가 i로 나누어질 경우를 찾아주었음.
  • map() : 요소들을 특정 조건에 해당하는 값으로 변환해줌. rangeClosed() 를 통해 left 가 right까지 돌면서, 약수의 개수가 짝수인 경우는 해당 left 숫자를 그대로 돌려주고, 홀수인 경우는 빼주었음.
  • reduce() : map() 연산으로 반환된 number, 즉 약수의 개수가 짝수인 숫자들을 모두 더해줌.
    -> 예를 들어 14의 약수는 1, 2, 7, 14 로 약수의 개수가 2개인 짝수임. 그래서 14를 반환해줌.
import java.util.Arrays;
import java.util.stream.IntStream;

class Solution {
    public int solution(int left, int right) {
        int answer = calculate(left, right);

        return answer;
    }

    public int calculate(int left, int right) {
        return IntStream.rangeClosed(left, right)
                .map(number -> countDivisior(number) % 2 == 0
                        ? number : -number)
                .reduce(0, (value, number) -> value + number);
    }

    public int countDivisior(int number) {
        return (int) IntStream.rangeClosed(1, number)
                .filter(i -> number % i == 0)
                .count();
    }
}

  조금 헤맸던 부분은 countDivisior() 메서드를 만들 때 count() 뒤에 세미콜론을 찍으니 오류가 떴을 때와 reduce()를 사용할 때였다.

 일단 count() 메서드의 경우는 원소의 개수를 카운트 해서 long 타입으로 반환하는 종결 함수였다. 그래서 (int) 형으로 다운 캐스팅하는 방법으로 오류는 없애주었다.

 그리고 calculate() 에서 사용했던 reduce() 메서드는.. 처음에는 어떻게 구현하는지 모르겠어서 map() 까지만 구현하고 키보드에서 손을 떼고 멈칫, 하고 있었다. 구글링을 하며 조금 이해를 하던 와중에 내 옆자리 원재님이 '도와줘요 태석 맨!'을 외쳤고 태석님이 화이트보드로 그려가면서 reduce를 설명해 주셨다! 


  내가 이해한 내용을 다시 정리해보자면, reduce 는 모든 스트림 요소를 처리해서 최종 값으로 한 번에 도출하는 최종 연산 메서드이다. reduce()는 두 개의 인수를 갖는다. (초기값, 스트림의 두 요소를 합쳐 하나의 값으로 만드는 데에 사용하는 람다)

 태석님의 설명과 더불어 아래의 사진이 reduce 를 이해하는 데에 많은 도움을 주었다. 람다의 첫 번째 파라미터 (a)에 0 이 초기값으로 들어간다. 그 초기값 0과 두 번째 파라미터로 들어오는 4가 더해진다. 그 새로운 누적 값 4 (accumulated value)는 다시 a로 들어온다. 그리고 5가 다시 파라미터 b 가 된다. 이렇게 계속 누적된 값으로 람다를 호출하여 반복 연산을 한 후 최종 값인 21을 리턴하는 것이다.

  • 처음 연산 -> integer(초기값) + integer
  • 그 다음 연산 -> 지금까지의 총 합 + 다음 인자

 
 아래처럼 초기값을 사용하지 않을 수도 있다. 하지만 이 reduce는 Optional 객체 (객체를 포장해주는 래퍼 클래스)를 반환하기 때문에 다시 getAsInt()로 값을 변환해주어야 한다.

numbers.stream().reduce((a, b) -> (a + b));

 오늘 문제가 스트림으로 풀기에 유난히 쉬웠던 것 같기도 하다. 그리고 당연히 더 효율적인 방법이 있을 것이다. 하지만 동기들의 도움이 없었다면 그냥 구글링 하는 데에만 시간을 다 썼을 것 같다. 항상 지식 공유해주시는 쥬쥬님 태석님 원재님 보니님 감사합니다,, S2 

댓글