[Java] map.entrySet()

2 분 소요

map.entrySet()

프로그래머스의 위클리 챌린지 4주차 문제를 풀다가 슬프게도(?) 저의 code reviewer인 sonar lint가 한가지 제안을 했습니다.

직군-(언어-점수에 대한 key-value HashMap)에 대한 key-value HashMap을 만들고 개발자의 사용 언어를 순회하며 직군별 점수를 계산했습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.HashMap;
import java.util.Map;

class Solution {
  public String solution(String[] table, String[] languages, int[] preference) {
    HashMap<String, HashMap<String, Integer>> map = new HashMap<>();
    for (String row : table) {
      String[] r = row.split(" ");
      map.put(r[0], new HashMap<>());
      for (int i = 5; i > 0; i--) {
        map.get(r[0]).put(r[6 - i], i);
      }
    }

    int bestScore = 0;
    String bestJob = null;

    for (String job : map.keySet()) {
      int jobScore = 0;
      for (int i = 0; i < languages.length; i++) {
        jobScore += preference[i] * map.get(job).getOrDefault(languages[i], 0);
      }
      if (jobScore > bestScore) {
        bestScore = jobScore;
        bestJob = job;
      } else if (jobScore == bestScore && bestJob.compareTo(job) > 0) {
        bestJob = job;
      }
    }
    return bestJob;
  }
}

처음에는 이렇게 코드를 짰었는데 sonar lint가 18번째 줄의 for문에 노란 물결 무늬를 띄우며 한가지 제안을 했습니다.

“entrySet()” should be iterated when both the key and value are needed

Code smell     Major     java:S2864

When only the keys from a map are needed in a loop, iterating the keySet makes sense. But when both the key and the value are needed, it’s more efficient to iterate the entrySet, which will give access to both the key and value, instead.

Map을 순회할 때 key만 필요하다면 keySet을 사용하는 것이 지당하지만 key와 value모두 필요하다면 key와 value를 모두 제공하는 entrySet을 순회하는 것이 더 효율적이라고 합니다.

해당 제안을 본 순간 java의 enhanced for loop을 처음 보았을 때와 같은 기분을 느꼈습니다.

자바에서도 코드를 예쁘게 짤 수 있겠구나.

1
2
for key, value in dict.items():
  ...

위 코드와 같이 지금은 잘 기억도 나지 않는 파이썬처럼 간편하게 사용하는 iteration을 기대했습니다.

아래는 keySet 대신 entrySet을 사용한 코드입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static String solution(String[] table, String[] languages, int[] preference) {
  HashMap<String, HashMap<String, Integer>> map = new HashMap<>();
  for (String row : table) {
    String[] r = row.split(" ");
    map.put(r[0], new HashMap<>());
    for (int i = 5; i > 0; i--) {
      map.get(r[0]).put(r[6 - i], i);
    }
  }

  int bestScore = 0;
  String bestJob = null;

  for (Map.Entry<String, HashMap<String, Integer>> entry: map.entrySet()) {
    int jobScore = 0;
    for (int i = 0; i < languages.length; i++) {
      jobScore += preference[i] * entry.getValue().getOrDefault(languages[i], 0);
    }
    if (jobScore > bestScore) {
      bestScore = jobScore;
      bestJob = entry.getKey();
    } else if (jobScore == bestScore && bestJob.compareTo(entry.getKey()) > 0) {
      bestJob = entry.getKey();
    }
  }
  return bestJob;
}

저의 기대와는 달리 모양새가 크게 예뻐진것 같지는 않습니다.

오히려 코드가 더 보기 힘들어진것 같은 기분이 드네요^^;
그냥 파이썬이 보기 좋은 거였습니다^^*

그렇다면 sonar lint가 말해준 것 처럼 성능적으로 효율적인지 찾아보았습니다.


[StackOverflow] Performance considerations for keySet() and entrySet() of Map

위 글에서 좋은 답변들을 찾을 수 있었습니다.

위 제가 작성한 코드에서도 볼 수 있다시피, keySet()을 사용한다면 매 loop마다 map.get(key)를 실행시켜야 합니다.

HashMap이라 get 메서드가 O(1)일지라도 그 내부에서는 hashCode()와 equals()메서드가 한번씩 실행되고 있는 것입니다.

entrySet을 사용하면 key와 value를 한번에 가지고 있는 Entry에 대한 set을 순회하여 성능을 향상 시킬 수 있었습니다.




소스 코드를 확인해보았지만 entrySet()이 어떻게 작동하는지, HashMap의 entrySet이 언제 생성되는지, 각 Entry들이 언제 어떻게 생성되는지 잘 이해가 가지 않아 조금 더 공부를 해야할 것 같습니다.

Entry 또한 유용하게 쓰일 수 있는 것 같아 더 학습을 진행해야할 것 같습니다.

https://www.baeldung.com/java-map-entry

댓글남기기