Сортировать карту <ключ, значение> по значениям

avatar
Lii
20 сентября 2008 в 21:03
1521013
62
1757

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

Поскольку значения не уникальны, я обнаружил, что конвертирую keySet в array и сортирую этот массив через сортировку массива с помощью настраиваемого компаратора , который сортирует по значение, связанное с ключом.

Есть способ попроще?

Источник
Hannes
9 февраля 2014 в 17:34
28

Карта не предназначена для сортировки, а для быстрого доступа к ней. Равные значения объектов нарушают ограничение карты. Используйте набор записей, например List<Map.Entry<...>> list =new LinkedList(map.entrySet()) и Collections.sort .... таким образом.

demongolem
21 декабря 2017 в 20:03
2

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

alobodzk
22 апреля 2018 в 19:10
10

Существует множество вариантов использования отсортированных карт, поэтому у вас есть TreeMap и ConcurrentSkipListMap в jdk.

Raedwald
23 июля 2019 в 10:37
0

См. Также coderhelper.com/questions/7860822/…

Peter
28 апреля 2020 в 12:28
6

TreeMap и ConcurrentSkipListMap сортируются по ключу. Вопрос в сортировке по значению.

rococo
12 июля 2021 в 06:45
0

Я хотел бы добавить, что , в зависимости от вашего варианта использования , может быть разумным просто сохранить дубликат TreeMap, который сопоставляет ваше значение с вашими ключами. Например, ваша обычная карта может иметь «a» -> 5, «b» -> 7 ». А ваша« отсортированная »карта может иметь 5 ->« a », 7 ->« b ». Вы бы просто использовали то, что map подходит в разных местах и ​​старается всегда изменять две карты вместе. Это некрасиво, и есть много предостережений и предположений, но для некоторых случаев это может быть простым и эффективным ответом по сравнению со всеми главные ответы здесь, которые полагаются на активную сортировку ваших ценностей.

Ответы (62)

avatar
18 декабря 2021 в 07:40
0
    Map<String, Integer> map = new HashMap<>();
    map.put("b", 2);
    map.put("a", 1);
    map.put("d", 4);
    map.put("c", 3);
    
    // ----- Using Java 7 -------------------
    List<Map.Entry<String, Integer>> entries = new ArrayList<>(map.entrySet());
    Collections.sort(entries, (o1, o2) -> o1.getValue().compareTo(o2.getValue()));
    System.out.println(entries); // [a=1, b=2, c=3, d=4]


    // ----- Using Java 8 Stream API --------
   map.entrySet().stream().sorted(Map.Entry.comparingByValue()).forEach(System.out::println); // {a=1, b=2, c=3, d=4}

    
avatar
16 ноября 2021 в 17:30
0

В TreeMap ключи отсортированы в естественном порядке. Например, если вы сортируете числа,

{0 = 0, 10 = 10, 20 = 20, 30 = 30, 4 = 4 , 50 = 50, 60 = 60, 70 = 70}

Чтобы исправить это, в Java8 сначала проверьте длину строки, а затем сравните.

Map<String, String> sortedMap = new TreeMap<>Comparator.comparingInt(String::length)
.thenComparing(Function.identity()));

{0 = 0, 4 = 4 , 10 = 10, 20 = 20, 30 = 30, 50 = 50, 60 = 60, 70 = 70}

avatar
djklicks-dhananjay
24 июля 2021 в 11:14
0

Сортировка хэш-карты самым простым способом на Java. Нам не нужно хранить его в древовидных картах, списках и т. Д.

Здесь я бы использовал потоки Java:

Давайте отсортируем эту карту по ее значению (в порядке возрастания)

Map<String, Integer> mp= new HashMap<>();
mp.put("zebra", 1);
mp.put("blossom", 2);
mp.put("gemini", 3);
mp.put("opera", 7);
mp.put("adelaide", 10);

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

Теперь вы можете распечатать отсортированный resultMap несколькими способами, например, используя расширенные циклы for или итераторы.

Приведенную выше карту также можно отсортировать в порядке убывания значения

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

Давайте теперь рассмотрим другой сценарий, в котором мы сохраняем «User» на карте и отсортируем его по «имени» «User» в порядке возрастания (лексикографически):

User u1= new User("hi", 135);
User u2= new User("bismuth", 900);
User u3= new User("alloy", 675);
User u4= new User("jupiter", 342);
User u5= new User("lily", 941);

Map<String, User> map2= new HashMap<>();
map2.put("zebra", u3);
map2.put("blossom", u5);
map2.put("gemini", u1);
map2.put("opera", u2);
map2.put("adelaide", u4);


Map<String, User>  resultMap= 
          map2.entrySet().stream().sorted(Map.Entry.<String, User>comparingByValue( (User o1, User o2)-> o1.getName().compareTo(o2.getName()))).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,(e1, e2) -> e2, LinkedHashMap::new));



class User
 {
    String name;
    int id;
        

public User(String name, int id) {
    super();
    this.name = name;
    this.id = id;
}
public String getName() {
    return name;
}
public void setName(String name) {
    this.name = name;
}
public int getId() {
    return id;
}
public void setId(int id) {
    this.id = id;
}
@Override
public String toString() {
    return "User [name=" + name + ", id=" + id + "]";
}
@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + id;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return result;
}
@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    User other = (User) obj;
    if (id != other.id)
        return false;
    if (name == null) {
        if (other.name != null)
            return false;
    } else if (!name.equals(other.name))
        return false;
    return true;


    }
 }
avatar
27 января 2021 в 10:14
3

Этого очень легко добиться с помощью java 8

public static LinkedHashMap<Integer, String> sortByValue(HashMap<Integer, String> map) {

        List<Map.Entry<Integer, String>> list = new ArrayList<>(map.entrySet());
        list.sort(Map.Entry.comparingByValue());
        LinkedHashMap<Integer, String> sortedMap = new LinkedHashMap<>();
        list.forEach(e -> sortedMap.put(e.getKey(), e.getValue()));
        return sortedMap;
    }
avatar
XIAOLONG LI
11 сентября 2020 в 13:49
0

Я могу привести вам пример, но я уверен, что это именно то, что вам нужно.

map = {10 = 3, 11 = 1,12 = 2} 

Допустим, вам нужны 2 верхних наиболее часто используемых ключа: (10, 12) Итак, самый простой способ - использовать PriorityQueue для сортировки на основе значения карты.

PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> (map.get(a) - map.get(b));
for(int key: map.keySets()) {
   pq.add(key);
   if(pq.size() > 2) {
      pq.poll();
   }
}
// Now pq has the top 2 most frequent key based on value. It sorts the value. 
avatar
avi
2 сентября 2020 в 16:57
5

Простой способ сортировки любой карты в Java 8 и выше

Map<String, Object> mapToSort = new HashMap<>();

List<Map.Entry<String, Object>> list = new LinkedList<>(mapToSort.entrySet());

Collections.sort(list, Comparator.comparing(o -> o.getValue().getAttribute()));

HashMap<String, Object> sortedMap = new LinkedHashMap<>();
for (Map.Entry<String, Object> map : list) {
   sortedMap.put(map.getKey(), map.getValue());
}

, если вы используете Java 7 и ниже

Map<String, Object> mapToSort = new HashMap<>();

List<Map.Entry<String, Object>> list = new LinkedList<>(mapToSort.entrySet());

Collections.sort(list, new Comparator<Map.Entry<String, Object>>() {
    @Override
    public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
       return o1.getValue().getAttribute().compareTo(o2.getValue().getAttribute());      
    }
});

HashMap<String, Object> sortedMap = new LinkedHashMap<>();
for (Map.Entry<String, Object> map : list) {
   sortedMap.put(map.getKey(), map.getValue());
}
avatar
user157196
20 июня 2020 в 09:12
430

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

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

