[java] 값별 TreeMap 정렬

기본 자연 순서 대신 TreeMap을 값으로 정렬 할 수있는 비교기를 작성하고 싶습니다.

나는 이런 식으로 시도했지만 무엇이 잘못되었는지 알 수 없다 :

import java.util.*;

class treeMap {
    public static void main(String[] args) {
        System.out.println("the main");
        byValue cmp = new byValue();
        Map<String, Integer> map = new TreeMap<String, Integer>(cmp);
        map.put("de",10);
        map.put("ab", 20);
        map.put("a",5);

        for (Map.Entry<String,Integer> pair: map.entrySet()) {
            System.out.println(pair.getKey()+":"+pair.getValue());
        }
    }
}

class byValue implements Comparator<Map.Entry<String,Integer>> {
    public int compare(Map.Entry<String,Integer> e1, Map.Entry<String,Integer> e2) {
        if (e1.getValue() < e2.getValue()){
            return 1;
        } else if (e1.getValue() == e2.getValue()) {
            return 0;
        } else {
            return -1;
        }
    }
}

나는 내가 무엇을 요구하는지 추측한다 : 나는 Map.Entry비교기에 전달할 수 있습니까 ?



답변

사양 TreeMap을 무시하기 때문에 값 자체를 정렬 할 수 없습니다 SortedMap.

Map이 더는 제공 총 주문 의에 키를 .

그러나 외부 컬렉션을 사용하면 Map.entrySet()키, 값 또는 두 가지 조합 (!!)으로 언제든지 원하는대로 정렬 할 수 있습니다 .

여기에 반환하는 일반적인 방법이다 SortedSetMap.Entry, 주어진 Map, 값이이 Comparable:

static <K,V extends Comparable<? super V>>
SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
    SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
        new Comparator<Map.Entry<K,V>>() {
            @Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
                int res = e1.getValue().compareTo(e2.getValue());
                return res != 0 ? res : 1;
            }
        }
    );
    sortedEntries.addAll(map.entrySet());
    return sortedEntries;
}

이제 다음을 수행 할 수 있습니다.

    Map<String,Integer> map = new TreeMap<String,Integer>();
    map.put("A", 3);
    map.put("B", 2);
    map.put("C", 1);

    System.out.println(map);
    // prints "{A=3, B=2, C=1}"
    System.out.println(entriesSortedByValues(map));
    // prints "[C=1, B=2, A=3]"

더 이상 원래 맵의 “보기”가 아니기 때문에 SortedSet자체 또는 Map.Entry내부 를 수정하려고하면 펑키 한 일이 발생합니다 entrySet().

일반적으로지도의 항목을 값으로 정렬해야하는 것은 비정형입니다.


==대한 참고 사항Integer

원본 비교기는를 Integer사용하여 비교합니다 ==. 때문에 이것은 거의 항상 잘못 ==Integer피연산자 참조 평등, 값이 아닌 평등이다.

    System.out.println(new Integer(0) == new Integer(0)); // prints "false"!!!

관련 질문


답변

polygenelubricants 답변은 거의 완벽합니다. 그래도 중요한 버그가 하나 있습니다. 값이 동일한 맵 항목은 처리하지 않습니다.

이 코드 : …

Map<String, Integer> nonSortedMap = new HashMap<String, Integer>();
nonSortedMap.put("ape", 1);
nonSortedMap.put("pig", 3);
nonSortedMap.put("cow", 1);
nonSortedMap.put("frog", 2);

for (Entry<String, Integer> entry  : entriesSortedByValues(nonSortedMap)) {
    System.out.println(entry.getKey()+":"+entry.getValue());
}

출력 :

ape:1
frog:2
pig:3

유인원과 값이 “1”을 공유하면서 소가 어떻게 사라 졌는지 주목하십시오.

이 코드 수정은 해당 문제를 해결합니다.

static <K,V extends Comparable<? super V>> SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
        SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
            new Comparator<Map.Entry<K,V>>() {
                @Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
                    int res = e1.getValue().compareTo(e2.getValue());
                    return res != 0 ? res : 1; // Special fix to preserve items with equal values
                }
            }
        );
        sortedEntries.addAll(map.entrySet());
        return sortedEntries;
    }


