Сортуйте карту <Ключ, Значення> за значеннями


1634

Я порівняно новачок у Java, і часто виявляю, що мені потрібно сортувати Map<Key, Value>за значеннями.

Оскільки значення не є чимось унікальним, я вважаю себе перетворюючи keySetв arrayі сортування цього масиву з допомогою масиву роду з призначеної для користувача компаратором , який сортує за значенням , пов'язаному з ключем.

Чи є простіший спосіб?


24
Карта не призначена для сортування, а доступ до неї швидко. Об'єктні рівні значення порушують обмеження карти. Використовуйте набір записів, як, List<Map.Entry<...>> list =new LinkedList(map.entrySet())і Collections.sort ....саме так.
Ганнес

1
Випадок, коли це може виникнути, коли ми намагаємось використовувати лічильник на Java (Map <Object, Integer>). Сортування за кількістю подій було б тоді звичайною операцією. Мова на зразок Python має вбудовану структуру даних Counter. Для альтернативного способу реалізації в Java ось приклад
demongolem

6
Існує безліч випадків використання для відсортованих карт, тому у вас є TreeMap та ConcurrentSkipListMap в jdk.
alobodzk


1
TreeMap та ConcurrentSkipListMap сортувати за клавішами. Питання полягає в сортуванні за значенням.
Петро

Відповіді:


899

Ось універсальна версія:

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

10
Радий, що це допомагає. Джон, LinkedHashMap важливий для рішення, оскільки він забезпечує передбачуваний порядок ітерації.
Carter Page

3
@ buzz3791 Щоправда. Так буде в будь-якому алгоритмі сортування. Зміна значення вузлів у структурі під час сортування створює непередбачувані (і майже завжди погані) результати.
Carter Page

3
@Sheagorath Я спробував це в Android, і він також працює. Це не певна проблема платформи, враховуючи, що ви використовуєте версію Java 6. Чи правильно ви реалізували Порівняльне у вашому об’єкті цінності?
saiyancoder

6
Чи не слід використовувати версію Java 8 forEachOrderedзамість forEach, оскільки в документах forEachзазначено: "Поведінка цієї операції явно недетерміновано."?
пограбувати

1
повністю зірвав це, але зараховується @CarterPage в коментарях (все одно це буде у проекті з відкритим кодом). дуже дякую.
Натан Біч

419

Важлива примітка:

Цей код може порушуватися кількома способами. Якщо ви плануєте використовувати наданий код, обов’язково прочитайте коментарі, щоб бути в курсі їх наслідків. Наприклад, значення більше не можна отримати за їх ключем. ( 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("unsorted map: " + map);
        sorted_map.putAll(map);
        System.out.println("results: " + sorted_map);
    }
}

class ValueComparator implements Comparator<String> {
    Map<String, Double> base;

    public ValueComparator(Map<String, Double> base) {
        this.base = base;
    }

    // Note: this comparator imposes orderings that are inconsistent with
    // equals.
    public int compare(String a, String b) {
        if (base.get(a) >= base.get(b)) {
            return -1;
        } else {
            return 1;
        } // returning 0 would merge keys
    }
}

Вихід:

unsorted map: {D=67.3, A=99.5, B=67.4, C=67.4}
results: {D=67.3, B=67.4, C=67.4, A=99.5}

18
Більше не ( stackoverflow.com/questions/109383/… ). Крім того, чому там був акторський склад для "Double"? Чи не повинно бути просто так return ((Comparable)base.get(a).compareTo(((Comparable)base.get(b)))?
Стівен

12
@Stephen: Ні. У цьому випадку всі клавіші, рівні за значенням, падають (різниця між рівними та порівняннями за посиланням). Додатково: Навіть у цього коду є проблеми з наступною послідовністю map.put("A","1d");map.put("B","1d");map.put("C",67d);map.put("D",99.5d);
steffen

43
Порівняльник, який використовується для карти дерева, не відповідає рівним (див. Сорт. Карта javadox). Це означає, що вилучення елементів з дерева не працюватиме. sorted_map.get ("A") повернеться до нуля. Це означає, що використання цієї карти порушено.
mR_fr0g

87
Про всяк випадок, коли це не зрозуміло людям: це рішення, ймовірно, не зробить те, що ви хочете, якщо у вас є декілька ключів, які відображають одне і те ж значення - у сортованому результаті з’явиться лише одна з цих клавіш.
Maxy-B

63
Луї Вассерман (так, один із хлопців Google Guava) насправді дуже не любить цю відповідь: "Це розбивається кількома дійсно заплутаними способами, якщо ви навіть дивитесь на це смішно. Якщо карта резервної копії зміниться, вона зламається. Якщо кілька клавіш Якщо ви зателефонуєте отримати ключ, який відсутній на резервній карті, він зламається. Якщо ви щось зробите, це призвело б до пошуку ключа, який не знаходиться в карта - виклик Map.equals, міститьKey, що завгодно - вона порушиться із дійсно дивними стежками стека. " plus.google.com/102216152814616302326/posts/bEQLDK712MJ
haylem

339

Java 8 пропонує нову відповідь: перетворіть записи в потік і використовуйте комбінатори порівняння з Map.Entry:

Stream<Map.Entry<K,V>> sorted =
    map.entrySet().stream()
       .sorted(Map.Entry.comparingByValue());

Це дозволить вам споживати записи, відсортовані у порядку зростання. Якщо ви бажаєте зменшити значення, просто переверніть компаратор:

Stream<Map.Entry<K,V>> sorted =
    map.entrySet().stream()
       .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));

Якщо значення не порівнянні, ви можете передати явний компаратор:

Stream<Map.Entry<K,V>> sorted =
    map.entrySet().stream()
       .sorted(Map.Entry.comparingByValue(comparator));

Потім можна перейти до використання інших потокових операцій для споживання даних. Наприклад, якщо ви хочете перших 10 на новій карті:

Map<K,V> topTen =
    map.entrySet().stream()
       .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
       .limit(10)
       .collect(Collectors.toMap(
          Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));

Або надрукувати на System.out:

map.entrySet().stream()
   .sorted(Map.Entry.comparingByValue())
   .forEach(System.out::println);

Приємно, а як щодо використання parallelStream()в цьому випадку?
Бендж

11
Він працюватиме паралельно, однак, можливо, ви виявите, що вартість об’єднання карт для об'єднання часткових результатів є занадто дорогою і паралельна версія може працювати не так добре, як ви сподівалися. Але це працює і дає правильну відповідь.
Брайан Гец

Дякую за корисну пораду. Мені було цікаво саме це, хоча це залежить від того, який тип ключа ви використовуєте і стільки параметрів ... Важливим є те, що "він працює і дає правильну відповідь".
Бендж

2
чи не потрібно вам використовувати CompareByValue у прикладі топ10?
Лев

