본문 바로가기

프로그래밍 기술 노트/Functional Study

[함수형 미세 팁] 파라미터 3개 이상의 Function 으 로 Reduce 하기

reduce / fold 는 함수형 프로그래밍의 hello world 같은 존재로 요즘에는 뭐 워낙 많이쓰기 때문에 자세한 설명이 필요없겠지만 간단하게 도식화 하자면 다음과 같다

 

위 예시는 [1 2 3 4 5 6 7] 의 시퀀스에 + 로 reduce 돌린 결과다.

reduce 는 다루기 위험한 index 기반 for문의 아주 휼륭한 대체제로, 단순히 값을 계산해 내는 방법부터, reduce 로 ListA 에서 ListB 로 변환 (map) / ListA 에서 특정 추출 (filter) 도 reduce / fold 함수로 구현할수있다. (즉 List[A] 를 reduce 한다고 A 가 나오는건 아니라는거)

뭐 이게 중요한게 아니고 위와 같이 동작하다보니, reduce 에서 받는 함수는 기본적으로 F(X,Y) 꼴의 이항 연산이다.

즉 아래처럼 사용하는건 매우 쉽다

; clojure
; (fx (fx (fx (fx (fx (fx 1 2) 3) 4) 5) 6) 7)
(reduce + [1 2 3 4 5 6 7])


// kotlin 
// fx(fx(fx(fx(fx(fx(1, 2), 3), 4), 5), 6), 7)
val numbers = listOf(1, 2, 3, 4, 5, 6, 7)
val sum = numbers.reduce { acc, i -> acc + i }

 

근데 만약 [1 2 3 4 5 6 7] 을 + 같은 이항 연산이 아닌 3항 연산을 적용하고 싶을수가있다.

// 별의미 없는 암튼 3개 받은 함수
fun tri (a:Int, b:Int, c:Int): Int {
    return a + b - c * (b - a) / (a + b)
}

val numbers = listOf(1, 2, 3, 4, 5, 6, 7)

// 내가 원하는 함수 호출
// (tri(tri(tri(1, 2, 3), 4, 5), 6, 7)

그러나 reduce / fold 는 2개 받는 함수만 가능하므로 이렇게 쓰는것이 불가능하다.

그렇다고  바로 chunk 나 windowed 를 사용할수도 없다, 우리가 원하는건

[tri(1,2,3), tri(4 5 6)] 도 아니고 [tri(1,2,3), tri(3 4 5), tri(4 5 6) tri(5 6 7)] 도 아니기 때문이다.

 

이를 reduce 로 function 을 iterate 하게 처리하고 싶으면 이를 파라미터 2개 받는 함수로 레핑하면된다.

 val head = numbers.first()
 val tail = numbers.drop(1).chunked(2)
 tail.fold(head) { i, pair -> tri(i, pair[0], pair[1]) }

함수가 3개의 파라미터를 받는다는건 1 + 2 개를 받는다는것과 같고, reduce 는 계산결과를 다음으로 넘기므로..

1 개 <- 초기값 겸 redcue 가 계산하는 결과 + 2개의 나머지 파라미터 라고 보면된다

대충 도식화 하면 다음과 같다

 

다른 좋은 방법이나 다른 고차함수가 있을지도 모르는데 나는 못찾았다. 그래서 그냥 내가 생각한 대로..

728x90