1. 개요

Map 자료구조를 사용하는 경우가 많은데, 항상 정렬하는 부분에서 까먹고 구글링을 하게 된다. 답답해서 내 블로그에 정리한다.

또 저번에 문제 풀다가 key 하나에 value여러개를 저장해야하는 경우가 있었는데, Value를 List로 두어 구현하니 1 key, multi value가 가능했다. 이것도 까먹을 수 있으니 정리해두자.


2. Map 정렬

Map 자료구조를 정렬할 때 크게 두 가지 경우를 생각해 볼 수 있다.

  • Key를 기준으로 정렬
  • Value 기준으로 정렬

2-1. Key를 기준으로 정렬

Key를 기준으로 정렬할 때 TreeMap을 사용하면 된다. TreeMap은 저장할 때 Key 순서로 저장하도록 구현되어있다. 오름차순 정렬, 내림차순 정렬, 또는 다른 기준으로 정렬할 수 있는데 이 때 Comparator를 사용한다.

예시를 보자

package algorithm.map;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import org.junit.jupiter.api.Test;

public class MapSortTest {

  Map<Integer, String> numberCountryMap;

  MapSortTest() {
    numberCountryMap = new HashMap<>();
    numberCountryMap.put(82, "대한민국");
    numberCountryMap.put(1, "미국");
    numberCountryMap.put(33, "프랑스");
    numberCountryMap.put(7, "러시아");
    numberCountryMap.put(61, "호주");
    numberCountryMap.put(84, "베트남");
    numberCountryMap.put(81, "일본");
    numberCountryMap.put(886, "대만");
    numberCountryMap.put(44, "영국");
  }

  @Test
  public void mapEntireSortByAscendingKeyTest() {
    Map<Integer, String> keyAscendingMap = new TreeMap<>(Comparator.naturalOrder());
    //Map<Integer, String> keyAscendingMap = new TreeMap<>((o1, o2) -> o1.compareTo(o2));도 가능
    keyAscendingMap.putAll(numberCountryMap);
    System.out.println("---key ascending map ---");
    printMap(keyAscendingMap);
  }

}

생성자에서 구현한 numberCountryMap을 출력하면 저장한 순서대로 출력된다. 그러나 TreeMap으로 구현한 keyAscendingMap은 key 오름차순 순서로 출력된다. 생성자에 원하는 정렬 기준 Comparator를 입력으로 하면 된다.

기본 맵
key 오름차순 정렬

이번엔 내림차순으로 정렬한 것을 보자

package algorithm.map;

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import org.junit.jupiter.api.Test;

public class MapSortTest {

  //numberCountryMap은 오름차순 때와 같음

  @Test
  public void mapEntireSortByDescendingKeyTest() {
    Map<Integer, String> keyDescendingMap = new TreeMap<>(Comparator.reverseOrder());
    // Map<Integer, String> keyDescendingMap = new TreeMap<>((o1, o2) -> o2.compareTo(o1));도 가능
    keyDescendingMap.putAll(numberCountryMap);
    System.out.println("---key descending map---");
    printMap(keyDescendingMap);
  }
}

정렬 기준을 익명클래스로 Comparator를 생성하거나, 람다식을 이용해서 구현할 수도 있다.

key 내림차순 정렬

2-2. Value를 기준으로 정렬

Key를 기준으로 한 Map의 정렬은 TreeMap을 이용해서 쉽게 구현할 수 있다. 그렇다면 Value를 기준으로 한 정렬은 어떻게 구현할까?

Map.EntryList로 저장하여 value를 기준으로 정렬 한 후 LinkedHashMap에 저장한다.

예시로 쉽게 알아보자.

package algorithm.map;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import org.junit.jupiter.api.Test;

public class MapSortTest {

  // numberCountryMap은 위 예시들과 같음

  @Test
  public void mapEntireSortByAscendingValueTest() {
    List<Entry<Integer, String>> entryList = new ArrayList<>(numberCountryMap.entrySet());
    entryList.sort((o1, o2) -> o1.getValue().compareTo(o2.getValue()));
    // entryList.sort(Comparator.comparing(Entry::getValue));도 가능
    // entryList.sort(Entry.comparingByValue());도 가능

    Map<Integer, String> valueAscendingMap = new LinkedHashMap<>();
    for (Entry<Integer, String> entry : entryList) {
      valueAscendingMap.put(entry.getKey(), entry.getValue());
    }

    System.out.println("--- value ascending map ---");
    printMap(valueAscendingMap);
  }