1
@Benj це буде працювати з точки зору вилучення перших 10, але отримана карта більше не буде замовлена.
OrangeDog

211

Три відповіді на 1 рядок ...

Я би використовував Google Collections Guava для цього - якщо ваші цінності, Comparableто ви можете використовувати

valueComparator = Ordering.natural().onResultOf(Functions.forMap(map))

Який створить функцію (об'єкт) для карти [, яка приймає будь-яку з клавіш як вхідну, повертаючи відповідне значення], а потім застосує до них природне (порівнянне) впорядкування [значення].

Якщо вони не порівнянні, вам потрібно буде щось робити за принципом

valueComparator = Ordering.from(comparator).onResultOf(Functions.forMap(map)) 

Вони можуть бути застосовані до TreeMap (як Orderingрозширення Comparator) або LinkedHashMap після деякого сортування

Примітка : Якщо ви збираєтесь використовувати TreeMap, пам’ятайте, що якщо порівняння == 0, то цей пункт вже є у списку (що станеться, якщо у вас є кілька значень, що порівнюють одне і те ж). Щоб полегшити це, ви можете додати свій ключ до компаратора так (припускаючи, що ваші ключі та значення Comparable):

valueComparator = Ordering.natural().onResultOf(Functions.forMap(map)).compound(Ordering.natural())

= Застосувати природне впорядкування до значення, відображеного ключем, і з'єднати його з природним упорядкуванням ключа

Зверніть увагу , що це буде по- , як і раніше не працює , якщо ключі порівнювати 0, але це повинно бути досить для більшості comparableелементів (як hashCode, equalsі compareToчасто синхронізовані ...)

Див. Замовлення.onResultOf () та Functions.forMap () .

Впровадження

Отже, коли у нас є компаратор, який робить те, що ми хочемо, нам потрібно отримати результат.

map = ImmutableSortedMap.copyOf(myOriginalMap, valueComparator);

Зараз це, швидше за все, спрацює, але:

  1. потрібно зробити, маючи повну готову карту
  2. Не намагайтеся порівняти вище на а TreeMap; немає сенсу намагатися порівнювати вставлений ключ, коли він не має значення, поки не буде поставлено, тобто він зламається дуже швидко

Точка 1 для мене є дещо розривом угоди; Колекції google неймовірно ліниві (що добре: ви можете виконати майже кожну операцію за мить; справжня робота робиться, коли ви починаєте використовувати результат), і для цього потрібно скопіювати цілу карту!

"Повна" відповідь / Жива відсортована карта за значеннями

Не хвилюйтесь, хоча; якщо ви були достатньо одержимі тим, що "жива" карта впорядкована таким чином, ви могли б вирішити не одну, а обидві (!) вищезазначених питань чимось божевільним, як це:

Примітка. Це значно змінилося в червні 2012 року - попередній код ніколи не міг працювати: потрібен внутрішній HashMap для пошуку значень без створення нескінченного циклу між TreeMap.get()-> compare()і compare()->get()

import static org.junit.Assert.assertEquals;

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

import com.google.common.base.Functions;
import com.google.common.collect.Ordering;

class ValueComparableMap<K extends Comparable<K>,V> extends TreeMap<K,V> {
    //A map for doing lookups on the keys for comparison so we don't get infinite loops
    private final Map<K, V> valueMap;

    ValueComparableMap(final Ordering<? super V> partialValueOrdering) {
        this(partialValueOrdering, new HashMap<K,V>());
    }

    private ValueComparableMap(Ordering<? super V> partialValueOrdering,
            HashMap<K, V> valueMap) {
        super(partialValueOrdering //Apply the value ordering
                .onResultOf(Functions.forMap(valueMap)) //On the result of getting the value for the key from the map
                .compound(Ordering.natural())); //as well as ensuring that the keys don't get clobbered
        this.valueMap = valueMap;
    }

    public V put(K k, V v) {
        if (valueMap.containsKey(k)){
            //remove the key in the sorted set before adding the key again
            remove(k);
        }
        valueMap.put(k,v); //To get "real" unsorted values for the comparator
        return super.put(k, v); //Put it in value order
    }

    public static void main(String[] args){
        TreeMap<String, Integer> map = new ValueComparableMap<String, Integer>(Ordering.natural());
        map.put("a", 5);
        map.put("b", 1);
        map.put("c", 3);
        assertEquals("b",map.firstKey());
        assertEquals("a",map.lastKey());
        map.put("d",0);
        assertEquals("d",map.firstKey());
        //ensure it's still a map (by overwriting a key, but with a new value) 
        map.put("d", 2);
        assertEquals("b", map.firstKey());
        //Ensure multiple values do not clobber keys
        map.put("e", 2);
        assertEquals(5, map.size());
        assertEquals(2, (int) map.get("e"));
        assertEquals(2, (int) map.get("d"));
    }
 }

Коли ми ставимо, ми гарантуємо, що хеш-карта має значення для компаратора, а потім ставимо в TreeSet для сортування. Але перед цим ми перевіряємо хеш-карту, щоб побачити, що ключ насправді не є дублікатом. Крім того, створений нами компаратор також міститиме ключ, щоб дублюючі значення не видаляли не повторювані ключі (через порівняння ==). Ці два пункти життєво важливі для забезпечення збереження контракту на карту; якщо ви думаєте, що цього не хочете, ви майже перебуваєте в точці повернення карти повністю (до Map<V,K>).

Конструктор повинен бути названий як

 new ValueComparableMap(Ordering.natural());
 //or
 new ValueComparableMap(Ordering.from(comparator));

Привіт, @Stephen, чи можете ви навести приклад використання замовлення? Я переглядаю вихідний код Порядку і абсолютно не можу зрозуміти, що .natural (). OnResultOf (...) повертається! Вихідний код "public <F> Ordering <F> onResultOf", я навіть не знаю, як він компілюється! Найголовніше, як використовувати "<F> Замовлення <F>" для сортування карти? Це компаратор чи щось таке? Дякую.
smallufo

Orderingпросто багатий Comparator. Я намагався коментувати кожен приклад (курсив під кожним). "природний" означає, що об'єкти є Comparable; це як ComparableComparator apache common. onResultOfзастосовує функцію до об'єкта, який порівнюється. Тож якби у вас була функція, яка додала 1 до цілого числа, тоді це natural().onResultOf(add1Function).compare(1,2)було б в результаті2.compareTo(3)
Stephen

ImmutableSortedMap.copyOf кидає IllegalArgumentException, якщо в оригінальній карті є дублюючі значення.
lbalazscs

@Ibalazscs Так, так - Ви повинні мати можливість використовувати ImmutableSetMultiMapабо ImmutableListMultiMapмістити колекцію повторюваних змінних.
Стівен

1
Дякую за це, я використав ваше рішення в одному проекті. Я думаю, що в цьому є проблема: щоб поводитись як карта, вона повинна повернути значення, яке раніше було пов'язане з ключем, якщо воно існує, але подібне це ніколи не зробить. Я використовував рішення - повернути видалене значення, якщо воно існує.
alex

185

Від http://www.programmersheaven.com/download/49349/download.aspx

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

16
Список для сортування - "новий LinkedList" ?? Гей. На щастя, Collections.sort () спочатку скидає список до масиву, щоб уникнути саме подібних помилок (але все ж, скидання ArrayList до масиву повинно бути швидшим, ніж те ж саме для LinkedList).
Димитріс Андреу

не вдається перетворити з Iterator на TernaryTree.Iterator
lisak

4
@ gg.kaspersky Я не говорю "погано сортувати LinkedList", але сам LinkedList - це поганий вибір, незалежно від сортування. Набагато краще використовувати ArrayList, а для додаткових точок розміріть його точно на map.size (). Також дивіться code.google.com/p/memory-measurer/wiki/… середня вартість за елемент у ArrayList: середня вартість 5 елементів на елемент у LinkedList: 24 байти. Для ArrayList точно розміру середня вартість складе 4 байти. Тобто LinkedList займає шісткратну кількість пам'яті, яка потрібна ArrayList. Це просто цвіте
Димитріс Андреу

2
Використання вищевказаних значень було відсортовано у порядку зростання. Як сортувати за спаданням?
таран

1
Замініть o1 і o2 для сортування за спаданням.
Soheil

68

З Java 8 ви можете використовувати потоки api, щоб зробити це значно менш багатослівним способом:

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

Як сортувати його у зворотному порядку?
Влад Голубієв

6
знайшли рішення -Collections.reverseOrder(comparing(Entry::getValue))
Влад Голубієв

1
Я думаю, я бачу друкарську помилку - чи не слід називати "toMap" так, як "Collectors.toMap ()"?
Джейк Стоукс

1
@JakeStokes Або використовуйте статичний імпорт :-)
assylias