Больше нет (coderhelper.com/questions/109383/…). Кроме того, почему был актерский состав на Double? Разве это не должно быть просто return ((Comparable)base.get(a).compareTo(((Comparable)base.get(b)))?

mlwida
20 августа 2010 в 07:00
12

@Stephen: Нет. В этом случае все ключи, равные по значению, отбрасываются (разница между равными и сравнением по ссылке). Дополнительно: даже этот код имеет проблемы со следующей последовательностью map.put("A","1d");map.put("B","1d");map.put("C",67d);map.put("D",99.5d);

mR_fr0g
1 декабря 2010 в 14:36
43

Компаратор, используемый для древовидной карты, несовместим с equals (см. Javadox sortMap). Это означает, что удаление элементов из древовидной карты не сработает. sorted_map.get ("A") вернет null. Это означает, что использование древовидной карты нарушено.

aalku
5 ноября 2011 в 16:16
0

@ mR_fr0g Это верно, только если базовая карта изменена. Пока он не изменен, естественный порядок ключей (в соответствии с компаратором) будет соответствовать естественному порядку значений.

Maxy-B
24 ноября 2011 в 04:37
89

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

lisak
23 мая 2012 в 14:37
4

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

haylem
3 июля 2012 в 21:19
63

Луи Вассерман (да, один из парней из Google Guava) на самом деле очень не любит этот ответ: «Он ломается несколькими действительно запутанными способами, если вы даже посмотрите на него смешно. Если фоновая карта изменится, она сломается. Если несколько клавиш сопоставить с тем же значением, он сломается. Если вы вызовете get для ключа, которого нет в резервной карте, он сломается. Если вы сделаете что-либо, что приведет к поиску по ключу, которого нет в карта - вызов Map.equals, containsKey, что угодно - она ​​сломается с действительно странными трассировками стека. " plus.google.com/102216152814616302326/posts/bEQLDK712MJ

Tomasz
6 сентября 2012 в 17:14
2

Если ValueComparator # compare () не возвращает 0 для равенства (см. Отредактированный ответ), комментарий @ Maxy-B об отсутствующих ключах больше не применяется

Volodymyr Krupach
8 апреля 2013 в 15:58
8

Я сожалею об использовании такого подхода. Теперь я воспроизвел в своем коде все ошибки, описанные здесь, в комментариях. Моя ошибка заключалась в том, что я не читал комментарии. Этот coderhelper.com/a/2581754/1537800 отлично работает.

Konrad Morawski
11 сентября 2013 в 22:05
3

var sorted_map = map.OrderByDescending(entry => entry.Value); (на C #). Извините, не удержался;) +1 хотя

Mohammad
11 марта 2014 в 09:05
1

Думаю, добавление if (a.equals (b)) return 0; в сравнении сделает его идеальным. Так как это устранит повторяющиеся ключи, + сделает возможным нормальный обход. Сообщите мне, если я чего-то упускаю, я собираюсь дать этому шанс в производстве ...: P

Aniket Kapse
13 апреля 2015 в 12:54
1

Разве мы не можем вместо этого просто использовать TreeMap? Я что-нибудь пропустил?

Victor
22 августа 2015 в 15:57
0

@AniketKapse, вы можете использовать только древовидную карту, созданную после заполнения карты (ValueComparator не нужен). Затем вы можете перемещаться по карте, следуя порядку.

Bruce Zu
14 января 2016 в 19:28
0

@Override public int compare (символ o1, символ o2) {if (charToTimes.get (o1) == null || charToTimes.get (o2) == null) {return -o1.compareTo (o2); } int result = -charToTimes.get (o1) .compareTo (charToTimes.get (o2)); если (результат! = 0) {вернуть результат; } return -o1.compareTo (o2);}

hephestos
9 октября 2018 в 08:44
0

Разве вы не сравниваете объекты, а не примитивные значения ... поэтому вы не сравниваете позицию в памяти вместо реальных значений?

Alex Salauyou
16 мая 2019 в 12:20
0

возврат -1 для равных значений - очень неправильный способ. Он пройдет небольшие тесты и некоторое время проработает, пока внезапно не выдаст IllegalArgumentException

avatar
30 марта 2020 в 14:20
1
map = your hashmap;

List<Map.Entry<String, Integer>> list = new LinkedList<Map.Entry<String, Integer>>(map.entrySet());
Collections.sort(list, new cm());//IMP

HashMap<String, Integer> sorted = new LinkedHashMap<String, Integer>();
for(Map.Entry<String, Integer> en: list){
    sorted.put(en.getKey(),en.getValue());
}

System.out.println(sorted);//sorted hashmap

создать новый класс

class cm implements Comparator<Map.Entry<String, Integer>>{
    @Override
    public int compare(Map.Entry<String, Integer> a, 
                            Map.Entry<String, Integer> b)
    {
        return (a.getValue()).compareTo(b.getValue());
    }
}
avatar
4 февраля 2020 в 11:52
1

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

import static java.util.Comparator.comparingInt;
import static java.util.stream.Collectors.toMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class Utils {
    public static Map<String, Integer> sortMapBasedOnValues(Map<String, Integer> map, boolean descending) {
        int multiplyBy = (descending) ? -1: 1;
        Map<String, Integer> sorted =  map.entrySet().stream()
                .sorted(comparingInt(e -> multiplyBy * e.getValue() ))
                .collect(toMap(
                        Map.Entry::getKey, 
                        Map.Entry::getValue,
                        (a, b) -> { throw new AssertionError();},
                        LinkedHashMap::new
                    ));
        return sorted;
    }
}
avatar
6 декабря 2019 в 11:58
1

Использование LinkedList

//Create a list by HashMap
List<Map.Entry<String, Double>> list = new LinkedList<>(hashMap.entrySet());

//Sorting the list
Collections.sort(list, new Comparator<Map.Entry<String, Double>>() {
    public int compare(Map.Entry<String, Double> o1, Map.Entry<String, Double> o2) {
        return (o1.getValue()).compareTo(o2.getValue());
    }
});

//put data from sorted list to hashmap
HashMap<String, Double> sortedData = new LinkedHashMap<>();
for (Map.Entry<String, Double> data : list) {
    sortedData.put(data.getKey(), data.getValue());
}

System.out.print(sortedData);
avatar
21 ноября 2019 в 08:02
18

Данная карта

   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}

Ali Tou
7 февраля 2020 в 11:53
0

Это работает, но я не понимаю, как порядок элементов влияет на HashMap?

WesternGun
18 октября 2020 в 18:07
1

Если вы имеете в виду чистый HashMap, порядок не имеет значения . Но в LinkedHashMap порядок вставки сохраняется.

avatar
Kaplan
15 ноября 2019 в 10:09
0

создает список записей для каждого значения, где значения отсортированы
требуется Java 8 или выше

Map<Double,List<Entry<String,Double>>> sorted =
map.entrySet().stream().collect( Collectors.groupingBy( Entry::getValue, TreeMap::new,
    Collectors.mapping( Function.identity(), Collectors.toList() ) ) );

с использованием карты {[A = 99,5], [B = 67,4], [C = 67,4], [D = 67,3]}
получает {67.3=[D=67.3], 67.4=[B=67.4, C=67.4], 99.5=[A=99.5]}


… и как получить доступ к каждой записи одну за другой:

sorted.entrySet().forEach( e -> e.getValue().forEach( l -> System.out.println( l ) ) );

D=67.3 B=67.4 C=67.4 A=99.5

avatar
27 августа 2019 в 21:32
0

Использование библиотеки Guava:

public static <K,V extends Comparable<V>>SortedMap<K,V> sortByValue(Map<K,V> original){
    var comparator = Ordering.natural()
            .reverse() // highest first
            .nullsLast()
            .onResultOf(Functions.forMap(original, null))
            .compound(Ordering.usingToString());
    return ImmutableSortedMap.copyOf(original, comparator);
}
avatar
23 июля 2019 в 12:23
1

публикация моей версии ответа

List<Map.Entry<String, Integer>> list = new ArrayList<>(map.entrySet());
    Collections.sort(list, (obj1, obj2) -> obj2.getValue().compareTo(obj1.getValue()));
    Map<String, Integer> resultMap = new LinkedHashMap<>();
    list.forEach(arg0 -> {
        resultMap.put(arg0.getKey(), arg0.getValue());
    });
    System.out.println(resultMap);
avatar
user_3380739
29 января 2019 в 06:07
2

Вот код Java 8 с AbacusUtil

Map<String, Integer> map = N.asMap("a", 2, "b", 3, "c", 1, "d", 2);
Map<String, Integer> sortedMap = Stream.of(map.entrySet()).sorted(Map.Entry.comparingByValue()).toMap(e -> e.getKey(), e -> e.getValue(),
    LinkedHashMap::new);
N.println(sortedMap);
// output: {c=1, a=2, d=2, b=3}

Декларация : Я разработчик AbacusUtil.

charles-allen
19 августа 2017 в 17:13
1

Какая часть ответа использует AbacusUtil? Просто toMap() помощник?

avatar
parsecer
4 декабря 2018 в 17:29
1

Самый простой метод грубой силы sortHashMap для HashMap<String, Long>: вы можете просто скопировать его и использовать 47015470154

public class Test  {
    public static void main(String[] args)  {
        HashMap<String, Long> hashMap = new HashMap<>();
        hashMap.put("Cat", (long) 4);
        hashMap.put("Human", (long) 2);
        hashMap.put("Dog", (long) 4);
        hashMap.put("Fish", (long) 0);
        hashMap.put("Tree", (long) 1);
        hashMap.put("Three-legged-human", (long) 3);
        hashMap.put("Monkey", (long) 2);

        System.out.println(hashMap);  //{Human=2, Cat=4, Three-legged-human=3, Monkey=2, Fish=0, Tree=1, Dog=4}
        System.out.println(sortHashMap(hashMap));  //{Cat=4, Dog=4, Three-legged-human=3, Human=2, Monkey=2, Tree=1, Fish=0}
    }

    public LinkedHashMap<String, Long> sortHashMap(HashMap<String, Long> unsortedMap)  {
        LinkedHashMap<String, Long> result = new LinkedHashMap<>();

        //add String keys to an array: the array would get sorted, based on those keys' values
        ArrayList<String> sortedKeys = new ArrayList<>();
        for (String key: unsortedMap.keySet())  {
            sortedKeys.add(key);
        }

        //sort the ArrayList<String> of keys    
        for (int i=0; i<unsortedMap.size(); i++)  {
            for (int j=1; j<sortedKeys.size(); j++)  {
                if (unsortedMap.get(sortedKeys.get(j)) > unsortedMap.get(sortedKeys.get(j-1))) {
                    String temp = sortedKeys.get(j);
                    sortedKeys.set(j, sortedKeys.get(j-1));
                    sortedKeys.set(j-1, temp);
                }
            }
        }

        // construct the result Map
        for (String key: sortedKeys)  {
            result.put(key, unsortedMap.get(key));
        }

        return result;
    }
}

Kaplan
17 ноября 2019 в 12:56
0

Целочисленный литерал имеет тип long , если он дополнен буквой ASCII L или l <83835569513810> 495, например. 3.10.1. Целочисленные литералы

avatar
assylias
18 сентября 2018 в 06:04
81

В 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));
Vlad Holubiev
1 июня 2014 в 19:18
0

