값별 TreeMap 정렬


137

기본 자연 순서 대신 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비교기에 전달할 수 있습니까 ?


아마도 이것은 당신이 stackoverflow.com/questions/109383/…에
Inv3r53


답변:


179

사양 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"!!!

관련 질문


5
map.put ( "D", 2)를 추가하면; 결과는 여전히 "[C = 1, B = 2, A = 3]"이고 "[C = 1, B = 2, D = 2, A = 3]"이 아님
Igor Milla

3
수정 : int res = e2.getValue (). compareTo (e1.getValue ()); return res! = 0? 입술 : 1;
beshkenadze

new Integer (0) == new Integer (0) 객체에 대한 참조를 비교하고 두 개의 객체를 만듭니다! 출력은 절대적으로 정확하고 예측 가능합니다.
Yuriy Chernyshov

2
"일반적으로 말하면, 맵의 항목을 값으로 정렬 할 필요성은 비정형적인 것입니다."저는 초보자이지만 매우 일반적인 일이라고 생각합니까? 예를 들어지도에서 가장 오래된 3 명을 검색하려면 { "ana"= 37, "peter"= 43, "john"= 4, "mary"= 15, "Matt"= 78}
fartagaintuxedo

@igormilla 코드가 작동하는 간단한 이유가 있습니다. Autoboxing은 Integer.valueOf를 사용합니다. 그리고 그것은 -128에서 127까지의 인스턴스 캐시를 가지고 있습니다!
jokster

75

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;
    }

2
-1. AFASIK Set구현에 요소가 두 번 이상 포함되어 있지 않습니다 . 당신은 그 제약을 위반했습니다. 로부터 SortedSetAPI : 주 소트 세트에 의해 유지되는 순서는 것을 equals와 일관성이 있어야합니다 ... . 해결책은 List구현 으로 변경하는 것입니다.
dacwe

4
@dacwe 같은 키가 아닌 값
bellum

1
@ bellum : 우리는 Set여기 에 대해 이야기하고 있습니다. 는 이후 Comparator위반하는 Set계약을 Set.remove, Set.contains이 작동하지 않음 ! 이 예제를 ideone에서 확인하십시오 .
dacwe

3
참고 int res= e1.getValue().compareTo(e2.getValue());로 전환 int res= e2.getValue().compareTo(e1.getValue());하면 오름차순 대신 내림차순으로 정렬됩니다.
tugcem

6
차라리 res != 0 ? res : e1.getKey().compareTo(e2.getKey())같은 값을 가진 키의 순서를 유지하기 위해 돌아갑니다 .
Marco Lackovic

46

자바 8 :

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

17

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

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


10

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


2
TreeMap이 키를 Comparator에만 전달하는 경우 Comparator의 생성자에서 TreeMap의 참조를 만든 다음 키를 사용하여 값을 가져옵니다. (참조로 전달하는 방법을 잘 모르겠습니다) : class byValue Comparator를 구현합니다. {TreeMap theTree; public byValue (TreeMap theTree) {this.theTree = theTree; } public int compare (String k1, String k2) {// TreeMap의 getKey 메소드를 값으로 사용}}
vito huang

1
@vito : 아니요. 일반적으로 두 키 중 하나가 아직 맵에없고 값을 얻을 수 없기 때문입니다.
Joachim Sauer

이것이 TreeMap의 Comparator 내에서 이것을하는 예제가 아닙니까? (그러나 위에서 언급했듯이 SortedMap키별로 정렬을 지정 하는 사양을 위반 함 ) beginnersbook.com/2014/07/…
Marcus

@Marcus : 기존 지도를 Comparator사용 하여 값을 정렬합니다. 즉, 나중에 넣은 임의의 값을 정렬 할 수 없으며 원래지도에 이미있는 값만 정렬 할 수 있습니다 . TreeMap
Joachim Sauer

5

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 )

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


3

이 게시물은 구체적으로 값별로 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));

2

많은 사람들이 List를 사용하라는 조언을 듣고 그것을 사용하는 것을 선호합니다.

다음은 값에 따라지도 항목을 정렬하는 데 필요한 두 가지 방법입니다.

    static final Comparator<Entry<?, Double>> DOUBLE_VALUE_COMPARATOR = 
        new Comparator<Entry<?, Double>>() {
            @Override
            public int compare(Entry<?, Double> o1, Entry<?, Double> o2) {
                return o1.getValue().compareTo(o2.getValue());
            }
        };

        static final List<Entry<?, Double>> sortHashMapByDoubleValue(HashMap temp)
        {
            Set<Entry<?, Double>> entryOfMap = temp.entrySet();

            List<Entry<?, Double>> entries = new ArrayList<Entry<?, Double>>(entryOfMap);
            Collections.sort(entries, DOUBLE_VALUE_COMPARATOR);
            return entries;
        }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.