  @Test
  public void mapEntireSortByDescendingValueTest() {
    List<Entry<Integer, String>> entryList = new ArrayList<>(numberCountryMap.entrySet());
    entryList.sort((o1, o2) -> o2.getValue().compareTo(o1.getValue()));

    Map<Integer, String> valueDescendingMap = new LinkedHashMap<>();
    for (Entry<Integer, String> entry : entryList) {
      valueDescendingMap.put(entry.getKey(), entry.getValue());
    }
    System.out.println("--- value descending map ---");
    printMap(valueDescendingMap);
  }

}

예시에서는 List를 정렬 후 LinkedHashMap에 저장했지만, 정렬 된Map을 구현하는 것이 아닌, 단순 출력 목적이라면 List까지만 구현해도 된다.

value 오름차순(가나다 순) 정렬
value 내림차순 정렬

2-3. 번외 - Key만 필요하거나, Value만 필요할 경우

그냥 EntryList를 구현하여 정렬하는것이 더 낫다고 생각하지만, 참고용으로 keySet()values()로 구현한것도 정리해둔다.

package algorithm.map;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import org.junit.jupiter.api.Test;

public class MapSortTest {

  // numberCountryMap은 위 예시들과 같음

  @Test
  public void keyListTest() {
    List<Integer> keys = new ArrayList<>(numberCountryMap.keySet());
    for (Integer key : keys) {
      System.out.println("Key : " + key);
    }
    System.out.println();
  }

  @Test
  public void valueListTest() {
    List<String> values = new ArrayList<>(numberCountryMap.values());
    for (String value : values) {
      System.out.println("Value : " + value);
    }
    System.out.println();
  }

}

key list
value list


3. one Key Multi Value

Spring에 MultiValueMap이 있기는 하지만 사용하기 위해선 라이브러리를 추가해주어야 한다.

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/MultiValueMap.html

 

MultiValueMap (Spring Framework 5.3.1 API)

default void addIfAbsent(K key, V value) Add the given value, only when the map does not contain the given key.

docs.spring.io

Spring 라이브러리 추가 없이 사용하기 위해 Map<Key, Value>에서 Value를 List로 사용한다.

package algorithm.map;

import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import org.junit.jupiter.api.Test;

public class MultiValueMapTest {

  Map<Character, List<String>> dictionaryMap = new TreeMap<>();

  @Test
  public void multiValueMapTest() {
    addWord("apple");
    addWord("dig");
    addWord("drug");
    addWord("banana");
    addWord("abc-Mart");
    addWord("africa");
    addWord("ace");
    addWord("big");
    addWord("boy");
    addWord("drum");
    addWord("cap");

    printSortedDictionaryMap(dictionaryMap);
  }

  private void printSortedDictionaryMap(Map<Character, List<String>> dictionaryMap) {
    for (Entry<Character, List<String>> entry : dictionaryMap.entrySet()) {
      System.out.println("--- " + entry.getKey() + " ---");

      List<String> words = entry.getValue();
      words.sort(Comparator.naturalOrder());

      for (int i = 0; i < words.size(); i++) {
        System.out.println(i + 1 + ". " + words.get(i));
      }
    }
  }

  private void addWord(String word) {
    List<String> words = dictionaryMap.containsKey(word.charAt(0))
        ? dictionaryMap.get(word.charAt(0))
        : new ArrayList<>();

    words.add(word);
    dictionaryMap.put(word.charAt(0), words);
  }
}

addWord()에서 Map에 key가 없다면 새 List를 생성하고, key가 있다면 key에 해당하는 List를 가져온다. 그리고 List에 word를 저장하고 ListMap에 저장한다.

printSortedDictionaryMap()에서 반복문으로 Entry를 가져와서 Key와 Value를 가져온다. Value는 List이므로 정렬 후 List를 돌며 word를 출력한다.

위의 정렬 예시와 마찬가지로 정렬된 Map으로 저장하고 싶다면 LinkedHashMap<Character, List<String>>으로 새 Map을 생성하여 정렬된 List를 순서대로 저장한다.

+ Recent posts