Как отсортировать в обратном порядке?

Vlad Holubiev
9 августа 2014 в 08:26
6

нашел решение - Collections.reverseOrder(comparing(Entry::getValue))

Jake Stokes
2 октября 2017 в 14:31
1

Я думаю, что вижу здесь опечатку - не следует ли называть "toMap" как "Collectors.toMap ()"?

assylias
3 октября 2017 в 05:59
2

@JakeStokes Или используйте статический импорт :-)

Gediminas Rimsa
24 ноября 2018 в 09:20
6

Лучший способ сортировки по значению ввода в обратном порядке: Entry.comparingByValue(Comparator.reverseOrder())

avatar
Kenston Choi
7 сентября 2018 в 07:18
1

Если существует предпочтение иметь структуру данных Map, которая по своей сути сортирует по значениям без необходимости запускать какие-либо методы сортировки или явно передавать служебной программе, тогда могут быть применимы следующие решения:

(1) org.drools.chance.core.util.ValueSortedMap (проект JBoss) поддерживает две внутренние карты: одну для поиска и одну для хранения отсортированных значений. Очень похоже на ранее добавленные ответы, но, вероятно, это часть абстракции и инкапсуляции (включая механизм копирования), которая делает его более безопасным для использования извне.

(2) http://techblog.molindo.at/2008/11/java-map-sorted-by-value.html избегает поддержки двух карт и вместо этого полагается / расширяется из LinkedMap Apache Common . (Примечание автора блога: as all the code here is in the public domain):

// required to access LinkEntry.before and LinkEntry.after
package org.apache.commons.collections.map;

// SNIP: imports

/**
* map implementation based on LinkedMap that maintains a sorted list of
* values for iteration
*/
public class ValueSortedHashMap extends LinkedMap {
    private final boolean _asc;

    // don't use super()!
    public ValueSortedHashMap(final boolean asc) {
        super(DEFAULT_CAPACITY);
        _asc = asc;
    }

    // SNIP: some more constructors with initial capacity and the like

    protected void addEntry(final HashEntry entry, final int hashIndex) {
        final LinkEntry link = (LinkEntry) entry;
        insertSorted(link);
        data[hashIndex] = entry;
    }

    protected void updateEntry(final HashEntry entry, final Object newValue) {
        entry.setValue(newValue);
        final LinkEntry link = (LinkEntry) entry;
        link.before.after = link.after;
        link.after.before = link.before;
        link.after = link.before = null;
        insertSorted(link);
    }

    private void insertSorted(final LinkEntry link) {
        LinkEntry cur = header;
        // iterate whole list, could (should?) be replaced with quicksearch
        // start at end to optimize speed for in-order insertions
        while ((cur = cur.before) != header & amp; & amp; !insertAfter(cur, link)) {}
        link.after = cur.after;
        link.before = cur;
        cur.after.before = link;
        cur.after = link;
    }

    protected boolean insertAfter(final LinkEntry cur, final LinkEntry link) {
        if (_asc) {
            return ((Comparable) cur.getValue())
            .compareTo((V) link.getValue()) & lt; = 0;
        } else {
            return ((Comparable) cur.getValue())
            .compareTo((V) link.getValue()) & gt; = 0;
        }
    }

    public boolean isAscending() {
        return _asc;
    }
}

(3) Напишите пользовательский Map или расширенный от LinkedHashMap, который будет сортировать только во время перечисления (например, values(), keyset(), entryset()) по мере необходимости. Внутренняя реализация / поведение абстрагируется от той, которая использует этот класс, но клиенту этого класса кажется, что значения всегда сортируются при запросе для перечисления. Этот класс надеется, что сортировка будет происходить в основном один раз, если все операции put были завершены до перечисления. Метод сортировки использует некоторые из предыдущих ответов на этот вопрос.

public class SortByValueMap<K, V> implements Map<K, V> {

    private boolean isSortingNeeded = false;

    private final Map<K, V> map = new LinkedHashMap<>();

    @Override
    public V put(K key, V value) {
        isSortingNeeded = true;
        return map.put(key, value);
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> map) {
        isSortingNeeded = true;
        map.putAll(map);
    }

    @Override
    public Set<K> keySet() {
        sort();
        return map.keySet();
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        sort();
        return map.entrySet();
    }

    @Override
    public Collection<V> values() {
        sort();
        return map.values();
    }

    private void sort() {
        if (!isSortingNeeded) {
            return;
        }

        List<Entry<K, V>> list = new ArrayList<>(size());

        for (Iterator<Map.Entry<K, V>> it = map.entrySet().iterator(); it.hasNext();) {
            Map.Entry<K, V> entry = it.next();
            list.add(entry);
            it.remove();
        }

        Collections.sort(list);

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

        isSortingNeeded = false;
    }

    @Override
    public String toString() {
        sort();
        return map.toString();
    }
}

(4) Guava предлагает ImmutableMap.Builder.orderEntriesByValue (Comparator valueComparator), хотя результирующая карта будет неизменной:

Настраивает этот Builder для сортировки записей по значению в соответствии с указанный компаратор.

Порядок сортировки стабильный, то есть если две записи имеют значения, сравнить как эквивалент, запись, которая была вставлена ​​первой, будет первой в порядке итерации построенной карты.

avatar
smart-developer
22 августа 2018 в 16:22
1

