본문 바로가기
멀티캠퍼스 풀스택 과정/Java의 정석

자바의 정석11-6 스트림의 그룹화와 분할(groupingBy, partitioningBy)

by 이쟝 2022. 2. 2.
  • 그룹화(groupingBy)는 스트림의 요소를 특정 기준으로 그룹화 하는 것을 의미한다.
  • 분할(partitioningBy)은 스트림의 요소를 두 가지, 즉 지정된 조건에 일치하는 그룹과 일치하지 않는 그룹으로의 분할을 의미한다.
  • partitioningBy( ) Predicate로, groupingBy( )는 스트림의 요소를 Function으로 분류한다.
  • partitioningBy( )는 스트림을 2분할 하고, groupingBy( )는 스트림을 n분할 한다. (둘 다 Collectors 클래스 안에 있다.)

1. 스트림의 분할 - paritioningBy()


-> 2분할을 할 때는 groupingBy( )보다는 partitioningBy( )



  • Comparator생략! (import static java.util.Comparator.comparingInt;)
  • maxBy( )는 반환타입이 Optional<Student>라서 위와 같은 결과가 나왔다. Optional<Student>가 아닌 Student를 반환결과로 얻으려면, collectingAndThen( )과 Optional::get을 함께 사용해야 한다. 
.collect(partitioningBy(Student::isMale,
 collectingAndThen(maxBy(comparingInt(Student::getScore), Optional::get)));

 

  • 1. 성별로 분할(남/녀)  2. 성적으로 분할(불합격/합격) 150점보다 낮으면 불합격(true) 
  • 150점보다 높다면 false

예제) 

 

import static java.util.Comparator.comparingInt;
import static java.util.stream.Collectors.*;
import java.util.*;
import java.util.stream.Stream;

public class Ex14_11 {

	public static void main(String[] args) {
		Student3[] stuArr = {
				new Student3("나자바", true, 1, 1, 300),
				new Student3("김자바", false, 1, 1, 250),
				new Student3("박자바", true, 1, 1, 200),
				new Student3("이자바", false, 1, 2, 150),
				new Student3("남자바", true, 1, 2, 100),
				new Student3("안자바", false, 1, 2, 50),
				new Student3("황자바", false, 1, 3, 100),
				new Student3("강자바", false, 1, 3, 150), 
				new Student3("이자바", true, 1, 3, 200), 
				new Student3("신자바", false, 2, 1, 300),
				new Student3("김자바", false, 2, 1, 250),
				new Student3("문자바", true, 2, 1, 200),
				new Student3("양자바", false, 2, 2, 100),
				new Student3("윤자바", true, 2, 2, 150),
				new Student3("손자바", true, 2, 2, 50),
				new Student3("고자바", false, 2, 3, 100),
				new Student3("황자바", false, 2, 3, 150),
				new Student3("정자바", true, 2, 3, 200)
		};
		
		System.out.printf("1. 단순분할(성별로 분할)%n");  
		Map<Boolean, List<Student3>> stuByGender = Stream.of(stuArr)
        				.collect(partitioningBy(Student3::isMale));
		

		List<Student3> maleStudent = stuByGender.get(true);    // 남학생들만 뽑아서 리스트로 
		List<Student3> femaleStudent = stuByGender.get(false); // 여학생들만 뽑아서 리스트로
		
		// 리스트 출력
		for(Student3 s : maleStudent) System.out.println(s);   // (1) 
		for(Student3 s : femaleStudent) System.out.println(s); // (1)
		
		System.out.printf("%n2. 단순분할 + 통계(성별 학생수)%n"); 
		Map<Boolean, Long> stuNumByGender = Stream.of(stuArr)
        				.collect(partitioningBy(Student3::isMale, counting()));
		
		System.out.println("남학생 수 : " + stuNumByGender.get(true));  // (2)
		System.out.println("여학생 수 : " + stuNumByGender.get(f확인alse)); // (2)
		
		System.out.printf("%n3. 단순분할 + 통계(성별1등)%n");  
		Map<Boolean, Optional<Student3>> topScoreByGender = Stream.of(stuArr)
				.collect(partitioningBy(Student3::isMale, 
                	maxBy(comparingInt(Student3::getScore))
                )); 
		
		System.out.println("남학생 1등 : " + topScoreByGender.get(true));  // (3)
		System.out.println("여학생 1등 : " + topScoreByGender.get(false)); // (3)
		
		// Optional에 있는 값을 꺼내서 출력
		Map<Boolean, Student3> topScoreByGender2 = Stream.of(stuArr)
				.collect(partitioningBy(Student3::isMale,
					collectingAndThen(maxBy(comparingInt(Student3::getScore)), 
                    Optional::get)
				));
		System.out.println("남핛생 1등 : " + topScoreByGender2.get(true));  // (4)
		System.out.println("여학생 1등 : " + topScoreByGender2.get(false)); // (4)

		System.out.printf("%n4. 다중분할(성별 불합격자, 100점 이하)%n");
		Map<Boolean, Map<Boolean, List<Student3>>> failedStuByGender = Stream.of(stuArr)
				.collect(partitioningBy(Student3::isMale, 
                	partitioningBy(s -> s.getScore() <= 100))
				);
		
		List<Student3> failedMaleStu = failedStuByGender.get(true).get(true);  
		List<Student3> failedFemaleStu = failedStuByGender.get(false).get(true); 
		
		for(Student3 s : failedMaleStu) System.out.println(s);    // (5)
		for(Student3 s : failedFemaleStu) System.out.println(s);  // (5)	
	}
}
class Student3 {  // 스트림으로 분할하기 위한 클래스 생성
	String name;
	boolean isMale;   // 성별
	int grade;          // 학년
	int ban;          // 반
	int score;
	