6
Кращий спосіб сортування за значенням введення в зворотному порядку:Entry.comparingByValue(Comparator.reverseOrder())
Gediminas Rimsa

31

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

Ось більш ефективна, хоча і менш приваблива версія:

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();

Нарешті, якщо вам потрібно безперервно отримувати доступ до відсортованої інформації (а не просто сортувати її раз у раз), ви можете використовувати додаткову мульти карту. Повідомте мене, якщо вам потрібно більше деталей ...


Друга версія може бути більш стислою, якщо ви повернете Список <Map.Entry <K, V >> Це також полегшує ітерацію та отримання як клавіш, так і значень, не вимагаючи багато зайвого потрапляння на карту. Це все за умови, що ви все в порядку, оскільки цей код є небезпечним для потоків. Якщо резервна карта або відсортований список надаються у багатопотоковому середовищі, усі ставки відключаються.
Майк Міллер

26

Бібліотека колекцій спільнот містить рішення під назвою TreeBidiMap . Або ви можете ознайомитися з API колекцій Google. У ньому є TreeMultimap, який ви можете використовувати.

І якщо ви не хочете використовувати ці рамки ... вони поставляються із вихідним кодом.


Не потрібно використовувати колекцію спільноти. Java поставляється з власною java.util.TreeMap.
йоліхо

2
так, але TreeMap набагато менш гнучкий при сортуванні за цінністю частин мапентрі.
p3t0r

9
Проблема з BidiMap полягає в тому, що він додає обмеження відношення 1: 1 між ключами та значеннями, щоб зробити відношення незворотним (тобто, і ключі, і значення повинні бути унікальними). Це означає, що ви не можете використовувати це для зберігання чогось подібного до об'єкта підрахунку слів, оскільки багато слів матимуть однаковий підрахунок.
Дуг

26

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

Ось рішення, яке, на мою думку, підходить краще:

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

Зауважте, що карта сортується від найвищого значення до найнижчого.


6
ПРОБЛЕМА: якщо ви хочете пізніше використати повернуту карту, наприклад, щоб перевірити, чи містить вона певний елемент, ви завжди отримаєте помилкову, завдяки своєму користувальницькому компаратору! Можливе рішення: замініть останній рядок на: повернути новий LinkedHashMap <K, V> (sortedByValues);
Ерел Сегал-Халеві

Для мене це виглядає чистим рішенням, за винятком того, що вказував @ErelSegalHalevi, перевірити, чи існують значення на карті, неможливо, як ви вказали в порівнянні. map.put ("1", "Один"); map.put ("2", "Два"); map.put ("3", "Три"); map.put ("4", "Four"); map.put ("5", "П'ять"); map.containsKey ("1") завжди повертатиме false, якщо ви повернете новий об'єкт у функції sortByValues ​​(), як повернути новий TreeMap <K, V> (sortedByValues); вирішує проблему. Дякую Абхі
абхі

майже так само, як відповідь користувача157196 та відповіді Картера Пейджа. Carter Page's містить виправлення LinkedHashMap
Кірбі

4-й рядок рішення повинен бути int порівняти = map.get (k1) .compareTo (map.get (k2)); якщо вам потрібно зростаюче замовлення
www.Decompiler.com

19

Щоб досягти цього за допомогою нових функцій на Java 8:

import static java.util.Map.Entry.comparingByValue;
import static java.util.stream.Collectors.toList;

<K, V> List<Entry<K, V>> sort(Map<K, V> map, Comparator<? super V> comparator) {
    return map.entrySet().stream().sorted(comparingByValue(comparator)).collect(toList());
}

Записи упорядковуються за їх значеннями за допомогою даного компаратора. Крім того, якщо ваші значення взаємопорівняні, явний компаратор не потрібен:

<K, V extends Comparable<? super V>> List<Entry<K, V>> sort(Map<K, V> map) {
    return map.entrySet().stream().sorted(comparingByValue()).collect(toList());
}

Повернений список - це знімок даної карти під час виклику цього методу, тому жоден з них не відображатиме подальші зміни до іншого. Для перегляду карти на карті:

<K, V extends Comparable<? super V>> Iterable<Entry<K, V>> sort(Map<K, V> map) {
    return () -> map.entrySet().stream().sorted(comparingByValue()).iterator();
}

Повертається ітерабельний файл створює новий знімок даної карти щоразу, коли вона повторюється, тому забороняючи одночасну модифікацію, вона завжди відображатиме поточний стан карти.


Це повертає Список записів, а не карту, відсортовану за значенням. Інша версія, яка повертає карту: stackoverflow.com/a/22132422/829571
assylias

17

Створіть персоналізований компаратор та використовуйте його під час створення нового об’єкта TreeMap.