Я переписал метод Девинмура, который выполняет сортировку карты по ее значению без использования Iterator:

public static Map<K, V> sortMapByValue(Map<K, V> inputMap) {

    Set<Entry<K, V>> set = inputMap.entrySet();
    List<Entry<K, V>> list = new ArrayList<Entry<K, V>>(set);

    Collections.sort(list, new Comparator<Map.Entry<K, V>>()
    {
        @Override
        public int compare(Entry<K, V> o1, Entry<K, V> o2) {
            return (o1.getValue()).compareTo( o2.getValue() );  //Ascending order
        }
    } );

    Map<K, V> sortedMap = new LinkedHashMap<>();

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

    return sortedMap;
}

Note:, что мы использовали LinkedHashMap в качестве выходной карты, потому что наш список был отсортирован по значению, и теперь мы должны сохранить наш список в выходной карте с порядком вставленных ключей и значений. Поэтому, если вы используете, например, TreeMap в качестве выходной карты, ваша карта снова будет отсортирована по ключам карты!

Это основной метод:

public static void main(String[] args) {
    Map<String, String> map = new HashMap<>();
    map.put("3", "three");
    map.put("1", "one");
    map.put("5", "five");
    System.out.println("Input Map:" + map);
    System.out.println("Sorted Map:" + sortMapByValue(map));
}

Наконец, вот результат:

Input Map:{1=one, 3=three, 5=five}
Sorted Map:{5=five, 1=one, 3=three}
avatar
Pankaj Singhal
18 мая 2018 в 08:50
7

Поздний вход.

С появлением 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()
Peter T.
11 января 2019 в 08:58
0

Спасибо за эту прокомментированную версию. Один вопрос: в чем разница между использованием Entry.comparingByValue() (как ответ на предыдущий ответ coderhelper.com/a/22132422/1480587) или comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey), который вы использовали? Я так понимаю, вы также сравниваете ключи, если значения идентичны, верно? Я заметил, что при сортировке сохраняется порядок элементов с одним и тем же значением - так нужна ли сортировка по ключам, если ключи уже были отсортированы раньше?

avatar
Carter Page
3 апреля 2018 в 00:00
957

Вот общая версия:

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;
    }
}
ciamej
30 мая 2012 в 00:50
0

зачем использовать Collections.sort, если есть Arrays.sort? см. мой ответ для более подробной информации

John Mark
19 июня 2012 в 16:06
0

Это сэкономило мне кучу времени! Тестовый пример тоже отличный. :) Есть ли причина, по которой вы использовали LinkedHashMap вместо обычного HashMap?

Carter Page
1 июля 2012 в 12:46
11

Рад, что это помогает. Джон, LinkedHashMap важен для решения, поскольку обеспечивает предсказуемый порядок итераций.

buzz3791
25 апреля 2013 в 15:02
0

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

Carter Page
25 апреля 2013 в 18:55
3

@ buzz3791 Верно. Так будет с любым алгоритмом сортировки. Изменение значения узлов в структуре во время сортировки приводит к непредсказуемым (и почти всегда плохим) результатам.

fire in the hole
1 ноября 2014 в 07:53
0

НЕ РАБОТАЕТ, я пробовал ваш код в android, он сортирует хэш-карту по значению, но после этого, когда он выполняет итерацию, чтобы заполнить карту результатов, он снова сортируется по ключу

saiyancoder
8 декабря 2014 в 01:12
3

@Sheagorath Я пробовал на Android, тоже работает. Это не проблема конкретной платформы, учитывая, что вы используете версию Java 6. Правильно ли вы реализовали Comparable в своем объекте значения?

anton1980
2 февраля 2015 в 22:29
0

Это решение не сработало для меня. Я использую BigDecimal в качестве объекта значения и Pair <K, V> в качестве ключа (из Apache Commons). При обращении к карте после сортировки значения не сортируются.

rob
27 июня 2015 в 14:06
6

Разве версия Java 8 не должна использовать forEachOrdered вместо forEach, поскольку в документации forEach указано: «Поведение этой операции явно недетерминировано»?

Nathan Beach
2 сентября 2015 в 03:07
1

полностью разорвал это, но указал @CarterPage в комментариях (в любом случае он будет в проекте с открытым исходным кодом). Спасибо.

akhil_mittal
6 сентября 2015 в 11:55
0

Есть ли конкретная причина для использования LinkedList и LinkedHashMap?

Reinstate Monica
30 сентября 2015 в 14:53
0

@akhil_mittal LinkedList следует заменить на ArrayList, но LinkedHashMap необходим, поскольку он сохраняет порядок вставки.

cirko
13 ноября 2015 в 12:40
0

@trillions Не могли бы вы сказать мне, какое изменение вы внесли, чтобы отменить порядок? Спасибо.

Ferrybig
17 ноября 2015 в 11:26
0

+1, но тест junit не самый лучший, исходя из случайности он может провалиться или пройти без изменений в коде

Alexis C.
20 декабря 2015 в 21:25
0

Версия Java 8 должна использовать подход коллекционеров ... Посмотрите на Collectors.toMap.

basZero
4 октября 2016 в 15:33
0

Версия Java 8 компилируется только в моей среде Eclipse, если я заменяю LinkedHashMap::new на LinkedHashMap<K, V>::new.

emilly
28 ноября 2016 в 14:50
0

Я думаю, что Linked List следует заменить на ArrayList, поскольку мы просто хотим перебирать список.

R. Oosterholt
5 мая 2017 в 08:20
0

Возможно, вы захотите изменить for(int i = 0 ; i < 1000 ; ++i) { на while (testMap.size() < 1000) { в модульном тесте, потому что тот же ключ может быть сгенерирован случайным образом. Это может не соответствовать утверждению testMap.size().

weston
15 мая 2017 в 16:35
0

Мне не нравится тест, который каждый раз использует разные данные new Random(System.currentTimeMillis())

Lii
10 марта 2018 в 22:14
0

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

Cecilia
27 октября 2019 в 20:51
0

Что, если я хочу отсортировать по ключу (который содержит строку)?

Holger
25 ноября 2019 в 10:46
0

@Cecilia, затем замените comparingByValue на comparingByKey.

Akanksha
12 марта 2021 в 12:01
0

Это потрясающе! Спасибо!

avatar
Arun Raaj
10 марта 2018 в 21:32
0
public class Test {
  public static void main(String[] args) {
    TreeMap<Integer, String> hm=new TreeMap();
    hm.put(3, "arun singh");
    hm.put(5, "vinay singh");
    hm.put(1, "bandagi singh");
    hm.put(6, "vikram singh");
    hm.put(2, "panipat singh");
    hm.put(28, "jakarta singh");

    ArrayList<String> al=new ArrayList(hm.values());
    Collections.sort(al, new myComparator());

    System.out.println("//sort by values \n");
    for(String obj: al){
        for(Map.Entry<Integer, String> map2:hm.entrySet()){
            if(map2.getValue().equals(obj)){
                System.out.println(map2.getKey()+" "+map2.getValue());
            }
        } 
     }
  }
}

class myComparator implements Comparator{
    @Override
    public int compare(Object o1, Object o2) {
       String o3=(String) o1;
       String o4 =(String) o2;
       return o3.compareTo(o4);
    }   
}

ВЫХОД =

//sort by values 

3 arun singh
1 bandagi singh
28 jakarta singh
2 panipat singh
6 vikram singh
5 vinay singh
Stephen C
7 октября 2018 в 01:05
0

Решение O (N ^ 2). И это приведет к ложным результатам, если будут повторяющиеся значения.

avatar
Brian Goetz
25 ноября 2017 в 22:16
395

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

Затем вы можете перейти к использованию других потоковых операций для потребления данных. Например, если вы хотите попасть в первую десятку на новой карте:

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);
Benj
9 декабря 2014 в 18:21
0

Хорошо, но как насчет использования parallelStream() в этом случае?

Brian Goetz
9 декабря 2014 в 18:37
11

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

Benj
10 декабря 2014 в 19:30
0

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

Pete
28 апреля 2015 в 15:04
0

Как я могу отсортировать по значениям списки разного размера? Я хочу отсортировать список по убыванию.

CyberMew
6 октября 2015 в 09:18
0

После сортировки, как мы будем извлекать карту из Stream <>?

Leo
19 августа 2016 в 23:34
2

разве вам не нужно использовать compareByValue в примере из топ-10?

CKM
4 июля 2017 в 06:25
0

Как скопировать отсортированные значения карты в список?

Steven
21 августа 2017 в 11:19
0

Часть для создания первой десятки неверна, вам нужно добавить еще два параметра, как указано здесь: coderhelper.com/a/19671853/5655767

Vadzim
21 ноября 2017 в 00:28
0

Collectors.toMap здесь по умолчанию будет простой HashMap, что приведет к отмене упорядочивания. Обратитесь к верхнему ответу для правильного сбора на LinkedHashMap.

shmosel
21 ноября 2017 в 00:47
0

@Vadzim Смысл в том, чтобы захватить топ-10, а не сохранить порядок.

Vadzim
21 ноября 2017 в 01:50
0

@shmosel, порядок имеет смысл даже для топ-10;)