	Student3(String name, boolean isMale, int grade, int ban, int score) {
		this.name = name;
		this.isMale = isMale;
		this.grade = grade;
		this.ban = ban;
		this.score = score;
	}
	
	String getName() { return name; }
	boolean isMale() { return isMale; }
	int getGrade() { return grade; }
	int getBan() { return ban; }
	int getScore() { return score; }
	
	public String toString() {
		return String.format("[%s, %s, %d학년 %d반 %3d점]", name, 
							isMale ? "남":"여", grade, ban, score);
	}
}

 

더보기

(1) 1. 단순분할(성별로 분할)
[나자바, 남, 1학년 1반 300점]
[박자바, 남, 1학년 1반 200점]
[남자바, 남, 1학년 2반 100점]
...

-> Stream.of으로 stuArr를 스트림으로 바꾼 다음에 collect의 partitioningBy를 이용해서 성별로 나눴다.

(2) 2. 단순분할 + 통계(성별 학생수)
남학생 수 : 8
여학생 수 : 10

(3) 3. 단순분할 + 통계(성별1등)
남학생 1등 : Optional[[나자바, 남, 1학년 1반 300점]]
여학생 1등 : Optional[[신자바, 여, 2학년 1반 300점]]

(4)
남핛생 1등 : [나자바, 남, 1학년 1반 300점]
여학생 1등 : [신자바, 여, 2학년 1반 300점]

(5)

4. 다중분할(성별 불합격자, 100점 이하)
[남자바, 남, 1학년 2반 100점]
[손자바, 남, 2학년 2반  50점]
[안자바, 여, 1학년 2반  50점]
[황자바, 여, 1학년 3반 100점]
[양자바, 여, 2학년 2반 100점]
[고자바, 여, 2학년 3반 100점]


2. 스트림의 그룹화 - groupingBy()

(1) 반 별로 그룹화

  • groupingBy()로 그룹화를 하면 기본적으로 List<T>에 담는다. 그래서 Collectors.toList()생략 가능하다. 

 

(2) 다중 그룹화

  • groupingBy()를 여러번 사용하면 다중 그룹화 가능 1.학년 별 그룹화, 2.반 별 그룹화

 

(3) 조금 더 복잡한 다중 그룹화-1: stuStream을 성적의 등급(Student.Level)으로 그룹화

  •  모든 학생을 세 등급(HIGH, MID, LOW)으로 분류해서 그룹화한다음 Collectors.counting을 사용해서 학생들의 수를 카운팅했다. 

(4) 조금 더 복잡한 다중 그룹화-2: 학년 별, 반별로 그룹화 한다음 성적그룹으로 변환(mapping)하여 Set에 저장

  • 성적등급(Level)으로 변환. List<Student> -> Set<Student.Level>
  • 특별한 기준을 만들어서 사용할 때 mapping을 사용할 수 있다.

 (5) 각 반의 1등을 출력: 학년, 반별로 그룹화 한다음 collectingAndThen과 Optional::get을 사용

 

예제) 

 

import static java.util.Comparator.comparingInt;
import static java.util.stream.Collectors.*;
import java.util.*;
import java.util.stream.Stream;

public class Ex14_12 {
	public static void main(String[] args) {
		Student4[] stuArr = {
				new Student4("나자바", true, 1, 1, 300),
				new Student4("김자바", false, 1, 1, 250),
				new Student4("박자바", true, 1, 1, 200),
				new Student4("이자바", false, 1, 2, 150),
				new Student4("남자바", true, 1, 2, 100),
				new Student4("안자바", false, 1, 2, 50),
				new Student4("황자바", false, 1, 3, 100),
				new Student4("강자바", false, 1, 3, 150), 
				new Student4("이자바", true, 1, 3, 200), 
				new Student4("신자바", true, 2, 1, 300),
				new Student4("김자바", false, 2, 1, 250),
				new Student4("문자바", true, 2, 1, 200),
				new Student4("양자바", false, 2, 2, 100),
				new Student4("윤자바", true, 2, 2, 150),
				new Student4("손자바", false, 2, 2, 50),
				new Student4("고자바", false, 2, 3, 100),
				new Student4("황자바", false, 2, 3, 150),
				new Student4("정자바", true, 2, 3, 200)
		};

		System.out.printf("1. 단순그룹화(반별로 그룹화)%n");
		Map<Integer, List<Student4>> stuByBan = Stream.of(stuArr)
        					.collect(groupingBy(Student4::getBan));
		
		for(List<Student4> ban : stuByBan.values()) {
			for(Student4 s : ban) {
				System.out.println(s);   // (1)
			}
		}
		
		System.out.printf("%n2. 단순그룹화(성적별로 그룹화)%n");
		Map<Student4.Level, List<Student4>> stuByLevel = Stream.of(stuArr)
				.collect(groupingBy(s -> {
					if(s.getScore() >= 200)      return Student4.Level.HIGH;
					else if(s.getScore() >= 100) return Student4.Level.MID;
					else                         return Student4.Level.LOW;
				}));
		
		TreeSet<Student4.Level> keySet = new TreeSet<>(stuByLevel.keySet());
		
		for(Student4.Level key : keySet) {
			System.out.println("[" + key + "]");
			
			for(Student4 s : stuByLevel.get(key)) 
				System.out.println(s);     // (2)
			System.out.println();
		}
		
		System.out.printf("%n3. 단순그룹화 + 통계(성적별 학생수)%n");
		Map<Student4.Level, Long> stuCntByLevel = Stream.of(stuArr)
				.collect(groupingBy(s -> {
					if(s.getScore() >= 200)      return Student4.Level.HIGH;
					else if(s.getScore() >= 100) return Student4.Level.MID;
					else                         return Student4.Level.LOW;
				}, counting()));
		
		for(Student4.Level key : stuCntByLevel.keySet())
			System.out.printf("[%s]:%d명, ", key, stuCntByLevel.get(key));  // (3)
		System.out.println();

		System.out.printf("%n4. 다중그룹화(학년별, 반별)%n");
		Map<Integer, Map<Integer, List<Student4>>> stuByGradeAndBan = Stream.of(stuArr)
				.collect(groupingBy(Student4::getGrade, groupingBy(Student4::getBan)));
		
		for(Map<Integer, List<Student4>> grade : stuByGradeAndBan.values()) { // 학년별
			for(List<Student4> ban : grade.values()) {   // 반별
				System.out.println();
				for(Student4 s : ban)
					System.out.println(s);  // (4)
			}
		}
		
		System.out.printf("%n5. 다중그룹화 + 통계(학년별, 반별1등)%n");
		Map<Integer, Map<Integer, Student4>> topStuByGradeAndBan = Stream.of(stuArr)
				.collect(groupingBy(Student4::getGrade, groupingBy(Student4::getBan,
					collectingAndThen(maxBy(comparingInt(Student4::getScore)), 
                    Optional::get)
					)));
		
		for(Map<Integer, Student4> ban : topStuByGradeAndBan.values()) 
			for(Student4 s : ban.values())
				System.out.println(s);   // (5)
		
		System.out.printf("%n6. 다중그룹화 + 통계(학년별, 반별 성적그룹)%n");
		Map<String, Set<Student4.Level>>  stuByScoreGroup = Stream.of(stuArr)
			.collect(groupingBy(s -> s.getGrade() +  "학년" + s.getBan() + "반", 
				mapping(s-> {
					if(s.getScore() >= 200)      return Student4.Level.HIGH;
					else if(s.getScore() >= 100) return Student4.Level.MID;
					else 			     return Student4.Level.LOW;
					}, toSet())
				));
		
		Set<String> keySet2 = stuByScoreGroup.keySet();
		
		for(String key : keySet2) {
			System.out.println("["+key+"]" + stuByScoreGroup.get(key)); // (6)
		}

	} // main
}
class Student4 {
	String name;
	boolean isMale;   // 성별
	int grade;          // 학년
	int ban;          // 반
	int score;
	
