Сортировка 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 переданный компаратору?

Ответ 1

Вы не можете иметь сортировку TreeMap для значений, поскольку это не соответствует спецификации SortedMap:

A Map, который далее обеспечивает полное упорядочение на клавишах.

Однако, используя внешнюю коллекцию, вы всегда можете сортировать Map.entrySet(), однако вы хотите либо с помощью клавиш, либо значений, либо даже комбинации (!!) двух.

Вот общий метод, который возвращает SortedSet of Map.Entry, учитывая a 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"!!!

Связанные вопросы

Ответ 2

Ответы на полигенные смазки почти идеальны. Однако у него есть одна важная ошибка. Он не будет обрабатывать записи карт, где значения одинаковы.

Этот код:...

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" с нашей обезьяной: O!

Эта модификация кода решает эту проблему:

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

Ответ 3

В Java 8:

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

Ответ 4

A TreeMap всегда сортируется по клавишам, все остальное невозможно. A Comparator просто позволяет вам контролировать, как сортируются ключи.

Если вам нужны отсортированные значения, вы должны извлечь их в List и отсортировать их.

Ответ 5

Это невозможно сделать с помощью Comparator, так как он всегда будет иметь ключ сопоставления карты. TreeMap может сортировать только ключ.

Ответ 6

Ответ Olof - это хорошо, но ему нужно еще одно, прежде чем оно станет совершенным. В комментариях ниже его ответа, dacwe (правильно) указывает, что его реализация нарушает контракт Compare/Equals для Sets. Если вы попытаетесь позвонить, содержит или удаляет запись, которая четко указана в наборе, этот набор не будет распознавать ее из-за кода, который допускает размещение записей с равными значениями в наборе. Итак, чтобы исправить это, нам нужно проверить равенство между ключами:

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), поэтому два элемента, которые считаются равными этому методу, с точки зрения сортированного набора равны." (http://docs.oracle.com/javase/6/docs/api/java/util/SortedSet.html)

Поскольку мы первоначально упускали из виду равенство, чтобы заставить набор добавлять одинаковые значения, теперь мы должны проверить равенство в ключах, чтобы набор фактически возвращал запись, которую вы ищете. Это довольно беспорядочно и определенно не то, как наборы предназначались для использования, но он работает.

Ответ 7

Я знаю, что этот пост специально запрашивает сортировку 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));

Ответ 8

Многие люди слышат советы использовать 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;
        }