shmosel
21 ноября 2017 в 01:51
0

@Vadzim Не уверен, что вы пытаетесь сказать.

OrangeDog
18 июня 2019 в 14:30
1

@Benj он будет работать с точки зрения извлечения топ-10, но получившаяся карта больше не будет заказываться.

Sourabh
11 января 2020 в 15:17
0

Что здесь происходит в версии потока java8, чтобы попасть в топ-10? Может кто-нибудь мне это объяснить?

avatar
David Bleckmann
5 июня 2017 в 21:36
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;
    }
}
Ved Prakash
28 мая 2019 в 17:41
0

Если Comparable и Comparator не разрешены, как это сделать?

David Bleckmann
30 мая 2019 в 04:57
0

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

avatar
Stephen
23 мая 2017 в 12:26
213

Три однострочных ответа ...

Я бы использовал Коллекции Google Гуава для этого - если ваши значения , вы можете использовать

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

Которая создаст функцию (объект) для карты [которая принимает любой из ключей в качестве входных данных, возвращая соответствующее значение], а затем применяет естественный (сопоставимый) порядок к ним [значениям].

Если они не сопоставимы, вам нужно сделать что-то вроде

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

Их можно применить к TreeMap (поскольку Ordering расширяет Comparator) или к LinkedHashMap после некоторой сортировки

NB : если вы собираетесь использовать TreeMap, помните, что если сравнение == 0, то элемент уже есть в списке (что произойдет, если у вас есть несколько значений, которые сравнивают одинаковые ). Чтобы облегчить это, вы можете добавить свой ключ в компаратор следующим образом (при условии, что ваши ключи и значения равны Comparable):

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

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

Обратите внимание, что это все равно не будет работать, если ваши ключи сравниваются с 0, но этого должно быть достаточно для большинства элементов comparable (поскольку hashCode, equals ​​и compareTo часто синхронизируются ...)

См. Ordering.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 для сортировки. Но перед этим мы проверяем хеш-карту, чтобы убедиться, что ключ на самом деле не дубликат. Кроме того, созданный нами компаратор также будет включать ключ, чтобы повторяющиеся значения не удаляли неповторяющиеся ключи (из-за == сравнения). Эти 2 элемента являются жизненно важными для обеспечения сохранения контракта на карту; если вы думаете, что этого не хотите, то вы почти готовы полностью перевернуть карту (на Map<V,K>).

Конструктор должен вызываться как

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

smallufo
10 ноября 2010 в 10:58
0

Привет, @Stephen, не могли бы вы привести пример использования упорядочивания? Я смотрю исходный код Ordering и совершенно не могу понять, что возвращает .natural (). OnResultOf (...)! Исходный код - "public <F> Ordering <F> onResultOf", я даже не знаю, как он компилируется! Самое главное, как использовать «<F> Упорядочивание <F>» для сортировки карты? Это компаратор или что-то в этом роде? Спасибо.

Stephen
11 ноября 2010 в 11:44
0

Ordering - это просто богатый Comparator. Я пробовал прокомментировать каждый пример (курсив под каждым). "естественный" означает, что объекты имеют размер Comparable; это похоже на ComparableComparator apache common. onResultOf применяет функцию к сравниваемому элементу. Итак, если у вас была функция, которая добавляла 1 к целому числу, тогда natural().onResultOf(add1Function).compare(1,2) в конечном итоге выполнял бы 2.compareTo(3)

lbalazscs
30 апреля 2013 в 09:39
0

ImmutableSortedMap.copyOf выбрасывает исключение IllegalArgumentException, если в исходной карте есть повторяющиеся значения.

Stephen
1 мая 2013 в 00:31
0

@Ibalazscs Да, будет - у вас должна быть возможность использовать ImmutableSetMultiMap или ImmutableListMultiMap для хранения коллекции повторяющихся переменных.

alex
21 августа 2013 в 09:22
1

Спасибо за это, я использовал ваше решение в одном проекте. Я думаю, что здесь есть проблема: чтобы вести себя как карта, она должна возвращать значение, ранее связанное с ключом, если оно существует, но этого никогда не будет. Решение, которое я использовал, - вернуть удаленное значение, если оно существует.

Abdull
22 апреля 2014 в 09:41
0

В настоящее время class ValueComparableMap<K extends Comparable<K>,V> extends TreeMap<K,V>. Почему K extends Comparable<K>? А как насчет всего K? Требование, чтобы тип ключа K был Comparable, кажется чрезмерно ограничительным.

Stephen
22 апреля 2014 в 18:07
0

@Abdull, вы, вероятно, захотите прочитать абзац под кодом несколько раз. (Я только что перечитал, и через год у меня возникли небольшие проблемы;)). Мы должны убедиться, что ключи не засоряются (иначе мы могли бы удалить элементы с такими же значениями). Самый простой способ сделать это - сделать ключ сопоставимым. Так что да, это ограничительно, но в противном случае вам понадобится 2 компаратора (что, безусловно, возможно, но выходит за рамки вопроса).

Christian Hujer
5 марта 2015 в 22:47
0

Разве это не могло бы просто работать без valueMap, используя super.put () в первую очередь? Просто подумал, еще не пробовал ...

Ryan The Leach
10 июня 2015 в 02:39
0

Значение объектаComparator = Ordering.natural (). OnResultOf (Functions.forMap (карта)). Соединение (Ordering.natural ()); приводит к ошибке, не удается разрешить .compound (Ordering <Comparable>)

has981
5 июня 2016 в 07:56
0

Спасибо за решение, мне пришлось добавить: @Override public boolean containsKey(Object key) { return valueMap.containsKey(key); }, чтобы преодолеть исключение, возникающее при вызове containsKey.

avatar
20 февраля 2017 в 05:37
-5

Лучше всего преобразовать HashMap в TreeMap. Ключи сортировки TreeMap сами по себе. Если вы хотите отсортировать значения, чем быстрое исправление, вы можете переключать значения с помощью ключей, если ваши значения не дублируются.

avatar
Nilesh Jadav
5 декабря 2016 в 22:29
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
avatar
23 сентября 2016 в 11:10
1

Мое решение - довольно простой подход к использованию в основном заданных API. Мы используем функцию карты для экспорта ее содержимого как Set с помощью метода entrySet () . Теперь у нас есть Set , содержащий объекты Map.Entry .

Хорошо, Set не передает заказ, но мы можем взять содержимое и поместить его в ArrayList . Теперь он имеет случайный порядок , но мы все равно его отсортируем.

Поскольку ArrayList является Коллекцией , теперь мы используем метод Collections.sort () , чтобы навести порядок в хаосе. Поскольку наши объекты Map.Entry не реализуют нужный тип сравнения, мы предоставляем настраиваемый компаратор .

public static void main(String[] args) {
    HashMap<String, String> map = new HashMap<>();
    map.put("Z", "E");
    map.put("G", "A");
    map.put("D", "C");
    map.put("E", null);
    map.put("O", "C");
    map.put("L", "D");
    map.put("Q", "B");
    map.put("A", "F");
    map.put(null, "X");
    MapEntryComparator mapEntryComparator = new MapEntryComparator();

    List<Entry<String,String>> entryList = new ArrayList<>(map.entrySet());
    Collections.sort(entryList, mapEntryComparator);

    for (Entry<String, String> entry : entryList) {
        System.out.println(entry.getKey() + " : " + entry.getValue());
    }

}
SMUsamaShah
18 марта 2021 в 16:01
0

