Сортировка Map<Key, Value> по значениям
Я столкнулся с проблемой сортировки объекта типа <code>Map<Key, Value></code>
по его значениям. Поскольку значения не уникальны, я вынужден преобразовывать keySet
в массив и затем сортировать этот массив с помощью sort с кастомным компаратором, который сортирует по значению, связанному с ключом.
Есть ли более простой способ решения этой задачи?
5 ответ(ов)
Вот версия на русском языке в стиле ответа на StackOverflow:
public class MapUtil {
public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) {
List<Entry<K, V>> list = new ArrayList<>(map.entrySet());
list.sort(Entry.comparingByValue());
Map<K, V> result = new LinkedHashMap<>();
for (Entry<K, V> entry : list) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
}
Данный метод sortByValue
позволяет отсортировать элементы карты по значениям. Он принимает в качестве параметра Map<K, V>
, где K
— это тип ключей, а V
— тип значений, который должен реализовывать интерфейс Comparable
.
Как это работает:
- Создается список пар (ключ-значение) из входной карты с помощью
map.entrySet()
. - Этот список сортируется по значениям с использованием
Entry.comparingByValue()
. - Затем создается новая карта (типа
LinkedHashMap
), чтобы сохранить порядок элементов. - В цикле все пары из отсортированного списка добавляются в новую карту.
- Наконец, метод возвращает отсортированную карту.
Используя этот метод, вы можете легко получить карту, отсортированную по значениям ее элементов.
Важное примечание:
Этот код может ломаться несколькими способами. Если вы собираетесь использовать предложенный код, обязательно прочитайте комментарии, чтобы быть в курсе возможных последствий. Например, значения больше нельзя будет извлечь по их ключу. (get
всегда возвращает null
.)
Похоже, что это гораздо проще, чем все вышеописанное. Используйте TreeMap
следующим образом:
public class Testing {
public static void main(String[] args) {
HashMap<String, Double> map = new HashMap<String, Double>();
ValueComparator bvc = new ValueComparator(map);
TreeMap<String, Double> sorted_map = new TreeMap<String, Double>(bvc);
map.put("A", 99.5);
map.put("B", 67.4);
map.put("C", 67.4);
map.put("D", 67.3);
System.out.println("несортированная карта: " + map);
sorted_map.putAll(map);
System.out.println("результаты: " + sorted_map);
}
}
class ValueComparator implements Comparator<String> {
Map<String, Double> base;
public ValueComparator(Map<String, Double> base) {
this.base = base;
}
// Обратите внимание: этот компаратор накладывает порядок, который несовместим с
// equals.
public int compare(String a, String b) {
if (base.get(a) >= base.get(b)) {
return -1;
} else {
return 1;
} // возвращение 0 объединило бы ключи
}
}
Вывод:
несортированная карта: {D=67.3, A=99.5, B=67.4, C=67.4}
результаты: {D=67.3, B=67.4, C=67.4, A=99.5}
Помните о том, что данный подход имеет свои ограничения и потенциальные проблемы.
Вы можете использовать следующий метод для сортировки Map
по значениям. Данный код написан на Java и работает следующим образом:
private static <K, V> Map<K, V> sortByValue(Map<K, V> map) {
List<Entry<K, V>> list = new LinkedList<>(map.entrySet());
Collections.sort(list, new Comparator<Object>() {
@SuppressWarnings("unchecked")
public int compare(Object o1, Object o2) {
return ((Comparable<V>) ((Map.Entry<K, V>) (o1)).getValue()).compareTo(((Map.Entry<K, V>) (o2)).getValue());
}
});
Map<K, V> result = new LinkedHashMap<>();
for (Iterator<Entry<K, V>> it = list.iterator(); it.hasNext();) {
Map.Entry<K, V> entry = (Map.Entry<K, V>) it.next();
result.put(entry.getKey(), entry.getValue());
}
return result;
}
Как это работает:
- Сначала мы получаем набор записей (entries) из переданного
Map
и помещаем их в список. - Затем сортируем этот список с помощью
Collections.sort()
, используя компаратор, который сравнивает значения каждой записи. - После сортировки мы создаем новый
LinkedHashMap
, чтобы сохранить порядок записи. В цикле мы добавляем отсортированные записи обратно в новыйMap
. - В итоге метод возвращает
Map
, отсортированный по значениям.
Примечания:
- Обратите внимание на использование
Comparable<V>
для сравнения значений. Убедитесь, что значения в вашемMap
реализуют интерфейсComparable
, иначе это приведет кClassCastException
. - Если возможно наличие
null
значений, стоит добавить проверку на них в компаратор.
Этот код подойдет в ситуации, когда вам нужно отсортировать Map
по значениям. Если у вас есть дополнительные вопросы или нужна помощь с реализацией, пожалуйста, дайте знать!
Сортировка ключей требует, чтобы компаратор выполнял поиск каждого значения для каждой сравнительной операции. Более масштабируемое решение будет использовать entrySet
напрямую, поскольку в этом случае значение будет доступно сразу для каждого сравнения (хотя я не подкреплял это цифрами).
Вот обобщенная версия такого решения:
public static <K, V extends Comparable<? super V>> List<K> getKeysSortedByValue(Map<K, V> map) {
final int size = map.size();
final List<Map.Entry<K, V>> list = new ArrayList<Map.Entry<K, V>>(size);
list.addAll(map.entrySet());
final ValueComparator<V> cmp = new ValueComparator<V>();
Collections.sort(list, cmp);
final List<K> keys = new ArrayList<K>(size);
for (int i = 0; i < size; i++) {
keys.set(i, list.get(i).getKey());
}
return keys;
}
private static final class ValueComparator<V extends Comparable<? super V>>
implements Comparator<Map.Entry<?, V>> {
public int compare(Map.Entry<?, V> o1, Map.Entry<?, V> o2) {
return o1.getValue().compareTo(o2.getValue());
}
}
Есть способы уменьшить количество выделений памяти для приведенного решения. Первая ArrayList
, созданная с size
, может быть, например, переиспользована как возвращаемое значение; это потребует подавления некоторых предупреждений о дженериках, но может оправдать себя для переиспользуемого библиотечного кода. Также компаратор не нужно выделять заново при каждом вызове.
Вот более эффективная, хотя и менее привлекательная версия:
public static <K, V extends Comparable<? super V>> List<K> getKeysSortedByValue2(Map<K, V> map) {
final int size = map.size();
final List reusedList = new ArrayList(size);
final List<Map.Entry<K, V>> meView = reusedList;
meView.addAll(map.entrySet());
Collections.sort(meView, SINGLE);
final List<K> keyView = reusedList;
for (int i = 0; i < size; i++) {
keyView.set(i, meView.get(i).getKey());
}
return keyView;
}
private static final Comparator SINGLE = new ValueComparator();
Наконец, если вам нужно постоянно получать доступ к отсортированной информации (а не просто сортировать ее время от времени), вы можете использовать дополнительный мультимап. Дайте мне знать, если вам нужны дополнительные детали...
Я посмотрел на предложенные ответы, но многие из них более сложные, чем это нужно, или удаляют элементы карты, если несколько ключей имеют одно и то же значение.
Вот решение, которое, на мой взгляд, больше подходит:
public static <K, V extends Comparable<V>> Map<K, V> sortByValues(final Map<K, V> map) {
Comparator<K> valueComparator = new Comparator<K>() {
public int compare(K k1, K k2) {
int compare = map.get(k2).compareTo(map.get(k1));
if (compare == 0) return 1;
else return compare;
}
};
Map<K, V> sortedByValues = new TreeMap<K, V>(valueComparator);
sortedByValues.putAll(map);
return sortedByValues;
}
Обратите внимание, что карта сортируется от наибольшего значения к наименьшему.
Эффективный способ итерации по каждой записи в Java Map?
Как инициализировать статическую Map?
Как напрямую инициализировать HashMap (в литеральном виде)?
Инициализация ArrayList в одну строчку
Разница между <? super T> и <? extends T> в Java