class MyComparator implements Comparator<Object> {

    Map<String, Integer> map;

    public MyComparator(Map<String, Integer> map) {
        this.map = map;
    }

    public int compare(Object o1, Object o2) {

        if (map.get(o2) == map.get(o1))
            return 1;
        else
            return ((Integer) map.get(o2)).compareTo((Integer)     
                                                            map.get(o1));

    }
}

Використовуйте наведений нижче код у своєму головному функції

    Map<String, Integer> lMap = new HashMap<String, Integer>();
    lMap.put("A", 35);
    lMap.put("B", 75);
    lMap.put("C", 50);
    lMap.put("D", 50);

    MyComparator comparator = new MyComparator(lMap);

    Map<String, Integer> newMap = new TreeMap<String, Integer>(comparator);
    newMap.putAll(lMap);
    System.out.println(newMap);

Вихід:

{B=75, D=50, C=50, A=35}

У випадку, коли значення рівні, я змінив рядок "return 1" для порівняння клавіш: "return ((String) o1) .compareTo ((String) o2);"
gjgjgj

14

Хоча я згоден, що постійна потреба сортувати карту, мабуть, є запахом, я думаю, що наступний код - це найпростіший спосіб зробити це без використання іншої структури даних.

public class MapUtilities {

public static <K, V extends Comparable<V>> List<Entry<K, V>> sortByValue(Map<K, V> map) {
    List<Entry<K, V>> entries = new ArrayList<Entry<K, V>>(map.entrySet());
    Collections.sort(entries, new ByValue<K, V>());
    return entries;
}

private static class ByValue<K, V extends Comparable<V>> implements Comparator<Entry<K, V>> {
    public int compare(Entry<K, V> o1, Entry<K, V> o2) {
        return o1.getValue().compareTo(o2.getValue());
    }
}

}

І ось вам бентежно неповне одиничне випробування:

public class MapUtilitiesTest extends TestCase {
public void testSorting() {
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("One", 1);
    map.put("Two", 2);
    map.put("Three", 3);

    List<Map.Entry<String, Integer>> sorted = MapUtilities.sortByValue(map);
    assertEquals("First", "One", sorted.get(0).getKey());
    assertEquals("Second", "Two", sorted.get(1).getKey());
    assertEquals("Third", "Three", sorted.get(2).getKey());
}

}

Результат - відсортований список об’єктів Map.Entry, з яких можна отримати ключі та значення.


Цей метод набагато простіший та інтуїтивніший, ніж створення об’єкта Map <V, List <K>> з майже однаковим ефектом. Ці значення насправді не повинні бути ключами в об’єкті Map, що ви дійсно шукаєте, це список у цій ситуації, IMHO.
Джефф Ву

Це рішення не працює з великою кількістю значень, воно накручується на мої рахунки (значення, пов'язане з кожною клавішею)
Сем Левін

1
Це дивно. Не могли б ви детальніше? Який був ваш результат і який результат ви очікували?
Людмил

12

Використовуйте загальний компаратор, такий як:

final class MapValueComparator<K,V extends Comparable<V>> implements Comparator<K> {

    private Map<K,V> map;

    private MapValueComparator() {
        super();
    }

    public MapValueComparator(Map<K,V> map) {
        this();
        this.map = map;
    }

    public int compare(K o1, K o2) {
        return map.get(o1).compareTo(map.get(o2));
    }
}

11

Відповідь, яку проголосували більшість, не працює, якщо у вас є 2 пункти, що дорівнює. TreeMap залишає однакові значення.

Приклад: несортована карта

ключ / значення: D / 67.3
ключ / значення: A / 99,5
ключ / значення: B / 67.4
ключ / значення: C / 67.5
ключ / значення: E / 99.5

результати

ключ / значення: A / 99,5
ключ / значення: C / 67.5
ключ / значення: B / 67.4
ключ / значення: D / 67.3

Так залишає E !!

Для мене він працював чудово, щоб налаштувати компаратор, якщо він дорівнює, не повертається 0, але -1.

у прикладі:

клас ValueComparator реалізує компаратор {

База карт; public ValueComparator (база карт) {this.base = base; }

public int порівняння (Об'єкт a, Об'єкт b) {

if((Double)base.get(a) < (Double)base.get(b)) {
  return 1;
} else if((Double)base.get(a) == (Double)base.get(b)) {
  return -1;
} else {
  return -1;
}

}}

тепер він повертається:

несортована карта:

ключ / значення: D / 67.3
ключ / значення: A / 99,5
ключ / значення: B / 67.4
ключ / значення: C / 67.5
ключ / значення: E / 99.5

результати:

ключ / значення: A / 99,5
ключ / значення: E / 99.5
ключ / значення: C / 67.5
ключ / значення: B / 67.4
ключ / значення: D / 67.3

як відповідь на інопланетян (2011, 22 листопада): Я використовую це рішення для карти цілих ідентифікаторів і імен, але ідея та сама, тому може бути код вище невірний (я напишу це в тесті і надати правильний код), це код для сортування карти на основі рішення, наведеного вище:

package nl.iamit.util;

import java.util.Comparator;
import java.util.Map;

public class Comparators {


    public static class MapIntegerStringComparator implements Comparator {

        Map<Integer, String> base;

        public MapIntegerStringComparator(Map<Integer, String> base) {
            this.base = base;
        }

        public int compare(Object a, Object b) {

            int compare = ((String) base.get(a))
                    .compareTo((String) base.get(b));
            if (compare == 0) {
                return -1;
            }
            return compare;
        }
    }


}

і це тестовий клас (я тільки тестував його, і це працює для Integer, String Map:

package test.nl.iamit.util;

import java.util.HashMap;
import java.util.TreeMap;
import nl.iamit.util.Comparators;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;

public class TestComparators {


    @Test
    public void testMapIntegerStringComparator(){
        HashMap<Integer, String> unSoretedMap = new HashMap<Integer, String>();
        Comparators.MapIntegerStringComparator bvc = new Comparators.MapIntegerStringComparator(
                unSoretedMap);
        TreeMap<Integer, String> sorted_map = new TreeMap<Integer, String>(bvc);
        //the testdata:
        unSoretedMap.put(new Integer(1), "E");
        unSoretedMap.put(new Integer(2), "A");
        unSoretedMap.put(new Integer(3), "E");
        unSoretedMap.put(new Integer(4), "B");
        unSoretedMap.put(new Integer(5), "F");

        sorted_map.putAll(unSoretedMap);

        Object[] targetKeys={new Integer(2),new Integer(4),new Integer(3),new Integer(1),new Integer(5) };
        Object[] currecntKeys=sorted_map.keySet().toArray();

        assertArrayEquals(targetKeys,currecntKeys);
    }
}

ось код для компаратора карти:

public static class MapStringDoubleComparator implements Comparator {

    Map<String, Double> base;

    public MapStringDoubleComparator(Map<String, Double> base) {
        this.base = base;
    }

    //note if you want decending in stead of ascending, turn around 1 and -1
    public int compare(Object a, Object b) {
        if ((Double) base.get(a) == (Double) base.get(b)) {
            return 0;
        } else if((Double) base.get(a) < (Double) base.get(b)) {
            return -1;
        }else{
            return 1;
        }
    }
}

і ось цей зразок для цього:

@Test
public void testMapStringDoubleComparator(){
    HashMap<String, Double> unSoretedMap = new HashMap<String, Double>();
    Comparators.MapStringDoubleComparator bvc = new Comparators.MapStringDoubleComparator(
            unSoretedMap);
    TreeMap<String, Double> sorted_map = new TreeMap<String, Double>(bvc);
    //the testdata:
    unSoretedMap.put("D",new Double(67.3));
    unSoretedMap.put("A",new Double(99.5));
    unSoretedMap.put("B",new Double(67.4));
    unSoretedMap.put("C",new Double(67.5));
    unSoretedMap.put("E",new Double(99.5));

    sorted_map.putAll(unSoretedMap);

    Object[] targetKeys={"D","B","C","E","A"};
    Object[] currecntKeys=sorted_map.keySet().toArray();

    assertArrayEquals(targetKeys,currecntKeys);
}

Ви можете зробити це набагато більш загальним, але я просто знадобився для 1 випадку (Карта)


Ви мали рацію, в коді, який я спочатку дав, виникла помилка! Я сподіваюся, що моя остання редакція допоможе вам.
michel.iamit

9

Замість того, щоб використовувати, Collections.sortяк деякі, я б пропонував використовувати Arrays.sort. Насправді Collections.sortце щось таке:

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    Object[] a = list.toArray();
    Arrays.sort(a);
    ListIterator<T> i = list.listIterator();
    for (int j=0; j<a.length; j++) {
        i.next();
        i.set((T)a[j]);
    }
}

Він просто дзвонить toArrayу список і потім використовує Arrays.sort. Таким чином, всі записи на карті будуть скопійовані три рази: один раз з карти до тимчасового списку (будь то LinkedList або ArrayList), потім у тимчасовий масив і нарешті на нову карту.

Моє рішення пропускає цей один крок, оскільки не створює зайвого LinkedList. Ось код, загальний для використання та оптимальний для роботи:

public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) 
{
    @SuppressWarnings("unchecked")
    Map.Entry<K,V>[] array = map.entrySet().toArray(new Map.Entry[map.size()]);

    Arrays.sort(array, new Comparator<Map.Entry<K, V>>() 
    {
        public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2) 
        {
            return e1.getValue().compareTo(e2.getValue());
        }
    });

    Map<K, V> result = new LinkedHashMap<K, V>();
    for (Map.Entry<K, V> entry : array)
        result.put(entry.getKey(), entry.getValue());

    return result;
}