Что такое MapEntryComparator?

avatar
devinmoore
14 июня 2016 в 07:51
187

Из 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;
}
Dimitris Andreou
8 апреля 2010 в 13:13
16

Список для сортировки - «новый LinkedList» ?? Ну и дела. К счастью, Collections.sort () сначала выгружает список в массив, чтобы избежать именно такой ошибки (но, тем не менее, выгрузка ArrayList в массив должна быть быстрее, чем то же самое для LinkedList).

lisak
3 июня 2011 в 16:31
0

не может преобразовать Iterator в TernaryTree.Iterator

ciamej
30 мая 2012 в 00:49
0

зачем использовать Collections.sort, если есть Arrays.sort? см. мой ответ для более подробной информации

kaspersky
28 ноября 2012 в 11:15
0

@DimitrisAndreou, почему плохо сортировать связанный список?

Dimitris Andreou
29 ноября 2012 в 19:29
5

@ gg.kaspersky Я не говорю, что «сортировать LinkedList - плохо», но сам LinkedList - плохой выбор, независимо от сортировки. Намного лучше использовать ArrayList, а для дополнительных точек размер точно равен map.size (). См. Также code.google.com/p/memory-measurer/wiki/… средняя стоимость элемента в ArrayList: 5 байтов, средняя стоимость элемента в LinkedList: 24 байта. Для ArrayList точного размера средняя стоимость составит 4 байта. То есть LinkedList занимает ШЕСТЬ раз больше объема памяти, который требуется ArrayList. Это просто раздувание

Rishi Dua
26 июня 2014 в 03:33
0

Показывает ошибку: метод sort (List <T>, Comparator <? Super T>) в типе Collections не применим для аргументов (List, new Comparator () {})

Rishi Dua
26 июня 2014 в 03:34
0

^ Извините за это. Я не импортировал java.util.comparator

ram
16 апреля 2015 в 13:09
2

с использованием приведенных выше значений были отсортированы в порядке возрастания. Как сортировать по убыванию?

Soheil
5 июня 2017 в 20:14
1

Замените o1 и o2 для сортировки по убыванию.

avatar
ZBruce
15 января 2016 в 02:18
-2
    static <K extends Comparable<? super K>  V extends Comparable<? super V>>
    Map sortByValueInDescendingOrder(final Map<K, V> map) {
        Map re = new TreeMap(new Comparator<K>() {
            @Override
            public int compare(K o1, K o2) {
                if (map.get(o1) == null || map.get(o2) == null) {
                    return -o1.compareTo(o2);
                }
                int result = -map.get(o1).compareTo(map.get(o2));
                if (result != 0) {
                    return result;
                }
                return -o1.compareTo(o2);
            }
        });
        re.putAll(map);
        return re;
    }
    @Test(timeout = 3000l, expected = Test.None.class)
    public void testSortByValueInDescendingOrder() {
        char[] arr = "googler".toCharArray();
        Map<Character, Integer> charToTimes = new HashMap();
        for (int i = 0; i < arr.length; i++) {
            Integer times = charToTimes.get(arr[i]);
            charToTimes.put(arr[i], times == null ? 1 : times + 1);
        }
        Map sortedByTimes = sortByValueInDescendingOrder(charToTimes);
        Assert.assertEquals(charToTimes.toString(), "{g=2, e=1, r=1, o=2, l=1}");
        Assert.assertEquals(sortedByTimes.toString(), "{o=2, g=2, r=1, l=1, e=1}");
        Assert.assertEquals(sortedByTimes.containsKey('a'), false);
        Assert.assertEquals(sortedByTimes.get('a'), null);
        Assert.assertEquals(sortedByTimes.get('g'), 2);
        Assert.assertEquals(sortedByTimes.equals(charToTimes), true);
    }
avatar
Uxio
16 декабря 2015 в 10:30
-2

Если нет значения, превышающего размер карты, вы можете использовать массивы, это должен быть самый быстрый подход:

public List<String> getList(Map<String, Integer> myMap) {
    String[] copyArray = new String[myMap.size()];
    for (Entry<String, Integer> entry : myMap.entrySet()) {
        copyArray[entry.getValue()] = entry.getKey();
    }
    return Arrays.asList(copyArray);
}
Tom
15 декабря 2015 в 21:57
0

Этот copyArray[entry.getValue()] очень подвержен ошибкам, так как он завершится ошибкой, если карта содержит значения, превышающие размер карты.

avatar
Vitalii Fedorenko
29 ноября 2014 в 19:46
3

Вы можете попробовать мультикарты Guava:

TreeMap<Integer, Collection<String>> sortedMap = new TreeMap<>(
        Multimaps.invertFrom(Multimaps.forMap(originalMap), 
        ArrayListMultimap.<Integer, String>create()).asMap());

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

avatar
RoyalBigorno
9 сентября 2014 в 06:43
13

Используйте общий компаратор, например:

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));
    }
}
avatar
gdejohn
24 июня 2014 в 22:16
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();
}

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

assylias
3 марта 2014 в 17:26
0

Это возвращает список записей, а не карту, отсортированную по значению. Другая версия, возвращающая карту: coderhelper.com/a/22132422/829571

avatar
22 апреля 2014 в 09:09
3

Я объединил решения пользователя 157196 и Картера Пейджа:

class MapUtil {

    public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue( Map<K, V> map ){
        ValueComparator<K,V> bvc =  new ValueComparator<K,V>(map);
        TreeMap<K,V> sorted_map = new TreeMap<K,V>(bvc);
        sorted_map.putAll(map);
        return sorted_map;
    }

}

class ValueComparator<K, V extends Comparable<? super V>> implements Comparator<K> {

    Map<K, V> base;
    public ValueComparator(Map<K, V> base) {
        this.base = base;
    }

    public int compare(K a, K b) {
        int result = (base.get(a).compareTo(base.get(b)));
        if (result == 0) result=1;
        // returning 0 would merge keys
        return result;
    }
}
avatar
rohan kamat
8 марта 2014 в 05:51
-9

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

Map<String, String> map= new TreeMap<String, String>(unsortMap);

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

Duncan Jones
15 апреля 2014 в 07:29
8

Сортировка основана на ключах, а не на значениях.

avatar
10 февраля 2013 в 06:10
18

Создайте индивидуальный компаратор и используйте его при создании нового объекта 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}
gjgjgj
17 марта 2019 в 17:57
0

В случае, если значения равны, я изменил строку «return 1» для сравнения ключей: «return ((String) o1) .compareTo ((String) o2);»

avatar
18 ноября 2012 в 06:37
7

Серьезная проблема. Если вы используете первый ответ (Google берет вас сюда), измените компаратор, чтобы добавить предложение равенства, иначе вы не сможете получить значения из sorted_map по ключам:

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
    }
Masood_mj
23 января 2013 в 04:54
0

Теперь, когда вы добавляете две записи с равными значениями, они будут объединены, вы должны вернуть 0 только в том случае, если вы уверены, что объекты одинаковы (равны)

avatar
29 августа 2012 в 07:33
-3

Мы просто сортируем карту, как это

            Map<String, String> unsortedMap = new HashMap<String, String>();

    unsortedMap.put("E", "E Val");
    unsortedMap.put("F", "F Val");
    unsortedMap.put("H", "H Val");
    unsortedMap.put("B", "B Val");
    unsortedMap.put("C", "C Val");
    unsortedMap.put("A", "A Val");
    unsortedMap.put("G", "G Val");
    unsortedMap.put("D", "D Val");

    Map<String, String> sortedMap = new TreeMap<String, String>(unsortedMap);

    System.out.println("\nAfter sorting..");
    for (Map.Entry <String, String> mapEntry : sortedMap.entrySet()) {
        System.out.println(mapEntry.getKey() + " \t" + mapEntry.getValue());
NimChimpsky
18 сентября 2012 в 11:55
10

это просто создает древовидную карту, древовидные карты сортируются по ключу

avatar
30 мая 2012 в 00:47
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;
}
avatar
Anthony
25 февраля 2012 в 16:44
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;
}

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

Erel Segal-Halevi
2 октября 2011 в 15:58
6

ПРОБЛЕМА: если вы хотите использовать возвращенную карту позже, например, чтобы проверить, содержит ли она определенный элемент, вы всегда получите false из-за вашего настраиваемого компаратора! Возможное решение: замените последнюю строку на: return new LinkedHashMap <K, V> (sortedByValues);

abhi
14 мая 2013 в 08:14
0

Мне это кажется чистым решением, за исключением того факта, что @ErelSegalHalevi указал, что проверка того, существуют ли значения на карте, будет невозможна, поскольку вы указали компаратор. map.put («1», «Один»); map.put («2», «Два»); map.put («3», «Три»); map.put («4», «Четыре»); map.put («5», «Пять»); map.containsKey ("1") всегда будет возвращать false, если вы возвращаете новый объект в функции sortByValues ​​(), например return new TreeMap <K, V> (sortedByValues); решает проблему. Спасибо, Абхи

Kirby
22 апреля 2014 в 17:37
0

почти то же самое, что и ответ user157196 и Картера Пейджа. Картер Пейдж содержит исправление LinkedHashMap

Leo
22 июля 2014 в 08:20
0

4-я строка решения должна быть int compare = map.get (k1) .compareTo (map.get (k2)); если вам нужен возрастающий порядок

avatar
Darkless
29 декабря 2011 в 21:33
5

Это слишком сложно. Карты не должны были выполнять такую ​​работу, как сортировка их по значению. Самый простой способ - создать собственный класс, соответствующий вашим требованиям.

В примере ниже вы должны добавить TreeMap компаратор в месте, где *. Но с помощью java API он дает компаратору только ключи, а не значения. Все приведенные здесь примеры основаны на 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;
    }
}

и класс Comparator:

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()
        );
}
avatar
27 декабря 2011 в 17:43
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

Exec: 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

Надеюсь, это поможет некоторым людям

avatar
michel.iamit
23 ноября 2011 в 13:11
11

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

пример: несортированная карта

key/value: D/67.3
key/value: A/99.5
key/value: B/67.4
key/value: C/67.5
key/value: E/99.5

результаты

key/value: A/99.5
key/value: C/67.5
key/value: B/67.4
key/value: D/67.3

Таким образом, E !!

не учитывается.

У меня отлично сработало настроить компаратор, если он равен, возвращать не 0, а -1.

в примере:

класс ValueComparator реализует Comparator {

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

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 -1;
} else {
  return -1;
}

} }

теперь он возвращает:

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

key/value: D/67.3
key/value: A/99.5
key/value: B/67.4
key/value: C/67.5
key/value: E/99.5

результаты:

key/value: A/99.5
key/value: E/99.5
key/value: C/67.5
key/value: B/67.4
key/value: D/67.3

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

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

конечно, вы можете сделать это намного более общим, но мне это понадобилось только для одного случая (карта)

michel.iamit
23 ноября 2011 в 13:12
0

Вы были правы, сначала в коде, который я привел, была ошибка! Надеюсь, моя последняя редакция поможет вам.

avatar
26 сентября 2011 в 21:39
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.

Не самая быстрая сортировка, но выполняет свою работу без каких-либо дополнительных зависимостей.

avatar
1 сентября 2011 в 09:53
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;
        }
      }
    }
avatar
31 августа 2011 в 20:40
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;
}

Вы можете поместить list в LinkedHashMap , это лишнее ...

Sebastien Lorber
27 декабря 2011 в 17:48
0

это верно, но ваш компаратор не обрабатывает случай равных значений

avatar
23 августа 2011 в 18:52
2

Этот метод просто служит цели. («неудача» заключается в том, что значения должны реализовывать интерфейс java.util.Comparable )

  /**

 * Sort a map according to values.

 * @param <K> the key of the map.
 * @param <V> the value to sort according to.
 * @param mapToSort the map to sort.

 * @return a map sorted on the values.

 */ 
public static <K, V extends Comparable< ? super V>> Map<K, V>
sortMapByValues(final Map <K, V> mapToSort)
{
    List<Map.Entry<K, V>> entries =
        new ArrayList<Map.Entry<K, V>>(mapToSort.size());  

    entries.addAll(mapToSort.entrySet());

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

    Map<K, V> sortedMap = new LinkedHashMap<K, V>();      

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

    }      

    return sortedMap;

}

http://javawithswaranga.blogspot.com/2011/06/generic-method-to-sort-hashmap.html

avatar
8 июня 2011 в 15:23
5

Самый чистый способ - использовать коллекции для сортировки карты по значению:

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());
  }
}
avatar
Sunil Kumar Sahoo
30 мая 2011 в 19:56
-1
public class SortedMapExample {

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

        map.put("Cde", "C");
        map.put("Abc", "A");
        map.put("Cbc", "Z");
        map.put("Dbc", "D");
        map.put("Bcd", "B");
        map.put("sfd", "Bqw");
        map.put("DDD", "Bas");
        map.put("BGG", "Basd");

        System.out.println(sort(map, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                    return o1.compareTo(o2);
            }}));
    }

    @SuppressWarnings("unchecked")
    public static <K, V> Map<K,V> sort(Map<K, V> in, Comparator<? super V> compare) {
        Map<K, V> result = new LinkedHashMap<K, V>();
        V[] array = (V[])in.values().toArray();
        for(int i=0;i<array.length;i++)
        {

        }
        Arrays.sort(array, compare);
        for (V item : array) {
            K key= (K) getKey(in, item);
            result.put(key, item);
        }
        return result;
    }

    public static <K, V>  Object getKey(Map<K, V> in,V value)
    {
       Set<K> key= in.keySet();
       Iterator<K> keyIterator=key.iterator();
       while (keyIterator.hasNext()) {
           K valueObject = (K) keyIterator.next();
           if(in.get(valueObject).equals(value))
           {
                   return valueObject;
           }
       }
       return null;
   }

}

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

avatar
GHad
30 мая 2011 в 19:44
-2

Для сортировки по ключам я нашел лучшее решение с TreeMap (я также постараюсь получить готовое решение для сортировки на основе значений):

public static void main(String[] args) {
    Map<String, String> unsorted = new HashMap<String, String>();
    unsorted.put("Cde", "Cde_Value");
    unsorted.put("Abc", "Abc_Value");
    unsorted.put("Bcd", "Bcd_Value");

    Comparator<String> comparer = new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            return o1.compareTo(o2);
        }};

    Map<String, String> sorted = new TreeMap<String, String>(comparer);
    sorted.putAll(unsorted);
    System.out.println(sorted);
}

Вывод будет:

{Abc = Abc_Value, Bcd = Bcd_Value, Cde = Cde_Value}

sitakant
15 декабря 2015 в 11:19
0

поместите все значения в treeMap автоматически, они будут отсортированы по ключу с помощью R-B Tree.

Hussein El Motayam
6 июля 2017 в 11:41
0

Компаратор, переданный конструктору TreeMap, сравнивает КЛЮЧИ TreeMap вместо ЗНАЧЕНИЙ. В вашем примере это сработало, потому что сортировка по ключам такая же, как сортировка по значениям. Полный конструктор: public TreeMap (Comparator <? Super K> comparator) Где он принимает потомков K, который является ключом. Для получения дополнительной информации см .: docs.oracle.com/javase/6/docs/api/java/util/…

avatar
p3t0r
30 мая 2011 в 18:59
26

Библиотека общих коллекций содержит решение под названием TreeBidiMap. Или вы можете взглянуть на API коллекций Google. Он имеет TreeMultimap, который вы можете использовать.

И если вы не хотите использовать эти фреймворки ... они идут с исходным кодом.

yoliho
21 сентября 2008 в 04:23
0

Вам не обязательно использовать коллекцию общих ресурсов. Java поставляется с собственным java.util.TreeMap.

p3t0r
21 сентября 2008 в 06:18
2

да, но TreeMap гораздо менее гибок при сортировке по части значения элементов карты.

Doug
23 июля 2010 в 19:49
9

Проблема с BidiMap заключается в том, что он добавляет ограничение отношения 1: 1 между ключами и значениями, чтобы сделать отношение обратимым (т. Е. И ключи, и значения должны быть уникальными). Это означает, что вы не можете использовать это для хранения чего-то вроде объекта подсчета слов, поскольку у многих слов будет одинаковое количество слов.

avatar
9 февраля 2011 в 10:21
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;
}

Обратите внимание, что вопрос обработки нулевых значений пока не решен.

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

Anthony
25 февраля 2012 в 16:40
0

Это неверно, мой метод работает, если есть повторяющиеся значения. Я использовал его с картами, имеющими более 100 ключей со значением «1».

avatar
2 января 2011 в 05:53
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 );
  }
}

Настоящим пожертвовано в общественное достояние.

avatar
14 апреля 2009 в 13:44
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
}
Maxim Veksler
14 апреля 2009 в 13:48
0

Опять же, возможно, лучшим решением было бы просто использовать карту самосортировки, в случае использования org.apache.commons.collections.bidimap.TreeBidiMap

avatar
volley
1 октября 2008 в 21:02
32

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

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

Mike Miller
20 сентября 2011 в 21:00
0

Вторая версия может быть более краткой, если вы вернете List <Map.Entry <K, V >>. Это также упрощает итерацию и получение как ключей, так и значений без необходимости выполнять множество дополнительных операций с картой. Это все при условии, что вы согласны с тем, что этот код небезопасен для потоков. Если резервная карта или отсортированный список используются в многопоточной среде, все ставки отключены.

avatar
Lyudmil
24 сентября 2008 в 00:58
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, из которого можно получить ключи и значения.

Jeff Wu
30 декабря 2011 в 01:49
0

Этот метод намного проще и интуитивно понятнее, чем создание объекта Map <V, List <K>> с почти таким же эффектом. Значения на самом деле не должны быть ключами в объекте Map, в этой ситуации вы действительно ищете список, IMHO.

Sam Levin
15 сентября 2012 в 16:17
0

Это решение не работает с большим количеством значений, оно испортило мои подсчеты (значение, связанное с каждым ключом)

Lyudmil
29 сентября 2012 в 10:24
1

Это странно. Не могли бы вы уточнить? Каков был ваш результат и какой результат вы ожидали?

avatar
21 сентября 2008 в 15:03
2

Когда я с этим сталкиваюсь, я просто создаю список сбоку. Если вы объедините их в пользовательскую реализацию Map, это будет приятно ... Вы можете использовать что-то вроде следующего, выполняя сортировку только при необходимости. (Примечание: я на самом деле не тестировал это, но он компилируется ... может быть, где-то там есть небольшая глупая ошибка)

(Если вы хотите, чтобы он был отсортирован как по ключам, так и по значениям, расширьте класс TreeMap, не определяйте методы доступа и пусть мутаторы вызывают super.xxxxx вместо map_.xxxx)

package com.javadude.sample;

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

public class SortedValueHashMap<K, V> implements Map<K, V> {
    private Map<K, V> map_ = new HashMap<K, V>();
    private List<V> valueList_ = new ArrayList<V>();
    private boolean needsSort_ = false;
    private Comparator<V> comparator_;

    public SortedValueHashMap() {
    }
    public SortedValueHashMap(List<V> valueList) {
        valueList_ = valueList;
    }

    public List<V> sortedValues() {
        if (needsSort_) {
            needsSort_ = false;
            Collections.sort(valueList_, comparator_);
        }
        return valueList_;
    }

    // mutators
    public void clear() {
        map_.clear();
        valueList_.clear();
        needsSort_ = false;
    }

    public V put(K key, V value) {
        valueList_.add(value);
        needsSort_ = true;
        return map_.put(key, value);
    }

    public void putAll(Map<? extends K, ? extends V> m) {
        map_.putAll(m);
        valueList_.addAll(m.values());
        needsSort_ = true;
    }

    public V remove(Object key) {
        V value = map_.remove(key);
        valueList_.remove(value);
        return value;
    }

    // accessors
    public boolean containsKey(Object key)           { return map_.containsKey(key); }
    public boolean containsValue(Object value)       { return map_.containsValue(value); }
    public Set<java.util.Map.Entry<K, V>> entrySet() { return map_.entrySet(); }
    public boolean equals(Object o)                  { return map_.equals(o); }
    public V get(Object key)                         { return map_.get(key); }
    public int hashCode()                            { return map_.hashCode(); }
    public boolean isEmpty()                         { return map_.isEmpty(); }
    public Set<K> keySet()                           { return map_.keySet(); }
    public int size()                                { return map_.size(); }
    public Collection<V> values()                    { return map_.values(); }
}
avatar
21 сентября 2008 в 01:37
-2

Хорошо, эта версия работает с двумя новыми объектами Map и двумя итерациями и сортировками по значениям. Надеюсь, он работает хорошо, хотя записи карты должны быть зациклены дважды:

public static void main(String[] args) {
    Map<String, String> unsorted = new HashMap<String, String>();
    unsorted.put("Cde", "Cde_Value");
    unsorted.put("Abc", "Abc_Value");
    unsorted.put("Bcd", "Bcd_Value");

    Comparator<String> comparer = new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            return o1.compareTo(o2);
        }};

    System.out.println(sortByValue(unsorted, comparer));

}

public static <K, V> Map<K,V> sortByValue(Map<K, V> in, Comparator<? super V> compare) {
    Map<V, K> swapped = new TreeMap<V, K>(compare);
    for(Entry<K,V> entry: in.entrySet()) {
        if (entry.getValue() != null) {
            swapped.put(entry.getValue(), entry.getKey());
        }
    }
    LinkedHashMap<K, V> result = new LinkedHashMap<K, V>();
    for(Entry<V,K> entry: swapped.entrySet()) {
        if (entry.getValue() != null) {
            result.put(entry.getValue(), entry.getKey());
        }
    }
    return result;
}

Решение использует TreeMap с компаратором и сортирует все пустые ключи и значения. Во-первых, функция упорядочения из TreeMap используется для сортировки значений, затем отсортированная карта используется для создания результата в виде LinkedHashMap, который сохраняет тот же порядок значений.

Greetz, GHad

avatar
20 сентября 2008 в 22:47
-11

Используйте java.util.TreeMap .

«Карта сортируется в соответствии с естественным порядком ее ключей или компаратором, предоставленным во время создания карты, в зависимости от того, какой конструктор используется».

ScArcher2
20 сентября 2008 в 23:45
0

Я бы использовал интерфейс SortedMap вместе с TreeMap. Тогда вы не привязаны к реализации TreeMap.

benzado
20 сентября 2008 в 23:59
8

В документации говорится, что TreeMap сортирует свои ключи на основе их естественного порядка или с помощью предоставленного вами компаратора. Но сортировка основана на ключах, а не на значениях. Компаратор, который сравнивает значения, даст древовидную структуру, такую ​​же, как при использовании значения в качестве ключа в первую очередь.

avatar
Don
20 сентября 2008 в 21:16
-9

Если ваши значения Map реализуют Comparable (например, String), это должно работать

Map<Object, String> map = new HashMap<Object, String>();
// Populate the Map
List<String> mapValues = new ArrayList<String>(map.values());
Collections.sort(mapValues);

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

Collections.sort(mapValues, comparable);
Aaron
21 сентября 2008 в 19:27
1

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

gregory
20 октября 2008 в 13:22
20

Причина в том, что это не решает проблему. Значения сортируются нормально, но ключи выбрасываются. Был задан вопрос о способе сортировки карты, а это означает, что ключи и значения все еще должны быть связаны.

whiskeysierra
10 января 2010 в 14:05
3

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

avatar
20 сентября 2008 в 21:07
5

В зависимости от контекста, используя java.util.LinkedHashMap<T>, который запоминает порядок, в котором элементы размещаются на карте. В противном случае, если вам нужно отсортировать значения в соответствии с их естественным порядком, я бы рекомендовал вести отдельный список, который можно отсортировать через Collections.sort().

NobleUplift
18 апреля 2016 в 20:07
0

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