공부/Java

[Java] Stream Quiz 개인 풀이 (출처. 망나니 개발자 기술 블로그)

leejinwoo1126 2023. 8. 15. 11:56
반응형

 

 


목차


    출처 (망나니 개발자 기술 블로그)

    https://mangkyu.tistory.com/116

     

    [Java] Stream API 연습문제 풀이 (5/5)

    이번에는 Stream API를 연습해볼만한 문제를 제공해보고자 합니다. 자동화된 테스트를 통해 정답을 확인하도록 제공하고 있으니 직접 문제를 풀어서 정답을 확인해보실 분들은 아래 내용을 참고

    mangkyu.tistory.com

     

    *자료 받으실 때 Star 는 필수

     

     

    수행  

    - 소요 시간
    1일차 1시간 40분
    2일차 40분
    3일차 20분
    4일차 15분
    5일차 15분
    암기보다는 [stream 생성 -> 중간 연산 -> 최종 연산] 과정에 집중해서 연습

     

    Quiz1

    1-1. 각 취미별 선호 인원 수 구하기

    - csv 파일의 경우 readCsvLines() 통해서 읽어짐

    - 취미를 나타내는 String에 공백을 포함하고 있어 소거 후 split 처리 

    - Collector.toMap() 최종 연산을 통해 결과 Map<String, Integer> 타입을 맞춰 줌  (toMap 연산에 대해 덜 이해)

    (이때 Collector.counting()의 경우 Long 타입을 리턴하기 때문에 사용 불가)

    public Map<String, Integer> quiz1() throws IOException {
        List<String[]> csvLines = readCsvLines();
         return csvLines.stream()
                    .map(line -> line[1].replaceAll("\\s", ""))
                    .flatMap(text -> Stream.of(text.split(":")))
                    .collect(Collectors.toMap(Function.identity(), e -> 1, Integer::sum));
    }

     

    1-2. 각 취미를 선호하는 정씨 성을 갖는 인원 수 구하기 

    - 이름에서 "정" 으로 시작하는지 필터링 

    - 1-1과 동일하게 집계 처리

    public Map<String, Integer> quiz2() throws IOException {
        List<String[]> csvLines = readCsvLines();
        return csvLines.stream()
                    .filter(line -> line[0].startsWith("정"))
                    .map(line -> line[1].replaceAll("\\s", ""))
                    .flatMap(text -> Stream.of(text.split(":")))
                    .collect(Collectors.toMap(Function.identity(), e -> 1, Integer::sum));
    }

     

    1-3. 소개 내용에 '좋아' 가 몇번 등장하는지 계산 

    직접 풀이하지 못하였고, 답안 확인했을 때 재귀함수 호출하여 처리하는 방식을 알 수 있었습니다
    public int quiz3() throws IOException {
        List<String[]> csvLines = readCsvLines();
        return csvLines.stream()
                .map(line -> line[2])
                .map(text -> likeCount(text, 0))
                .reduce(0, Integer::sum);
    }
    
    private int likeCount(String text, int from) {
        final String TARGET = "좋아";
        final int TARGET_LENGTH = TARGET.length();
    
        int idx = text.indexOf(TARGET, from);
        if(idx >= 0) {
            return 1 + likeCount(text, idx + TARGET_LENGTH);
        }
    
        return 0;
    }

     

     

    Quiz2

    2-1. List 에 저장된 단어들의 접두사가 각 몇개인지 집계

    - 인스턴스 메서드 *.substring(int from, int to) 사용하여 첫 글자 뽑음 

    - 마찬가지로 Collectors.toMap() 최종 연산으로 집계 처리 

    (이때 Function.identity()는 각 element 뜻함 => substring으로 뽑은 단어 첫 글자)

    public Map<String, Integer> quiz1() {
        return WORDS.stream()
                .map(word -> word.substring(0, 1))
                .collect(Collectors.toMap(Function.identity(), e -> 1, Integer::sum));
    }

     

    2-2. List에 저장된 단어 중 단어 길이가 2 이상인 경우에, 단어 첫 글자를 대문자로 변환하여 공백으로 연결처리

    중간 연산

    - filter()로 단어 길이 2 이상인 요소만 거름 

    - map()에서 element 별 첫 글자를 뽑기 위해 substring 처리 

    - String 클래스의 toUpperCase 사용하여 대문자 변환 (인스턴스 메서드)

    - distinct() 로 중복 element 제거

     

    최종 연산 

    - Collectors.joining(" ") 으로 문자열 연결

    public String quiz2() {
        return WORDS.stream()
                .filter(word -> word.length() >= 2)
                .map(word -> word.substring(0, 1))
                .map(String::toUpperCase)
                .distinct()
                .collect(Collectors.joining(" "));
    }

     

    Quiz3

    3-1. 모든 숫자 쌍의 배열 리스트를 반환

    map을 사용할 경우 Stream<Stream<Integer[]>> 형이 반환 되기 때문에 flatMap 사용하여야 한다

    stream 안에서 stream으로 요소를 생성하는 방식은 이번에 처음 알게 되었다
    public List<Integer[]> quiz1() {
        return numbers1.stream()
                .flatMap(n1 -> numbers2.stream().map(n2 -> new Integer[] {n1, n2})) // Steam<Integer[]>
                .collect(Collectors.toList());
    }

     

    3-2. 모든 숫자 쌍의 곱에서 가장 큰 값 반환 

    - 3-1 과 같이 모든 숫자 쌍을 구함 

    - 중간 연산 map() 으로 숫자 쌍의 곱으로 element 변환 

    - 최종 연산 reduce() 로 최대값 구함 

    public int quiz2() {
        return numbers1.stream()
                .flatMap(n1 -> numbers2.stream().map(n2 -> new Integer[] {n1, n2}))
                .map(n -> n[0] * n[1])
                .reduce(Integer.MIN_VALUE, Math::max); // IntStream 처리 방식도 있음
    }

     

     

    Quiz4

    4-1. 2020년에 일어난 모든 거래 내역을 찾아, 거래값 기준 오름차순 정렬

    - 중간 연산 filter() 로 2020년 거래 element만 뽑음 

    - 중간 연산 sorted() 로 거래값 기준 오름차순 정렬 수행

    - 최종 연산 collect()

    public List<Transaction> quiz1() {
        return transactions.stream()
                .filter(t -> t.getYear() == 2020)
                .sorted(Comparator.comparingInt(Transaction::getValue))
                .collect(toList());
    }

     

    4-2. 거래 내역이 있는 거래자가 근무하는 모든 도시를 중복없이 나열하라

    - 중간 연산 map()으로 거래자의 도시를 뽑음 

    - 중간 연산 distinct()로 중복 제거 

    public List<String> quiz2() {
        return transactions.stream()
                .map(t -> t.getTrader().getCity())
                .distinct()
                .collect(toList());
    }

     

    4-3. 서울 근무하는 거래자를 찾아 이름 순으로 정렬하라

    - 중간 연산 map()으로 Trader 뽑음 

    - 중간 연산 filter()로 서울 근무자 필터링 

    - 중간 연산 distinct()로 중복 제거 

    - 중간 연산 sorted()로 거래자 이름 순으로 정렬

    public List<Trader> quiz3() {
        return transactions.stream()
                .map(Transaction::getTrader)
                .filter(t -> "Seoul".equals(t.getCity()))
                .distinct()
                .sorted(Comparator.comparing(Trader::getName))
                .collect(toList());
    }

     

    4-4. 모든 거래자의 이름을 정렬 후 구분자(",")로 연결하라

    - 구분자 연결의 경우 최종 연산에서 Collectors.joining() 사용

    public String quiz4() {
        return transactions.stream()
                .map(t -> t.getTrader().getName())
                .distinct()
                .sorted()
                .collect(joining(","));
    }

     

    4-5. 부산 근무하는 거래자 여부 확인 

    boolean 형을 리턴하는 최종연산 allMatch, noneMatch, anyMatch 중에 anyMatch를 사용하면 되는 문제

    public boolean quiz5() {
        return transactions.stream()
                .map(t -> t.getTrader().getCity())
                .anyMatch("Busan"::equals);
    }

     

    4-6. 서울에 근무하는 거래자의 모든 거래 금액을 구하라

    - 서울 근무지에 대한 필터링 후 map() 으로 거래 금액으로 element 를 mapping하고 처리해도 되는데 Collectors.mapping()을 사용해 보았다

    public List<Integer> quiz6() {
        return transactions.stream()
                .filter(t -> "Seoul".equals(t.getTrader().getCity()))
                .collect(mapping(Transaction::getValue, toList()));
    }

     

    4-7. 모든 거래 내역 중 거래 금액의 최대값, 최소값을 구하라 (이때 최대값은 reduce, 최소값은 stream min 활용할 것)

    - {최대값, 최소값} 결과 리턴 

    - min() 의 경우 IntStream으로 변환하기 위해 mapToInt 사용 (Integer -> int)

    public Integer[] quiz7() {
        int min = transactions.stream().mapToInt(Transaction::getValue).min().orElse(-1);
        int max = transactions.stream().map(Transaction::getValue).reduce(Integer.MIN_VALUE, Math::max);
    
        return new Integer[]{max, min};
    }

     

     

    Quiz5

    5-1. 모든 문자열 길이의 총합을 출력하여라

    - map()으로 String::length (instance method reference) 호출 하여 문자열 길이로 element mapping 처리 

    - mapToInt()로 IntStream 으로 변환 후 합계 구함

    // IntStream 
    public int quiz1() {
      return Stream.of(STRING_ARR)
                   .mapToInt(String::length) // IntStream
                   .sum(); // int
    }
    
    // reduce 
     public int quiz1() {
        return Stream.of(STRING_ARR)
                .map(String::length)
                .reduce(0, Integer::sum);
    }

     

    5-2. 문자열 길이 중 가장 긴 것의 길이를 출력하시오

    문자열을 길이로 변환 -> IntStream 변환 후 max 호출

    public int quiz2() {
        return Stream.of(STRING_ARR)
                .map(String::length)
                .mapToInt(Integer::intValue) // IntStream
                .max() // OptionalInt
                .getAsInt();
    }
    
    
    // reduce
    public int quiz2() {
        return Stream.of(STRING_ARR)
                .map(String::length)
                .reduce(0, Math::max);
    }

     

    5-3. 임의 로또 번호 (1~45)를 정렬해서 출력하시오 

    처음에 IntStream.rangeClosed(1, 45) 사용하여 숫자를 생성했었는데, 로또라는 특성상 랜덤 번호를 만들어야 한다는 것을 누락해버렸다. 

    - Stream 생성 : Random 클래스로 1 ~ 45 사이에 있는 임의 숫자 생성

    - 중간 연산 distinct() elements 중복 제거 

    - 중간 연산 limit(6) 으로 6개의 elements 뽑음 

    - IntStream 이기 때문에 boxed() 통해 Stream<Integer>로 변환

    public List<Integer> quiz3() {
        return new Random().ints(1, 46)
                            .distinct()
                            .limit(6)
                            .sorted()
                            .boxed()
                            .collect(Collectors.toList());
    }

     

    5-4. 두 개의 주사위를 굴려서 나온 눈의 합이 6인 경우을 모두 출력하시오

    - flatMap()을 사용하여 경우를 구한다 ( {1, 1}, {1, 2}, {1, 3} ... , {2, 1}, {2, 2} .. )

    - IntStream으로 생성했기 때문에 boxed() 호출하여 Stream<Integer>로 변환한다

    - 나머지를 필터링을 통해 두 주사위의 합이 6인 경우만 구하면 된다

    public List<Integer[]> quiz4() {
        return IntStream.rangeClosed(1, 6)
                .boxed()
                .flatMap(i -> IntStream.rangeClosed(1, 6).boxed().map(j -> new Integer[] {i , j}))
                .filter(n -> n[0] + n[1] == 6)
                .collect(Collectors.toList());
    }

     

     

    Quiz6

    6-1. 불합격(150점 미만) 학생 수를 남자와 여자로 구별하여라

    - 반환 타입을 Map<Boolean, List<Student>> 를 보면 알 수 있듯이 Collectors.partitioningBy() 최종 연산을 활용하는 문제이다

    - partitioningBy() 에서 성별을 기준으로 요소를 List에 담아 반환 처리 한다

    Collectors.partitioningBy : 2분할로 그룹 나눔 
    Collectors.groupingBy : n 분할로 그룹 나눔
    public Map<Boolean, List<Student>> quiz1() {
        return Stream.of(stuArr)
                .filter(s -> s.getScore() < 150)
                .collect(partitioningBy(Student::isMale, toList()));
    }

     

    6-2. 각 반별 총점을 학년 별로 나누어 구하여라 

    - 결과 형식 {학년 : {반별 : 총점} ..., }

    - Collectors.groupingBy() 최종 연산을 활용하는 문제이다

    public Map<Integer, Map<Integer, Integer>> quiz2() { 
        return Stream.of(stuArr)
                .collect(groupingBy(
                        Student::getHak, // 학년
                        groupingBy(Student::getBan, reducing(0, Student::getScore, Integer::sum)) // 반별, 점수 총합
                ));
    }

    해당 문제를 처음 접했을 때 막히는 문제도 있었고, 시간도 꽤 오래 걸렸지만

    Stream API 에서 모르거나 부족한 부분에 대해 알 수 있어 좋았다. 

     

    실무에서도 Stream API 가끔 사용하고 있었지만, 잘 알지는 못하는 상태여서

    최근 API 문서로 공부를 따로 했었는데 

    이번 문제 풀이 통해 좋은 경험을 해볼 수 있어 뜻 깊은 시간이 아니었나 싶다.

     

    앞으로도 API 문서를 읽고 직접 테스트 학습을 해보는 습관을 계속 이어가야겠다.

     

     

    (감사합니다. 망나니 개발자의 기술 블로그)

    https://mangkyu.tistory.com/116

     

    [Java] Stream API 연습문제 풀이 (5/5)

    이번에는 Stream API를 연습해볼만한 문제를 제공해보고자 합니다. 자동화된 테스트를 통해 정답을 확인하도록 제공하고 있으니 직접 문제를 풀어서 정답을 확인해보실 분들은 아래 내용을 참고

    mangkyu.tistory.com

     

    반응형