18

Сортировка Map<Key, Value> по значениям

16

Я столкнулся с проблемой сортировки объекта типа <code>Map<Key, Value></code> по его значениям. Поскольку значения не уникальны, я вынужден преобразовывать keySet в массив и затем сортировать этот массив с помощью sort с кастомным компаратором, который сортирует по значению, связанному с ключом.

Есть ли более простой способ решения этой задачи?

5 ответ(ов)

10

Вот версия на русском языке в стиле ответа на 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.

Как это работает:

  1. Создается список пар (ключ-значение) из входной карты с помощью map.entrySet().
  2. Этот список сортируется по значениям с использованием Entry.comparingByValue().
  3. Затем создается новая карта (типа LinkedHashMap), чтобы сохранить порядок элементов.
  4. В цикле все пары из отсортированного списка добавляются в новую карту.
  5. Наконец, метод возвращает отсортированную карту.

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

4

Важное примечание:

Этот код может ломаться несколькими способами. Если вы собираетесь использовать предложенный код, обязательно прочитайте комментарии, чтобы быть в курсе возможных последствий. Например, значения больше нельзя будет извлечь по их ключу. (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}

Помните о том, что данный подход имеет свои ограничения и потенциальные проблемы.

1

Вы можете использовать следующий метод для сортировки 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;
}

Как это работает:

  1. Сначала мы получаем набор записей (entries) из переданного Map и помещаем их в список.
  2. Затем сортируем этот список с помощью Collections.sort(), используя компаратор, который сравнивает значения каждой записи.
  3. После сортировки мы создаем новый LinkedHashMap, чтобы сохранить порядок записи. В цикле мы добавляем отсортированные записи обратно в новый Map.
  4. В итоге метод возвращает Map, отсортированный по значениям.

Примечания:

  • Обратите внимание на использование Comparable<V> для сравнения значений. Убедитесь, что значения в вашем Map реализуют интерфейс Comparable, иначе это приведет к ClassCastException.
  • Если возможно наличие null значений, стоит добавить проверку на них в компаратор.

Этот код подойдет в ситуации, когда вам нужно отсортировать Map по значениям. Если у вас есть дополнительные вопросы или нужна помощь с реализацией, пожалуйста, дайте знать!

0

Сортировка ключей требует, чтобы компаратор выполнял поиск каждого значения для каждой сравнительной операции. Более масштабируемое решение будет использовать 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();

Наконец, если вам нужно постоянно получать доступ к отсортированной информации (а не просто сортировать ее время от времени), вы можете использовать дополнительный мультимап. Дайте мне знать, если вам нужны дополнительные детали...

0

Я посмотрел на предложенные ответы, но многие из них более сложные, чем это нужно, или удаляют элементы карты, если несколько ключей имеют одно и то же значение.

Вот решение, которое, на мой взгляд, больше подходит:

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

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь