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()все, що завгодно, або за клавішами, значеннями або навіть комбінацією (!!) двох.

Ось загальний метод, який повертає a, SortedSetз Map.Entryурахуванням Mapзначень a , значення яких 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]"
Ігор Мілла

3
Виправити: int res = e2.getValue (). CompareTo (e1.getValue ()); повернути res! = 0? res: 1;
beshkenadze

new Integer (0) == new Integer (0) Ви порівнюєте посилання на об’єкти, і ви створюєте два об'єкти! Позиція абсолютно правильна і передбачувана.
Юрій Чернишов

2
"Взагалі кажучи, необхідність сортування записів на карті за її значеннями нетипова" Я тут початківець, але мені здається, що це має бути дуже поширеною справою? Наприклад, якщо я хочу знайти трьох найстаріших людей на карті {"ana" = 37, "peter" = 43, "john" = 4, "mary" = 15, "Matt" = 78}
fartagaintuxedo

@igormilla Існує проста причина, чому ваш код працює: Автобокс використовує Integer.valueOf. І це кеш примірників для від -128 до 127!
жартівник

75

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

Цей код: ...

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: Зверніть увагу , що порядок підтримується відсортованим набір повинен бути узгоджений з рівними ... . Рішенням було б змінити на Listреалізацію.
dacwe

4
@dacwe рівних значень не ключів
bellum

1
@bellum: Тут мова йде про Sets. Оскільки Comparatorпорушує Setдоговір Set.remove, і Set.containsт.д. не працює ! Перевірте цей приклад у ideone .
dacwe

3
Просто до відома, якщо ви перейдіть int res= e1.getValue().compareTo(e2.getValue());в int res= e2.getValue().compareTo(e1.getValue());, тобто спадні значеннях замовити замість висхідного.
тугчем

6
Я б швидше повернувся, res != 0 ? res : e1.getKey().compareTo(e2.getKey())щоб зберегти порядок ключів з рівними значеннями.
Марко Лацкович

46

На Java 8:

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

17

TreeMapБуде завжди впорядкований по клавішах, все інше неможливо. ComparatorПросто дозволяє контролювати , як ключі упорядковано.

Якщо ви хочете відсортованих значень, вам доведеться витягнути їх у a Listі сортувати це.


10

Це неможливо зробити за допомогою Comparatorкнопки " А" , оскільки вона завжди отримає ключ карти для порівняння. TreeMapможна сортувати лише за ключем.


2
якщо TreeMap передасть ключ лише в компаратор, чи це можливо, якщо я буду посилатися на TreeMap в конструкторі компаратора, то використовуючи ключ, щоб отримати значення, щось подібне (не впевнений, як пройти посилання): class byValue реалізує компаратор {TreeMap theTree; public byValue (TreeMap theTree) {this.theTree = theTree; } public int порівняння (String k1, String k2) {// використовуйте getKey метод TreeMap для значення}}
vito huang

1
@vito: ні, тому що зазвичай одна з двох клавіш ще не буде на карті, і ви не зможете отримати її значення.
Йоахім Зауер

Хіба це не приклад того, як це робити в порівнянні з TreeMap? (незважаючи на те, що, як було сказано вище, це дійсно порушує специфікацію, для SortedMapякої визначає сортування за ключами) beginnersbook.com/2014/07/…
Marcus,

@Marcus: Comparatorвикористовує існуючу карту для отримання значень для сортування. Іншими словами, він не може сортувати довільні значення, введені в TreeMapподальшому, лише значення, які вже є в оригінальній карті.
Йоахім Зауер

5

Відповідь Олофа хороша, але для її вдосконалення потрібна ще одна річ. У коментарях під своєю відповіддю, 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 ... , але відсортований набір виконує всі порівняння елементів за допомогою його методу порівняння (або порівняння), тому два елементи, які вважаються рівними цим методом, з точки зору відсортованого набору є рівними . " ( 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;
        }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.