Java 함수 프로그래밍(7): MapReduce

주:map(영사)과reduce(귀약, 간소화)는 수학적으로 두 가지 기초적인 개념이다. 그들은 일찍이 각종 함수 프로그래밍 언어에 등장했다. 2003년에 Google이 이를 발양하여 분포식 시스템에 활용하여 병행 계산을 한 후에야 이 조합의 이름은 컴퓨터계에서 이채를 띠기 시작했다.본고는 Java8이 함수식 프로그래밍을 지원하는 것으로 변신한 후 맵과 Reduce 조합이 처음으로 선보이는 것을 볼 수 있다.
집합 을 귀약 하다
지금까지 우리는 몇 가지 조작 집합의 새로운 기교를 소개했다. 그것이 바로 일치하는 원소를 찾고 하나의 원소를 찾으며 집합 전환이다.이 조작들은 집합 중의 단일 요소를 조작하는 공통점이 있다.원소를 비교하거나 두 원소를 연산할 필요가 없다.이 절에서 우리는 원소를 어떻게 비교하는지, 그리고 집합 과정에서 하나의 연산 결과를 동적으로 유지하는지 살펴본다.
우리는 먼저 간단한 예부터 시작한 다음에 순서에 따라 점진적으로 진행한다.첫 번째 예에서, 우리는 먼저 friends 집합을 훑어보고, 모든 이름의 총 문자 수를 계산해 봅시다.

System.out.println("Total number of characters in all names: " + friends.stream()
         .mapToInt(name -> name.length())
         .sum());
모든 문자의 총수를 계산하려면 우리는 모든 이름의 길이를 알아야 한다.mapToInt() 방법으로 쉽게 완성할 수 있습니다.우리가 이미 이름을 대응하는 길이로 바꾼 후에 마지막에는 그것들을 한 조각만 추가하면 된다.우리는 이것을 완성하기 위해 내장된sum() 방법이 있다.다음은 마지막 출력입니다.

Total number of characters in all names: 26
우리는 맵 조작의 변종인 맵 To Int () 방법을 사용했다. (이런 것은 맵 To Int, 맵 To Double 등이 있는데 구체적인 유형의 흐름, 예를 들어 Int Stream, Double Stream)을 생성하고 되돌아오는 길이에 따라 전체 문자 수를 계산한다.
sum방법을 사용하는 것 외에 유사한 방법도 많이 사용할 수 있다. 예를 들어max()로 가장 큰 길이를 구할 수 있고min()으로 가장 작은 길이,sorted()로 길이를 정렬하고average()로 평균 길이를 구할 수 있다.
상술한 이 예에서 또 하나의 매력적인 점은 현재 점점 유행하고 있는 맵 Reduce 모드, 맵 () 방법은 비추고sum () 방법은 비교적 자주 사용하는 Reduce 조작이다.사실 JDK에서sum() 방법의 실현은 Reduce() 방법을 사용한다.우리는 Reduce 조작이 더욱 자주 사용하는 몇 가지 형식을 살펴보자.
예를 들어 우리는 모든 이름을 두루 훑어보고 가장 긴 이름을 출력한다.만약 가장 긴 이름이 여러 개 있다면, 우리는 처음에 찾은 것을 인쇄해 낼 것이다.한 가지 방법은 우리가 가장 큰 길이를 계산한 후에 이 길이에 일치하는 첫 번째 요소를 선택하는 것이다.그러나 이렇게 하려면 두 번의 목록을 반복해야 한다. 효율이 너무 낮다.이것이 바로 리듀스 조작이 등장할 때다.
우리는 Reduce 조작으로 두 원소의 길이를 비교한 다음에 가장 긴 원소를 되돌려주고 나머지 원소와 한층 더 비교할 수 있다.우리가 이전에 본 다른 고급 함수와 마찬가지로, Reduce () 방법 역시 전체 집합을 두루 훑어보았다.이 밖에lambda 표현식이 되돌아오는 계산 결과를 기록합니다.예를 들면 우리가 이 점을 더 잘 이해하는 데 도움을 줄 수 있다. 그러면 우리 먼저 코드 한 토막을 보자.

final Optional<String> aLongName = friends.stream()
         .reduce((name1, name2) ->
            name1.length() >= name2.length() ? name1 : name2);
