배열 인덱스가 노래의 고유 번호이고, 장르 목록과 플레이 수 목록이 주어졌을 때, 장르별로 가장 많이 재생된 노래를 두 개 씩 선택하는 문제이다.
- 속한 노래가 많이 재생된 장르를 먼저 수록
- 장르 내에서 많이 재생된 노래를 먼저 수록
- 장르 내에서 재생 횟수가 같은 노래 중에서는 고유 번호가 낮은 노래를 먼저 수록
장르에 속한 곡이 하나라면, 하나의 곡만 선택한다. 노래의 개수는 10000이하이므로 O(N^2) 내에서 해결한다.
public int[] solution(String[] genres, int[] plays) {
    Map<String, Integer> sumMap = new HashMap<>();
    for (int i = 0; i < genres.length; i++) {
        sumMap.put(genres[i], sumMap.getOrDefault(genres[i], 0) + plays[i]);
    }
    
    Map<String, List<Integer>> indexMap = IntStream.range(0, plays.length)
        .boxed()
        .sorted((o1, o2) -> plays[o2] - plays[o1])
        .collect(Collectors.groupingBy(integer -> genres[integer]));
    return indexMap
        .keySet()
        .stream()
        .sorted(Comparator.comparingInt(sumMap::get).reversed())
        .reduce(new ArrayList<>(), (List<Integer> integers, String s) -> {
            List<Integer> list = indexMap.get(s);
            integers.add(list.get(0));
            if (list.size() > 1) {
                integers.add(list.get(1));
            }
            return integers;
        }, (integers, integers2) -> {
            integers.addAll(integers2);
            return integers;
        })
        .stream()
        .mapToInt(value -> value)
        .toArray();
}
이번엔 자바8 stream을 사용해서 풀어봤다. 정렬하면서 요소 몇개를 선택하고 이런 연속적인 기능을 표현하는데 효과적인 것 같다. 위의 코드가 초기 코드였는데, 테스트는 통과했지만 만족스럽지가 않다.
 private int[] bestAlbum(String[] genres, int[] plays) {
     return IntStream.range(0, plays.length)
         .boxed()
         .collect(Collectors.groupingBy(id -> genres[id]))
         .entrySet()
         .stream()
         .sorted((e1, e2) -> sum(plays, e2.getValue()) - sum(plays, e1.getValue()))
         .flatMap(e -> e.getValue().stream().sorted((id1, id2) -> {
             if (plays[id2] == plays[id1]) return id1 - id2;
             return plays[id2] - plays[id1];
         }).limit(2))
         .mapToInt(value -> value)
         .toArray();
 }
private int sum(int[] plays, List<Integer> ids) {
    return ids.stream()
        .mapToInt(i -> plays[i])
        .sum();
}
IntStream.range를 사용해서 필요없는 Map을 없애고 keySet이 아닌 entrySet와 flatMap을 사용하여 복잡해보이는 컬렉션 생성 과정을 없앴다.
private int[] solution(String[] genres, int[] plays) {
    return IntStream.range(0, genres.length)
        .mapToObj(i -> new Music(i, genres[i], plays[i]))
        .collect(Collectors.groupingBy(Music::getGenre))
        .entrySet()
        .stream()
        .sorted((o1, o2) -> sum(o2.getValue()) - sum(o1.getValue()))
        .flatMap(e -> e.getValue().stream().sorted().limit(2))
        .mapToInt(m -> m.id).toArray();
}
private int sum(List<Music> value) {
    return value
        .stream()
        .reduce(0, (sum, music) -> sum + music.played, (sum1, sum2) -> sum1 + sum2);
}
class Music implements Comparable<Music> {
    int id;
    String genre;
    int played;
    public Music(int id, String genre, int play) {
        this.id = id;
        this.genre = genre;
        this.played = play;
    }
    @Override
    public int compareTo(Music o) {
        if(played == o.played) return id - o.id;
        return o.played - played;
    }
    public String getGenre() {
        return genre;
    }
}
비슷하게 stream을 사용하지만, 알아보기 힘든 배열 접근 코드를 없애고 객체와 Comparable 인터페이스를 활용하는 방법이다.
