BanditBool2 / ReadingRecord

2 stars 0 forks source link

[Ch 5] 5.3.2 스트림 평면화 #26

Closed JinseoPark-bd closed 1 year ago

JinseoPark-bd commented 1 year ago

p163 ~ p165 두 단어에서 중복된 문자를 제거하여 하나의 문자열로 반환하는 문제입니다.

// map 사용
List<String> uniqueCharacters = 
    words.stream()
        .map(word -> word.split(""))
        .map(Arrays::stream)
        .distinct()
        .collect(toList());

// flatMap사용
List<String> uniqueCharacters = 
    words.stream()
        .map(word -> word.split(""))
        .flatMap(Arrays::stream)
        .distinct()
        .collect(toList());
  • 위의 map을 사용하는 코드에서는 두 문자열 배열을 각각의 스트림으로 계산해서 스트림 리스트가 반환되는 것이고, flatMap을 사용하면 스트림을 평면화 한다고 하는데 평면화 한다는 것이 두 문자열 배열의 각각의 스트림을 하나로 합쳐서 한 문자열로 계산하는 것으로 이해하는 것이 맞을까요?
  • flatMap에 대해 조금만 설명해주시면 감사하겠습니다.
sohhhyeeun commented 1 year ago

예를 들어 색깔별로 2개씩 분류한 과일의 String[][] 타입의 2차원 배열이 있다고 가정한다면 이때 Apple인 것만 제외하고 새로운 String[] 타입을 얻는 요구사항을 가장 간단하게 구현하는 방법은 아래와 같습니다.

@Test
public void 이차원_배열을_중첩for문으로_접근하기() {
    String[][] nested = new String[][]{ {"Apple", "Cherry"}, {"Mango", "Orange"}, {"Grape", "BlueBerry"}};

    List<String> fruits = new ArrayList<>();

    for (String[] fs : nested) {
        for (String f : fs) {
            if(!f.equals("Apple")) fruits.add(f);
        }
    }

    String[] exclude = fruits.toArray(new String[fruits.size()]);
    System.out.println(Arrays.toString(exclude));
}

결과 ) [Cherry, Mango, Orange, Grape, BlueBerry]

위 실행 결과를 Stream의 filter() 메서드만 활용해서 최대한 비슷하게 구현하면 아래와 같습니다.

@Test
public void 이차원_배열을_flatMap없이_접근하기() {
    String[][] nested = new String[][]{ {"Apple", "Cherry"}, {"Mango", "Orange"}, {"Grape", "BlueBerry"}};

    List<String[]> fruits = Arrays.stream(nested)
            .filter(fs -> { //<- nested가 2차원 배열이기 때문에 넘어오는 fs 변수는 1차원 배열 String[]
                for (String f : fs) { //<- fs가 1차원 배열이기 때문에 다시 순회하기 위해 for문 적용
                    if (f.equals("Apple")) return false;
                }
                return true;
            })
            .collect(Collectors.toList()); //<- 결과적으로 filter()가 리턴하는 타입은 Stream<String[]> 이기 때문에 List<String>타입으로 가져올 수 없고 List<String[]> 타입으로 collecting

    fruits.forEach(fs-> System.out.println(Arrays.toString(fs)));
}

코드에서 설명한대로 2차원배열-> 1차원배열 -> 개별요소로 접근해야하므로 코드의 가독성이 안 좋아지고 우리가 원하는 List 타입이 아닌 List<String[]> 타입으로 가져오게 되어 별도의 처리를 해주어야 합니다. 그리고 가장 큰 문제점은 결과값에서 확인할 수 있습니다.

결과 ) [Mango, Orange] [Grape, BlueBerry]

단순히 Apple값을 제외하려고 했지만 Apple과 같은 배열에 있는 Cherry 역시 제거된 상태가 되는데 위에서 설명한 fs 변수가 개별요소가 아닌 1차원 배열이기 때문에 Apple 이 포함된 [Apple, Cherry] 배열 자체를 필터링 시킨 결과가 나오게 됩니다.

이때 flatMap()을 활용하면 쉽게 접근할 수 있습니다.

@Test
public void 이차원_배열을_flatMap으로_접근하기() {
    String[][] nested = new String[][]{ {"Apple", "Cherry"}, {"Mango", "Orange"}, {"Grape", "BlueBerry"}};

    List<String> fruits = Arrays.stream(nested)
            .flatMap(fs -> Arrays.stream(fs)) //<- Stream 타입을 리턴
            .filter(f -> !f.equals("Apple"))
            .collect(Collectors.toList());

    fruits.forEach(System.out::println);
}

결과 ) Cherry Mango Orange Grape BlueBerry

참고 ) https://sup2is.github.io/2021/01/19/flatmap-example.html