aLongName.ifPresent(name ->
System.out.println(String.format("A longest name: %s", name)));
reduce () 방법에 전달된lambda 표현식은 두 개의 매개 변수를 수신합니다.name1과name2는 그것들의 길이를 비교하여 가장 긴 것을 되돌려줍니다.reduce () 방법은 우리가 무엇을 해야 할지 전혀 모른다.이 논리는 우리가 전달한lambda 표현식에서 박리되었다. 이것은 전략 모델의 경량급 실현이다.
이 lambda 표현식은 JDK의 Binary Operator 함수식 인터페이스의 apply 방법으로 적합하다.이것이 바로reduce 방법이 받아들여야 할 매개 변수 형식입니다.이 Reduce 방법을 실행해서 두 개의 가장 긴 이름 중에서 첫 번째를 정확하게 선택할 수 있는지 봅시다.

A longest name: Brian
reduce () 방법이 집합을 반복하는 과정에서 집합의 두 요소에 대해lambda 표현식을 호출하고 호출된 결과는 다음 호출에 계속 사용됩니다.두 번째 호출에서name1의 값은 지난번 호출한 결과로 귀속되고name2의 값은 집합된 세 번째 요소입니다.나머지 원소도 이렇게 순서대로 호출된다.마지막 lambda 표현식 호출 결과는 전체 Reduce () 방법이 되돌아온 결과입니다.
reduce () 방법은 Optional 값을 되돌려줍니다. 전달된 집합이 비어 있을 수 있기 때문입니다.그렇다면 가장 긴 이름은 존재하지 않을 것이다.만약 목록에 원소가 하나밖에 없다면, Reduce 방법은 그 원소를 직접 되돌려주고,lambda 표현식에 호출되지 않습니다.
이 예에서 우리는 Reduce의 결과가 가장 많은 것은 집합 중의 한 원소일 뿐이라고 추정할 수 있다.기본값이나 기본값을 되돌려 주기를 원한다면, Reduce () 방법의 변종을 사용할 수 있습니다. 추가 인자를 받을 수 있습니다.예를 들어 가장 짧은 이름이 Steve라면 Reduce () 방법에 전달할 수 있습니다.

final String steveOrLonger = friends.stream()
     .reduce("Steve", (name1, name2) ->
            name1.length() >= name2.length() ? name1 : name2);
만약 그것보다 긴 이름이 있다면, 이 이름은 선택될 것이다.그렇지 않으면 이 기본값 Steve로 돌아갑니다.이 버전의 Reduce () 방법은 Optional 대상을 되돌려주지 않습니다. 집합이 비어 있으면 기본값을 되돌려줍니다.반환 값이 없는 상황을 고려할 필요가 없다.
우리가 이 장을 끝내기 전에, 집합 조작 중의 매우 기초적이지만 그리 쉬운 조작은 아니다. 즉, 원소를 합병하는 것이다.
요소 결합
우리는 원소의 찾기, 두루 훑어보기, 그리고 집합의 전환을 어떻게 하는지 배웠다.그러나 집합 요소를 조합하는 흔한 조작이 하나 더 있다. 만약 이 새로 추가된join () 함수가 없다면 이전에 말한 간결하고 우아한 코드는 수포로 돌아갈 수밖에 없다.이 간단한 방법은 매우 실용적이어서 JDK에서 가장 자주 사용하는 함수 중의 하나가 되었다.목록의 요소를 쉼표로 구분하여 인쇄하는 방법을 살펴보겠습니다.
우리 이 친구 리스트로 하자.JDK 라이브러리의 낡은 방법을 사용하면 모든 이름을 출력하고 쉼표로 구분하려면 어떤 작업을 해야 합니까?
우리는 목록을 두루 훑어보고 원소를 하나씩 인쇄해야 한다.Java 5의 for 순환이 이전보다 개선되었으니 사용합시다.

for(String name : friends) {
      System.out.print(name + ", ");
}
System.out.println();
코드는 매우 간단합니다. 우리는 그것의 출력이 무엇인지 봅시다.

Brian, Nate, Neal, Raju, Sara, Scott,
빌어먹을, 마지막에 싫은 쉼표가 하나 더 생겼어.어떻게 자바에게 쉼표를 하나 넣지 말라고 할 수 있습니까?불행하게도 순환은 순서대로 실행되기 때문에 마지막에 특별히 처리하기가 쉽지 않다.이 문제를 해결하기 위해서 우리는 원래의 그런 순환 방식을 사용할 수 있다.

for(int i = 0; i < friends.size() - 1; i++) {
      System.out.print(friends.get(i) + ", ");
}
if(friends.size() > 0)
      System.out.println(friends.get(friends.size() - 1));
이 버전의 출력이 OK인지 확인해 봅시다.

