목차
출처 (망나니 개발자 기술 블로그)
https://mangkyu.tistory.com/116
*자료 받으실 때 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' 카테고리의 다른 글
[넥스트스탭] 숫자 야구 게임 - 자바 플레이 그라운드 (0) | 2024.04.30 |
---|---|
[Java] Reflection API 테스트 학습(with Baeldung) (0) | 2023.11.20 |
[Java] Stream 최종 연산(Terminal Operation) (0) | 2023.08.05 |
[Java] Stream 중간 연산 (Stream Intermediate Operation) (0) | 2023.08.03 |
[Java] Stream 생성 (파일 데이터 제외) (0) | 2023.08.03 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!