8

Це варіант відповіді Ентоні, який не працює, якщо є дублюючі значення:

public static <K, V extends Comparable<V>> Map<K, V> sortMapByValues(final Map<K, V> map) {
    Comparator<K> valueComparator =  new Comparator<K>() {
        public int compare(K k1, K k2) {
            final V v1 = map.get(k1);
            final V v2 = map.get(k2);

            /* Not sure how to handle nulls ... */
            if (v1 == null) {
                return (v2 == null) ? 0 : 1;
            }

            int compare = v2.compareTo(v1);
            if (compare != 0)
            {
                return compare;
            }
            else
            {
                Integer h1 = k1.hashCode();
                Integer h2 = k2.hashCode();
                return h2.compareTo(h1);
            }
        }
    };
    Map<K, V> sortedByValues = new TreeMap<K, V>(valueComparator);
    sortedByValues.putAll(map);
    return sortedByValues;
}

Зауважте, що швидше в повітрі, як поводитися з нулями.

Однією з важливих переваг такого підходу є те, що він фактично повертає карту, на відміну від деяких інших пропонованих тут рішень.


Це неправильно, мій метод працює, якщо є дублюючі значення. Я використовував це з картами, у яких понад 100 клавіш із значенням "1".
Ентоні

8

Кращий підхід

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry; 

public class OrderByValue {

  public static void main(String a[]){
    Map<String, Integer> map = new HashMap<String, Integer>();
    map.put("java", 20);
    map.put("C++", 45);
    map.put("Unix", 67);
    map.put("MAC", 26);
    map.put("Why this kolavari", 93);
    Set<Entry<String, Integer>> set = map.entrySet();
    List<Entry<String, Integer>> list = new ArrayList<Entry<String, Integer>>(set);
    Collections.sort( list, new Comparator<Map.Entry<String, Integer>>()
    {
        public int compare( Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2 )
        {
            return (o1.getValue()).compareTo( o2.getValue() );//Ascending order
            //return (o2.getValue()).compareTo( o1.getValue() );//Descending order
        }
    } );
    for(Map.Entry<String, Integer> entry:list){
        System.out.println(entry.getKey()+" ==== "+entry.getValue());
    }
  }}

Вихідні дані

java ==== 20

MAC ==== 26

C++ ==== 45

Unix ==== 67

Why this kolavari ==== 93

7

Основна проблема. Якщо ви використовуєте першу відповідь (Google веде вас сюди), змініть компаратор, щоб додати рівне застереження, інакше ви не можете отримати значення з сортованої карти за ключами:

public int compare(String a, String b) {
        if (base.get(a) > base.get(b)) {
            return 1;
        } else if (base.get(a) < base.get(b)){
            return -1;
        } 

        return 0;
        // returning 0 would merge keys
    }

Тепер, коли ви додасте два записи з рівними значеннями, вони будуть об'єднані, ви повинні повернути 0, лише якщо ви впевнені, що об'єкти однакові (рівні)
Masood_mj

7

Відповідей на це питання вже багато, але жоден не дав мені того, що я шукав, реалізація карти, яка повертає ключі та записи, відсортовані за пов'язаним значенням, і підтримує це властивість, оскільки ключі та значення змінюються на карті. Дві інші питання задати для цього спеціально.

Я підготував загальний приклад, який вирішує цю справу використання. Ця реалізація не виконує всіх контрактів інтерфейсу Map, таких як відображення змін значення та видалення в наборах, що повертаються з keySet () та entrySet () у вихідний об'єкт. Я вважав, що таке рішення буде занадто великим, щоб включити його у відповідь про переповнення стека. Якщо мені вдасться створити більш повну реалізацію, можливо, я опублікую її в Github, а потім на посилання в оновленій версії цієї відповіді.

import java.util.*;

/**
 * A map where {@link #keySet()} and {@link #entrySet()} return sets ordered
 * by associated values based on the the comparator provided at construction
 * time. The order of two or more keys with identical values is not defined.
 * <p>
 * Several contracts of the Map interface are not satisfied by this minimal
 * implementation.
 */
public class ValueSortedMap<K, V> extends HashMap<K, V> {
    protected Map<V, Collection<K>> valueToKeysMap;

    // uses natural order of value object, if any
    public ValueSortedMap() {
        this((Comparator<? super V>) null);
    }

    public ValueSortedMap(Comparator<? super V> valueComparator) {
        this.valueToKeysMap = new TreeMap<V, Collection<K>>(valueComparator);
    }

    public boolean containsValue(Object o) {
        return valueToKeysMap.containsKey(o);
    }

    public V put(K k, V v) {
        V oldV = null;
        if (containsKey(k)) {
            oldV = get(k);
            valueToKeysMap.get(oldV).remove(k);
        }
        super.put(k, v);
        if (!valueToKeysMap.containsKey(v)) {
            Collection<K> keys = new ArrayList<K>();
            keys.add(k);
            valueToKeysMap.put(v, keys);
        } else {
            valueToKeysMap.get(v).add(k);
        }
        return oldV;
    }

    public void putAll(Map<? extends K, ? extends V> m) {
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            put(e.getKey(), e.getValue());
    }

    public V remove(Object k) {
        V oldV = null;
        if (containsKey(k)) {
            oldV = get(k);
            super.remove(k);
            valueToKeysMap.get(oldV).remove(k);
        }
        return oldV;
    }

    public void clear() {
        super.clear();
        valueToKeysMap.clear();
    }

    public Set<K> keySet() {
        LinkedHashSet<K> ret = new LinkedHashSet<K>(size());
        for (V v : valueToKeysMap.keySet()) {
            Collection<K> keys = valueToKeysMap.get(v);
            ret.addAll(keys);
        }
        return ret;
    }

    public Set<Map.Entry<K, V>> entrySet() {
        LinkedHashSet<Map.Entry<K, V>> ret = new LinkedHashSet<Map.Entry<K, V>>(size());
        for (Collection<K> keys : valueToKeysMap.values()) {
            for (final K k : keys) {
                final V v = get(k);
                ret.add(new Map.Entry<K,V>() {
                    public K getKey() {
                        return k;
                    }

                    public V getValue() {
                        return v;
                    }

                    public V setValue(V v) {
                        throw new UnsupportedOperationException();
                    }
                });
            }
        }
        return ret;
    }
}

Якщо Порівняння та Порівняння заборонені, то як це зробити?
Вед Пракаш

Не впевнений, чи я розумію ваш випадок використання, можливо, ви можете допрацювати. Якщо об'єкт, який ви хочете використовувати як значення, не є порівняльним, то вам потрібно буде перетворити його на об'єкт, який є.
Девід Блеккманн

6

Пізній вступ.

З появою Java-8 ми можемо використовувати потоки для обробки даних дуже легким / стислим способом. Ви можете використовувати потоки для сортування записів на карті за значенням та створення LinkedHashMap, який зберігає ітерацію порядку вставки .

Наприклад:

LinkedHashMap sortedByValueMap = map.entrySet().stream()
                .sorted(comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey))     //first sorting by Value, then sorting by Key(entries with same value)
                .collect(LinkedHashMap::new,(map,entry) -> map.put(entry.getKey(),entry.getValue()),LinkedHashMap::putAll);

Для зворотного замовлення замініть:

comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey)

з

comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey).reversed()

Дякуємо за цю коментовану версію. Одне питання: В чому різниця у використанні Entry.comparingByValue()(як assylias відповідь вище stackoverflow.com/a/22132422/1480587 ) або comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey)що ви використовували? Я розумію, ви також порівнюєте ключі, якщо значення однакові, правда? Я помітив, що сортування підтримує порядок елементів з однаковим значенням - так чи потрібне сортування за ключами, якщо ключі вже були відсортовані раніше?
Петро Т.

6

Дана карта

   Map<String, Integer> wordCounts = new HashMap<>();
    wordCounts.put("USA", 100);
    wordCounts.put("jobs", 200);
    wordCounts.put("software", 50);
    wordCounts.put("technology", 70);
    wordCounts.put("opportunity", 200);

Сортуйте карту виходячи зі значення у порядку зростання

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

Сортуйте карту за значенням у порядку відхилення

Map<String,Integer>  sortedMapReverseOrder =  wordCounts.entrySet().
            stream().
            sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).
            collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
    System.out.println(sortedMapReverseOrder);

Вихід:

{програмне забезпечення = 50, технологія = 70, США = 100, робочі місця = 200, можливість = 200}

{вакансії = 200, можливість = 200, США = 100, технологія = 70, програмне забезпечення = 50}


Я думаю, що зменшення карти дійсно "зменшило" це .... хороше рішення.
ha9u63ar

дякую @ ha9u63ar
Арпан

Це працює, але я не розумію, як порядок елементів грає в HashMap?
Алі Ту

5

Залежно від контексту, використовуючи java.util.LinkedHashMap<T>який запам'ятовує порядок розміщення елементів на карті. В іншому випадку, якщо вам потрібно буде сортувати значення на основі їх природного впорядкування, я рекомендую вести окремий список, за яким можна сортувати Collections.sort().


Я не бачу, чому це було -1, поки що LinkedHashMap, мабуть, найкраще рішення для мене, я просто намагаюся розібратися, як дорого це викинути та створити новий LinkedHashMap.
NobleUplift

5

Оскільки TreeMap <> не працює для значень, які можуть бути рівними, я використав це:

private <K, V extends Comparable<? super V>> List<Entry<K, V>> sort(Map<K, V> map)     {
    List<Map.Entry<K, V>> list = new LinkedList<Map.Entry<K, V>>(map.entrySet());
    Collections.sort(list, new Comparator<Map.Entry<K, V>>() {
        public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) {
            return o1.getValue().compareTo(o2.getValue());
        }
    });

    return list;
}

Ви можете поставити список у LinkedHashMap , але якщо ви збираєтесь лише переглядати його відразу, це зайве ...


це правильно, але ваш компаратор не обробляє значення рівних значень
Себастьян Лорбер

5

Це просто занадто складно. Карти не повинні були виконувати таку роботу, як сортування їх за вартістю. Найпростіший спосіб - створити власний клас, щоб він відповідав вашим вимогам.

У прикладі нижче, ви повинні додати TreeMap для порівняння там, де є *. Але API Java дає компаратору лише ключі, а не значення. Усі наведені тут приклади базуються на 2 картах. Один хеш і одне нове дерево. Що дивно.

Приклад:

Map<Driver driver, Float time> map = new TreeMap<Driver driver, Float time>(*);

Тому змініть карту набір таким чином:

ResultComparator rc = new ResultComparator();
Set<Results> set = new TreeSet<Results>(rc);

Ви створите клас Results,

public class Results {
    private Driver driver;
    private Float time;

    public Results(Driver driver, Float time) {
        this.driver = driver;
        this.time = time;
    }

    public Float getTime() {
        return time;
    }

    public void setTime(Float time) {
        this.time = time;
    }

    public Driver getDriver() {
        return driver;
    }

    public void setDriver (Driver driver) {
        this.driver = driver;
    }
}

і клас компаратора:

public class ResultsComparator implements Comparator<Results> {
    public int compare(Results t, Results t1) {
        if (t.getTime() < t1.getTime()) {
            return 1;
        } else if (t.getTime() == t1.getTime()) {
            return 0;
        } else {
            return -1;
        }
    }
}

Таким чином ви можете легко додати більше залежностей.

І в якості останнього моменту я додам простий ітератор:

Iterator it = set.iterator();
while (it.hasNext()) {
    Results r = (Results)it.next();
    System.out.println( r.getDriver().toString
        //or whatever that is related to Driver class -getName() getSurname()
        + " "
        + r.getTime()
        );
}

4

На основі @devinmoore-коду, методів сортування на карті за допомогою генеричних даних та підтримують як висхідне, так і низхідне впорядкування.

/**
 * Sort a map by it's keys in ascending order. 
 *  
 * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
 * @author Maxim Veksler
 */
public static <K, V> LinkedHashMap<K, V> sortMapByKey(final Map<K, V> map) {
    return sortMapByKey(map, SortingOrder.ASCENDING);
}

/**
 * Sort a map by it's values in ascending order.
 *  
 * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
 * @author Maxim Veksler
 */
public static <K, V> LinkedHashMap<K, V> sortMapByValue(final Map<K, V> map) {
    return sortMapByValue(map, SortingOrder.ASCENDING);
}

/**
 * Sort a map by it's keys.
 *  
 * @param sortingOrder {@link SortingOrder} enum specifying requested sorting order. 
 * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
 * @author Maxim Veksler
 */
public static <K, V> LinkedHashMap<K, V> sortMapByKey(final Map<K, V> map, final SortingOrder sortingOrder) {
    Comparator<Map.Entry<K, V>> comparator = new Comparator<Entry<K,V>>() {
        public int compare(Entry<K, V> o1, Entry<K, V> o2) {
            return comparableCompare(o1.getKey(), o2.getKey(), sortingOrder);
        }
    };

    return sortMap(map, comparator);
}

/**
 * Sort a map by it's values.
 *  
 * @param sortingOrder {@link SortingOrder} enum specifying requested sorting order. 
 * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
 * @author Maxim Veksler
 */
public static <K, V> LinkedHashMap<K, V> sortMapByValue(final Map<K, V> map, final SortingOrder sortingOrder) {
    Comparator<Map.Entry<K, V>> comparator = new Comparator<Entry<K,V>>() {
        public int compare(Entry<K, V> o1, Entry<K, V> o2) {
            return comparableCompare(o1.getValue(), o2.getValue(), sortingOrder);
        }
    };

    return sortMap(map, comparator);
}

@SuppressWarnings("unchecked")
private static <T> int comparableCompare(T o1, T o2, SortingOrder sortingOrder) {
    int compare = ((Comparable<T>)o1).compareTo(o2);

    switch (sortingOrder) {
    case ASCENDING:
        return compare;
    case DESCENDING:
        return (-1) * compare;
    }

    return 0;
}

/**
 * Sort a map by supplied comparator logic.
 *  
 * @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
 * @author Maxim Veksler
 */
public static <K, V> LinkedHashMap<K, V> sortMap(final Map<K, V> map, final Comparator<Map.Entry<K, V>> comparator) {
    // Convert the map into a list of key,value pairs.
    List<Map.Entry<K, V>> mapEntries = new LinkedList<Map.Entry<K, V>>(map.entrySet());

    // Sort the converted list according to supplied comparator.
    Collections.sort(mapEntries, comparator);

    // Build a new ordered map, containing the same entries as the old map.  
    LinkedHashMap<K, V> result = new LinkedHashMap<K, V>(map.size() + (map.size() / 20));
    for(Map.Entry<K, V> entry : mapEntries) {
        // We iterate on the mapEntries list which is sorted by the comparator putting new entries into 
        // the targeted result which is a sorted map. 
        result.put(entry.getKey(), entry.getValue());
    }

    return result;
}

/**
 * Sorting order enum, specifying request result sort behavior.
 * @author Maxim Veksler
 *
 */
public static enum SortingOrder {
    /**
     * Resulting sort will be from smaller to biggest.
     */
    ASCENDING,
    /**
     * Resulting sort will be from biggest to smallest.
     */
    DESCENDING
}

Тоді знову може бути кращим рішенням буде просто використовувати карту самосортування, у разі використання org.apache.commons.collections.bidimap.TreeBidiMap
Максим Векслер

4

Ось рішення OO (тобто не використовує staticметоди):

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class SortableValueMap<K, V extends Comparable<V>>
  extends LinkedHashMap<K, V> {
  public SortableValueMap() { }

  public SortableValueMap( Map<K, V> map ) {
    super( map );
  }

  public void sortByValue() {
    List<Map.Entry<K, V>> list = new LinkedList<Map.Entry<K, V>>( entrySet() );

    Collections.sort( list, new Comparator<Map.Entry<K, V>>() {
      public int compare( Map.Entry<K, V> entry1, Map.Entry<K, V> entry2 ) {
        return entry1.getValue().compareTo( entry2.getValue() );
      }
    });

    clear();

    for( Map.Entry<K, V> entry : list ) {
      put( entry.getKey(), entry.getValue() );
    }
  }

  private static void print( String text, Map<String, Double> map ) {
    System.out.println( text );

    for( String key : map.keySet() ) {
      System.out.println( "key/value: " + key + "/" + map.get( key ) );
    }
  }

  public static void main( String[] args ) {
    SortableValueMap<String, Double> map =
      new SortableValueMap<String, Double>();

    map.put( "A", 67.5 );
    map.put( "B", 99.5 );
    map.put( "C", 82.4 );
    map.put( "D", 42.0 );

    print( "Unsorted map", map );
    map.sortByValue();
    print( "Sorted map", map );
  }
}

Цим пожертвував у загальнодоступне надбання.


4

Afaik - найчистіший спосіб використання колекцій для сортування карти за вартістю:

Map<String, Long> map = new HashMap<String, Long>();
// populate with data to sort on Value
// use datastructure designed for sorting

Queue queue = new PriorityQueue( map.size(), new MapComparable() );
queue.addAll( map.entrySet() );

// get a sorted map
LinkedHashMap<String, Long> linkedMap = new LinkedHashMap<String, Long>();

for (Map.Entry<String, Long> entry; (entry = queue.poll())!=null;) {
    linkedMap.put(entry.getKey(), entry.getValue());
}

public static class MapComparable implements Comparator<Map.Entry<String, Long>>{

  public int compare(Entry<String, Long> e1, Entry<String, Long> e2) {
    return e1.getValue().compareTo(e2.getValue());
  }
}

4

Деякі прості зміни, щоб мати відсортовану карту з парами, які мають повторювані значення. У методі порівняння (клас ValueComparator), коли значення рівні, не повертають 0, а повертають результат порівняння двох клавіш. Клавіші відрізняються на карті, тож вам вдається зберегти повторювані значення (які сортуються за ключами до речі). Отже, приклад вище можна змінити так:

    public int compare(Object a, Object b) {

        if((Double)base.get(a) < (Double)base.get(b)) {
          return 1;
        } else if((Double)base.get(a) == (Double)base.get(b)) {
          return ((String)a).compareTo((String)b);
        } else {
          return -1;
        }
      }
    }

4

Напевно, рішення Стівена справді чудове, але для тих, хто не може використовувати Guava:

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

// If you want to sort a map by value, and if there can be twice the same value:

// here is your original map
Map<String,Integer> mapToSortByValue = new HashMap<String, Integer>();
mapToSortByValue.put("A", 3);
mapToSortByValue.put("B", 1);
mapToSortByValue.put("C", 3);
mapToSortByValue.put("D", 5);
mapToSortByValue.put("E", -1);
mapToSortByValue.put("F", 1000);
mapToSortByValue.put("G", 79);
mapToSortByValue.put("H", 15);

// Sort all the map entries by value
Set<Map.Entry<String,Integer>> set = new TreeSet<Map.Entry<String,Integer>>(
        new Comparator<Map.Entry<String,Integer>>(){
            @Override
            public int compare(Map.Entry<String,Integer> obj1, Map.Entry<String,Integer> obj2) {
                Integer val1 = obj1.getValue();
                Integer val2 = obj2.getValue();
                // DUPLICATE VALUE CASE
                // If the values are equals, we can't return 0 because the 2 entries would be considered
                // as equals and one of them would be deleted (because we use a set, no duplicate, remember!)
                int compareValues = val1.compareTo(val2);
                if ( compareValues == 0 ) {
                    String key1 = obj1.getKey();
                    String key2 = obj2.getKey();
                    int compareKeys = key1.compareTo(key2);
                    if ( compareKeys == 0 ) {
                        // what you return here will tell us if you keep REAL KEY-VALUE duplicates in your set
                        // if you want to, do whatever you want but do not return 0 (but don't break the comparator contract!)
                        return 0;
                    }
                    return compareKeys;
                }
                return compareValues;
            }
        }
);
set.addAll(mapToSortByValue.entrySet());


// OK NOW OUR SET IS SORTED COOL!!!!

// And there's nothing more to do: the entries are sorted by value!
for ( Map.Entry<String,Integer> entry : set ) {
    System.out.println("Set entries: " + entry.getKey() + " -> " + entry.getValue());
}




// But if you add them to an hashmap
Map<String,Integer> myMap = new HashMap<String,Integer>();
// When iterating over the set the order is still good in the println...
for ( Map.Entry<String,Integer> entry : set ) {
    System.out.println("Added to result map entries: " + entry.getKey() + " " + entry.getValue());
    myMap.put(entry.getKey(), entry.getValue());
}

// But once they are in the hashmap, the order is not kept!
for ( Integer value : myMap.values() ) {
    System.out.println("Result map values: " + value);
}
// Also this way doesn't work:
// Logic because the entryset is a hashset for hashmaps and not a treeset
// (and even if it was a treeset, it would be on the keys only)
for ( Map.Entry<String,Integer> entry : myMap.entrySet() ) {
    System.out.println("Result map entries: " + entry.getKey() + " -> " + entry.getValue());
}


// CONCLUSION:
// If you want to iterate on a map ordered by value, you need to remember:
// 1) Maps are only sorted by keys, so you can't sort them directly by value
// 2) So you simply CAN'T return a map to a sortMapByValue function
// 3) You can't reverse the keys and the values because you have duplicate values
//    This also means you can't neither use Guava/Commons bidirectionnal treemaps or stuff like that

// SOLUTIONS
// So you can:
// 1) only sort the values which is easy, but you loose the key/value link (since you have duplicate values)
// 2) sort the map entries, but don't forget to handle the duplicate value case (like i did)
// 3) if you really need to return a map, use a LinkedHashMap which keep the insertion order

Виконання: http://www.ideone.com/dq3Lu

Вихід:

Set entries: E -> -1
Set entries: B -> 1
Set entries: A -> 3
Set entries: C -> 3
Set entries: D -> 5
Set entries: H -> 15
Set entries: G -> 79
Set entries: F -> 1000
Added to result map entries: E -1
Added to result map entries: B 1
Added to result map entries: A 3
Added to result map entries: C 3
Added to result map entries: D 5
Added to result map entries: H 15
Added to result map entries: G 79
Added to result map entries: F 1000
Result map values: 5
Result map values: -1
Result map values: 1000
Result map values: 79
Result map values: 3
Result map values: 1
Result map values: 3
Result map values: 15
Result map entries: D -> 5
Result map entries: E -> -1
Result map entries: F -> 1000
Result map entries: G -> 79
Result map entries: A -> 3
Result map entries: B -> 1
Result map entries: C -> 3
Result map entries: H -> 15

Сподіваюся, це допоможе деяким людям


3

Якщо у вас є дублікати ключів і лише невеликий набір даних (<1000), і ваш код не є критичним для продуктивності, ви можете просто зробити наступне:

Map<String,Integer> tempMap=new HashMap<String,Integer>(inputUnsortedMap);
LinkedHashMap<String,Integer> sortedOutputMap=new LinkedHashMap<String,Integer>();

for(int i=0;i<inputUnsortedMap.size();i++){
    Map.Entry<String,Integer> maxEntry=null;
    Integer maxValue=-1;
    for(Map.Entry<String,Integer> entry:tempMap.entrySet()){
        if(entry.getValue()>maxValue){
            maxValue=entry.getValue();
            maxEntry=entry;
        }
    }
    tempMap.remove(maxEntry.getKey());
    sortedOutputMap.put(maxEntry.getKey(),maxEntry.getValue());
}

inputUnsortedMap - це вхід до коду.

Змінна sortedOutputMap буде містити дані в порядку уповільнення при повторенні. Щоб змінити порядок, просто змініть> на <у заяві if.

Це не найшвидший сорт, але виконує роботу без додаткових залежностей.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.