Brian, Nate, Neal, Raju, Sara, Scott
결과는 그래도 괜찮았지만, 이 코드는 감히 아첨하지 못했다.우리를 구해줘, 자바.
우리는 더 이상 이런 고통을 참을 필요가 없다.Java8의 String Joiner 클래스는 우리가 이런 난제를 해결하는 데 도움을 주었다. 뿐만 아니라 String 클래스는 Join 방법을 추가하여 우리가 위의 그 덩어리를 대체할 수 있도록 한 줄의 코드를 사용할 수 있게 했다.

System.out.println(String.join(", ", friends));
어서 한번 봅시다. 결과는 코드만큼이나 만족스럽습니다.

Brian, Nate, Neal, Raju, Sara, Scott
결과는 그래도 괜찮았지만, 이 코드는 감히 아첨하지 못했다.우리를 구해줘, 자바.
우리는 더 이상 이런 고통을 참을 필요가 없다.Java8의 String Joiner 클래스는 우리가 이런 난제를 해결하는 데 도움을 주었다. 뿐만 아니라 String 클래스는 Join 방법을 추가하여 우리가 위의 그 덩어리를 대체할 수 있도록 한 줄의 코드를 사용할 수 있게 했다.

System.out.println(String.join(", ", friends));
어서 한번 봅시다. 결과는 코드만큼이나 만족스럽습니다.

Brian, Nate, Neal, Raju, Sara, Scott
밑바닥 구현 중, String.join () 방법은 두 번째 파라미터가 들어오는 값 (이것은 길어지는 파라미터) 을 긴 문자열로 연결하기 위해 StringJoiner 클래스를 호출했습니다. 첫 번째 파라미터를 구분자로 사용합니다.이 방법은 당연히 쉼표를 연결할 수 있는 것만은 아니다.예를 들어 우리는 한 무더기의 경로를 전송한 후에 클래스 경로 (classpath) 를 쉽게 조합할 수 있다. 이것은 정말 이런 새로운 방법과 클래스 덕분이다.
우리는 목록 요소를 어떻게 연결하는지 이미 알고 있다. 목록 연결을 하기 전에 우리는 먼저 원소를 전환할 수 있다. 물론 맵 방법을 사용하여 목록 전환을 하는 방법도 알고 있다.다음은 필터 () 방법으로 우리가 원하는 요소를 필터할 수 있습니다.마지막 단계의 연결 목록 요소는 쉼표로 구분됩니까? 간단한 Reduce 작업일 뿐입니다.
우리는 Reduce () 방법으로 원소를 문자열로 연결할 수 있지만, 이것은 우리가 시간을 좀 들여야 한다.JDK는 매우 편리한collect () 방법을 가지고 있으며, 이것은reduce () 의 변종이기도 하며, 우리는 그것으로 원소를 원하는 값으로 합칠 수 있다.
collect () 방법은 귀약 조작을 실행하지만, 구체적인 조작은collector에 의뢰합니다.우리는 전환된 원소를 하나의 ArrayList로 합칠 수 있다.아까 그 예를 계속하면, 우리는 전환된 요소를 쉼표로 구분된 문자열로 연결할 수 있다.

System.out.println(
      friends.stream()
          .map(String::toUpperCase)
          .collect(joining(", ")));
우리는 전환된 목록에서collect () 방법을 호출하여joining () 방법으로 되돌아오는collector를 전송했습니다.joining은Collectors 도구 클래스의 정적 방법입니다.collector는 마치 수신기입니다. collect가 전송한 대상을 수신하고 원하는 형식으로 저장합니다. Array List, String 등입니다.우리는 52페이지의collect 방법과 Collectors 클래스에서 이 방법을 더욱 탐색할 것이다.
이것은 출력된 이름입니다. 지금은 대문자로 되어 있고 쉼표로 구분됩니다.

BRIAN, NATE, NEAL, RAJU, SARA, SCOTT
총결산
집합은 프로그래밍에서 매우 흔히 볼 수 있는데 lambda 표현식이 생기면 자바의 집합 조작은 더욱 간단하고 쉬워진다.질질 끄는 집합 조작의 오래된 코드는 모두 이런 우아하고 간결한 새로운 방식으로 바꿀 수 있다.내부 교체기는 집합을 두루 다니게 하고 전환이 더욱 편리해지며 가변적인 고민을 멀리하고 집합 요소를 찾는 것도 매우 가벼워진다.이런 새로운 방법을 사용하면 많은 코드를 적게 쓸 수 있다.이것은 코드를 더욱 쉽게 유지하고 업무 논리에 초점을 맞추며 프로그래밍에서의 기본적인 조작도 더욱 적게 한다.
다음 장에서 우리는 lambda 표현식이 프로그램 개발의 또 다른 기본 조작인 문자열 조작과 대상 비교를 어떻게 간소화하는지 볼 수 있다.

좋은 웹페이지 즐겨찾기