	Student4(String name, boolean isMale, int grade, int ban, int score) {
		this.name = name;
		this.isMale = isMale;
		this.grade = grade;
		this.ban = ban;
		this.score = score;
	}
	
	String getName() { return name; }
	boolean isMale() { return isMale; }
	int getGrade() { return grade; }
	int getBan() { return ban; }
	int getScore() { return score; }
	
	public String toString() {
		return String.format("[%s, %s, %d학년 %d반 %3d점]", name, 
                           isMale ? "남":"여", grade, ban, score);
	}
	enum Level { HIGH, MID, LOW }  // 성적을 상, 중, 하 세 단계로 분류
}
더보기

(1) 1. 단순그룹화(반별로 그룹화)
[나자바, 남, 1학년 1반 300점]
[김자바, 여, 1학년 1반 250점]
[박자바, 남, 1학년 1반 200점]

.....

(2) 2. 단순그룹화(성적별로 그룹화)
[HIGH]
[나자바, 남, 1학년 1반 300점]
[김자바, 여, 1학년 1반 250점]
[박자바, 남, 1학년 1반 200점]
.....

[MID]
[이자바, 여, 1학년 2반 150점]
[남자바, 남, 1학년 2반 100점]
[황자바, 여, 1학년 3반 100점]
[강자바, 여, 1학년 3반 150점]
....

[LOW]
[안자바, 여, 1학년 2반  50점]
[손자바, 여, 2학년 2반  50점]

 

(3) 3. 단순그룹화 + 통계(성적별 학생수)
[MID]:8명, [LOW]:2명, [HIGH]:8명, 

 

(4) 4. 다중그룹화(학년별, 반별)

[나자바, 남, 1학년 1반 300점]
[김자바, 여, 1학년 1반 250점]
...


[이자바, 여, 1학년 2반 150점]
[남자바, 남, 1학년 2반 100점]
...

[황자바, 여, 1학년 3반 100점]
[강자바, 여, 1학년 3반 150점]
....


[신자바, 남, 2학년 1반 300점]
....

[양자바, 여, 2학년 2반 100점]
....


[고자바, 여, 2학년 3반 100점]
...

 

(5) 5. 다중그룹화 + 통계(학년별, 반별1등)
[나자바, 남, 1학년 1반 300점]
[이자바, 여, 1학년 2반 150점]
[이자바, 남, 1학년 3반 200점]
[신자바, 남, 2학년 1반 300점]
[윤자바, 남, 2학년 2반 150점]
[정자바, 남, 2학년 3반 200점]

 

(6) 6. 다중그룹화 + 통계(학년별, 반별 성적그룹)
[2학년1반][HIGH]
[2학년3반][MID, HIGH]
[2학년2반][MID, LOW]
[1학년3반][MID, HIGH]
[1학년2반][MID, LOW]
[1학년1반][HIGH]

-> 학년 반을 통째로 그룹핑하고 문자열