답변

자바 8 :

LinkedHashMap<Integer, String> sortedMap =
    map.entrySet().stream().
    sorted(Entry.comparingByValue()).
    collect(Collectors.toMap(Entry::getKey, Entry::getValue,
                             (e1, e2) -> e1, LinkedHashMap::new));


답변

A TreeMap항상 키로 정렬되며 다른 것은 불가능합니다. A는 Comparator단지 당신이 제어 할 수있는 방법 키가 분류되어 있습니다.

정렬 된 값을 원하면 값을 추출하여 List정렬해야합니다.


답변

Comparator는 항상 지도 의 를 비교하므로을 사용하여 수행 할 수 없습니다 . TreeMap키로 만 정렬 할 수 있습니다.


답변

Olof의 대답은 좋지만 완벽하기 전에 한 가지 더 필요합니다 . 그의 답변 아래 주석에서 dacwe는 (정확하게) 그의 구현이 세트에 대한 비교 / 균등 계약을 위반한다는 것을 지적합니다. 세트에있는 항목에 대해 호출을 포함하거나 제거하려고하면 세트에 동일한 값을 가진 항목을 배치 할 수있는 코드로 인해 세트가이를 인식하지 못합니다. 따라서이 문제를 해결하려면 키 사이의 동등성을 테스트해야합니다.

static <K,V extends Comparable<? super V>> SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
    SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
        new Comparator<Map.Entry<K,V>>() {
            @Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> e2) {
                int res = e1.getValue().compareTo(e2.getValue());
                if (e1.getKey().equals(e2.getKey())) {
                    return res; // Code will now handle equality properly
                } else {
                    return res != 0 ? res : 1; // While still adding all entries
                }
            }
        }
    );
    sortedEntries.addAll(map.entrySet());
    return sortedEntries;
}

“정렬 된 비교가 제공되는지 여부에 관계없이 정렬 된 집합이 유지 관리하는 순서는 정렬 된 집합이 Set 인터페이스를 올바르게 구현하는 경우 같음과 일치해야합니다. Set 인터페이스는 equals 연산의 관점에서 정의됩니다. 하지만 정렬 된 세트는 compareTo (또는 compare) 메소드를 사용하여 모든 요소 비교를 수행하므로이 메소드에서 동일한 것으로 간주되는 두 요소는 정렬 된 세트의 관점에서 equal 입니다. ” ( http://docs.oracle.com/javase/6/docs/api/java/util/SortedSet.html )

세트가 동일한 값을 가진 항목을 추가하도록 강제하기 위해 원래 동등성을 간과 했으므로 이제는 세트에서 실제로 원하는 항목을 반환하기 위해 키의 동등성을 테스트해야합니다. 이것은 다소 지저분하며 세트가 어떻게 사용되도록 의도 된지는 아니지만 확실히 작동합니다.


답변

이 게시물은 구체적으로 값별로 TreeMap을 정렬하도록 요구하지만 실제로 구현에 신경 쓰지 않지만 요소가 추가 될 때 컬렉션을 정렬하는 솔루션을 원하는 사람들에게는 TreeSet 기반에 대한 피드백을 보내 주셔서 감사합니다 해결책. 하나는 요소가 키로 쉽게 검색되지 않지만 사용 사례 (n 키를 가장 낮은 값으로 찾기)의 경우 이는 필수 사항이 아닙니다.

  TreeSet<Map.Entry<Integer, Double>> set = new TreeSet<>(new Comparator<Map.Entry<Integer, Double>>()
  {
    @Override
    public int compare(Map.Entry<Integer, Double> o1, Map.Entry<Integer, Double> o2)
    {
      int valueComparison = o1.getValue().compareTo(o2.getValue());
      return valueComparison == 0 ? o1.getKey().compareTo(o2.getKey()) : valueComparison;
    }
  });
  int key = 5;
  double value = 1.0;
  set.add(new AbstractMap.SimpleEntry<>(key, value));