Ordina una mappa <chiave, valore> per valori


1635

Sono relativamente nuovo a Java e spesso trovo che devo ordinare un Map<Key, Value>valore.

Dato che i valori non sono univoci, mi ritrovo a convertire keySetin un arraye a ordinare quell'array tramite l' ordinamento dell'array con un comparatore personalizzato che ordina il valore associato alla chiave.

C'è un modo più semplice?


24
Una mappa non è pensata per essere ordinata, ma è accessibile rapidamente. I valori uguali dell'oggetto infrangono il vincolo della mappa. Usa il set di voci, come List<Map.Entry<...>> list =new LinkedList(map.entrySet())e Collections.sort ....così.
Hannes,

1
Un caso in cui ciò potrebbe verificarsi quando proviamo a fare uso di un contatore in Java (Mappa <Oggetto, Numero intero>). L'ordinamento per numero di occorrenze sarebbe quindi un'operazione comune. Un linguaggio come Python ha una struttura di dati Counter integrata. Per un modo alternativo di implementazione in Java, ecco un esempio
demongolem il

7
Esistono molti casi d'uso per le mappe ordinate, ecco perché hai TreeMap e ConcurrentSkipListMap in jdk.
alobodzk,


1
TreeMap e ConcurrentSkipListMap ordinano per chiave. La domanda riguarda l'ordinamento per valore.
Peter,

Risposte:


901

Ecco una versione generica:

public class MapUtil {
    public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) {
        List<Entry<K, V>> list = new ArrayList<>(map.entrySet());
        list.sort(Entry.comparingByValue());

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

        return result;
    }
}

10
Sono contento che questo aiuti. John, LinkedHashMap è importante per la soluzione in quanto fornisce un ordine di iterazione prevedibile.
Carter Pagina

3
@ buzz3791 True. Questo sarà il caso di qualsiasi algoritmo di ordinamento. Modificare il valore dei nodi in una struttura durante un ordinamento crea risultati imprevedibili (e quasi sempre negativi).
Carter Page

3
@Sheagorath L'ho provato su Android e funziona anche. Non è un problema specifico della piattaforma, considerando che stai utilizzando la versione Java 6. Hai implementato Comparable correttamente nel tuo oggetto valore?
Saiyancoder

6
La versione di Java 8 non dovrebbe essere utilizzata al forEachOrderedposto di forEach, poiché la documentazione degli forEachstati: "Il comportamento di questa operazione è esplicitamente non deterministico"?
rapina il

1
totalmente strappato questo, ma accreditato @CarterPage nei commenti (sarà comunque in un progetto open source). grazie mille.
Nathan Beach,

420

Nota importante:

Questo codice può rompersi in più modi. Se si intende utilizzare il codice fornito, assicurarsi di leggere anche i commenti per essere consapevoli delle implicazioni. Ad esempio, i valori non possono più essere recuperati dalla loro chiave. ( getritorna sempre null.)


Sembra molto più facile di tutto quanto sopra. Utilizzare una TreeMap come segue:

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

Produzione:

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

18
Non più ( stackoverflow.com/questions/109383/… ). Inoltre, perché c'era un cast per raddoppiare? Non dovrebbe essere return ((Comparable)base.get(a).compareTo(((Comparable)base.get(b)))?
Stephen,

12
@Stephen: No. In questo caso vengono eliminate tutte le chiavi uguali per valore (differenza tra uguali e confronto per riferimento). Inoltre: anche questo codice ha problemi con la seguente sequenza map.put("A","1d");map.put("B","1d");map.put("C",67d);map.put("D",99.5d);
steffen

43
Il comparatore utilizzato per la mappa treem è incoerente con gli uguali (vedere il javadox sortMap). Ciò significa che il recupero di elementi dalla mappa ad albero non funzionerà. sort_map.get ("A") restituirà null. Ciò significa che questo uso della mappa treemap è interrotto.
mR_fr0g

87
Nel caso non fosse chiaro alle persone: questa soluzione probabilmente non farà ciò che vuoi se hai più chiavi mappate sullo stesso valore - solo una di quelle chiavi apparirà nel risultato ordinato.
Maxy-B,

63
Louis Wasserman (sì, uno dei ragazzi di Google Guava), in realtà non piace molto questa risposta: "Si rompe in molti modi davvero confusi se la guardi anche divertente. Se la mappa di supporto cambia, si romperà. Se più chiavi lo stesso valore si interromperà. Se chiami ottieni un tasto che non si trova nella mappa di supporto, si interromperà. Se fai qualcosa che provocherebbe una ricerca su un tasto che non è in la mappa - una chiamata Map.equals, contieneKey, qualsiasi cosa - si romperà con tracce di stack davvero strane. " plus.google.com/102216152814616302326/posts/bEQLDK712MJ
haylem

339

Java 8 offre una nuova risposta: converti le voci in un flusso e usa i combinatori di confronto di Map.Entry:

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

Questo ti permetterà di consumare le voci ordinate in ordine crescente di valore. Se si desidera un valore decrescente, è sufficiente invertire il comparatore:

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

Se i valori non sono comparabili, puoi passare un comparatore esplicito:

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

È quindi possibile procedere con l'uso di altre operazioni di flusso per utilizzare i dati. Ad esempio, se vuoi i primi 10 in una nuova mappa:

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

Oppure stampa su System.out:

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

Bello, ma per quanto riguarda l'uso di parallelStream()in questo caso?
Benj,

11
Funzionerà in parallelo, tuttavia, potresti scoprire che il costo della fusione delle mappe per combinare i risultati parziali è troppo costoso e la versione parallela potrebbe non funzionare come speravi. Ma funziona e produce la risposta corretta.
Brian Goetz,

Grazie per il tuo utile consiglio. Era esattamente quello che mi chiedevo, anche se dipende dal tipo di chiave che usi e da così tanti parametri ... L'importante è "funziona e produce la risposta corretta".
Benj,

2
non devi usare compareByValue nell'esempio top10?
Leone,

1
@Benj funzionerà in termini di estrazione della top-10, ma la mappa risultante non sarà più ordinata.
OrangeDog,

211

Tre risposte a 1 riga ...

Vorrei utilizzare Google Collections Guava per fare questo - se i tuoi valori sono Comparableallora puoi usare

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

Che creerà una funzione (oggetto) per la mappa [che accetta una qualsiasi delle chiavi come input, restituendo il rispettivo valore], e quindi applica loro l'ordine naturale (comparabile) [i valori].

Se non sono comparabili, allora dovrai fare qualcosa del genere

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

Questi possono essere applicati a una TreeMap (come Orderingestesa Comparator) o a LinkedHashMap dopo un certo ordinamento

NB : Se hai intenzione di utilizzare una TreeMap, ricorda che se un confronto == 0, l'elemento è già nell'elenco (cosa che succederà se hai più valori che confrontano lo stesso). Per alleviare questo, puoi aggiungere la tua chiave al comparatore in questo modo (presumendo che le tue chiavi e i tuoi valori siano Comparable):

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

= Applica l'ordinamento naturale al valore mappato dalla chiave e mescola quello con l'ordinamento naturale della chiave

Nota che questo non funzionerà ancora se le tue chiavi sono pari a 0, ma questo dovrebbe essere sufficiente per la maggior parte degli comparableelementi (come hashCode, equalse compareTospesso sono sincronizzati ...)

Vedere Ordering.onResultOf () e Functions.forMap () .

Implementazione

Quindi ora che abbiamo un comparatore che fa quello che vogliamo, dobbiamo ottenere un risultato da esso.

map = ImmutableSortedMap.copyOf(myOriginalMap, valueComparator);

Ora molto probabilmente funzionerà, ma:

  1. deve essere eseguito con una mappa completa completa
  2. Non provare i comparatori sopra su a TreeMap; non ha senso cercare di confrontare una chiave inserita quando non ha un valore fino a dopo l'inserimento, ovvero si romperà molto velocemente

Il punto 1 è un po 'una rottura per me; google collections è incredibilmente pigra (il che è buono: puoi fare praticamente ogni operazione in un istante; il vero lavoro viene fatto quando inizi a utilizzare il risultato) e questo richiede la copia di un'intera mappa!

Risposta "completa" / Mappa ordinata dal vivo per valori

Non preoccuparti però; se eri abbastanza ossessionato dall'avere una mappa "live" ordinata in questo modo, potresti risolvere non uno ma entrambi (!) dei problemi sopra con qualcosa di folle come il seguente:

Nota: questo è cambiato in modo significativo a giugno 2012 - il codice precedente non potrebbe mai funzionare: è necessaria una HashMap interna per cercare i valori senza creare un ciclo infinito tra TreeMap.get()-> compare()e 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"));
    }
 }

Quando lo inseriamo, ci assicuriamo che la mappa hash abbia il valore per il comparatore, quindi lo inseriamo nel TreeSet per l'ordinamento. Ma prima controlliamo la mappa hash per vedere che la chiave non è in realtà un duplicato. Inoltre, il comparatore che creiamo includerà anche la chiave in modo che i valori duplicati non cancellino le chiavi non duplicate (a causa del confronto ==). Questi 2 elementi sono fondamentali per garantire il mantenimento del contratto della mappa; se pensi di non volerlo, allora sei quasi sul punto di invertire completamente la mappa (a Map<V,K>).

Il costruttore dovrebbe essere chiamato come

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

Ciao @Stephen, puoi fare un esempio su come usare l'ordinamento? Guardo il codice sorgente di Ordering e non riesco assolutamente a capire cosa restituisce .natural (). OnResultOf (...)! Il codice sorgente è "public <F> Ordering <F> onResultOf", non so nemmeno come si compila! Soprattutto, come utilizzare "<F> Ordine <F>" per ordinare una mappa? È un comparatore o qualcosa del genere? Grazie.
smallufo,

Orderingè semplicemente un ricco Comparator. Ho provato a commentare ogni esempio (il corsivo sotto ognuno). "naturale" indica che gli oggetti sono Comparable; è come ComparableComparator di apache common. onResultOfapplica una funzione all'elemento confrontato. Quindi, se avessi una funzione che aggiungesse 1 a un numero intero, finiresti natural().onResultOf(add1Function).compare(1,2)per farlo2.compareTo(3)
Stephen,

ImmutableSortedMap.copyOf genera IllegalArgumentException se sono presenti valori duplicati nella mappa originale.
lbalazscs,

@Ibalazscs Sì. Dovresti essere in grado di utilizzare ImmutableSetMultiMapo ImmutableListMultiMapcontenere la raccolta di variabili duplicate.
Stephen,

1
Grazie per questo, ho usato la tua soluzione in un progetto. Tuttavia, penso che ci sia un problema: per comportarsi come una mappa, deve restituire il valore precedentemente associato alla chiave, se esiste, ma in questo modo non lo farà mai. La soluzione che ho usato è di restituire il valore rimosso se esiste.
alex,

185

Da http://www.programmersheaven.com/download/49349/download.aspx

private static <K, V> Map<K, V> sortByValue(Map<K, V> map) {
    List<Entry<K, V>> list = new LinkedList<>(map.entrySet());
    Collections.sort(list, new Comparator<Object>() {
        @SuppressWarnings("unchecked")
        public int compare(Object o1, Object o2) {
            return ((Comparable<V>) ((Map.Entry<K, V>) (o1)).getValue()).compareTo(((Map.Entry<K, V>) (o2)).getValue());
        }
    });

    Map<K, V> result = new LinkedHashMap<>();
    for (Iterator<Entry<K, V>> it = list.iterator(); it.hasNext();) {
        Map.Entry<K, V> entry = (Map.Entry<K, V>) it.next();
        result.put(entry.getKey(), entry.getValue());
    }

    return result;
}

16
L'elenco da ordinare è "nuovo LinkedList" ?? Gee. Fortunatamente Collections.sort () scarica prima l'elenco su un array, per evitare esattamente questo tipo di errore (ma comunque, scaricare un ArrayList su un array dovrebbe essere più veloce che fare lo stesso per un LinkedList).
Dimitris Andreou,

impossibile convertire da Iterator a TernaryTree.Iterator
lisak il

4
@ gg.kaspersky Non sto dicendo "è male ordinare un LinkedList", ma quel LinkedList stesso è una cattiva scelta qui, indipendentemente dall'ordinamento. Molto meglio usare un ArrayList e, per punti extra, dimensionarlo esattamente su map.size (). Vedi anche code.google.com/p/memory-measurer/wiki/… costo medio per elemento in ArrayList: 5 byte costo medio per elemento in LinkedList: 24 byte. Per una ArrayList di dimensioni esatte, il costo medio sarebbe di 4 byte. Cioè, LinkedList richiede SIX volte la quantità di memoria necessaria per ArrayList. È solo gonfio
Dimitris Andreou,

2
l'utilizzo dei valori sopra è stato ordinato in ordine crescente. Come ordinare in ordine decrescente?
ram

1
Sostituisci o1 e o2 per ordinare in ordine decrescente.
Soheil,

68

Con Java 8, è possibile utilizzare l' API dei flussi per farlo in modo significativamente meno dettagliato:

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

Come ordinare in ordine inverso?
Vlad Holubiev,

6
trovato una soluzione -Collections.reverseOrder(comparing(Entry::getValue))
Vlad Holubiev il

1
Penso di vedere un refuso lì - il "toMap" non dovrebbe essere chiamato "Collectors.toMap ()"?
Jake Stokes,

1
@JakeStokes O usa un'importazione statica :-)
assylias,

6
Un modo migliore per ordinare in base al valore della voce in ordine inverso è:Entry.comparingByValue(Comparator.reverseOrder())
Gediminas Rimsa,

31

L'ordinamento delle chiavi richiede al comparatore di cercare ciascun valore per ciascun confronto. Una soluzione più scalabile userebbe direttamente entrySet, da allora il valore sarebbe immediatamente disponibile per ogni confronto (anche se non ho eseguito il backup con i numeri).

Ecco una versione generica di una cosa del genere:

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

Esistono modi per ridurre la rotazione della memoria per la soluzione sopra. La prima ArrayList creata potrebbe ad esempio essere riutilizzata come valore di ritorno; ciò richiederebbe la soppressione di alcuni avvertimenti generici, ma potrebbe valerne la pena per il codice riutilizzabile della libreria. Inoltre, non è necessario riassegnare il comparatore ad ogni chiamata.

Ecco una versione più efficiente anche se meno accattivante:

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

Infine, se devi accedere continuamente alle informazioni ordinate (piuttosto che ordinarle di tanto in tanto), puoi utilizzare una multi mappa aggiuntiva. Fammi sapere se hai bisogno di più dettagli...


La seconda versione può essere più concisa se si restituisce List <Map.Entry <K, V >> Ciò semplifica anche l'iterazione e l'ottenimento di chiavi e valori senza dover ottenere molti extra sulla mappa. Tutto questo presuppone che tu stia bene con questo codice non sicuro. Se la mappa di supporto o l'elenco ordinato sono condivisi in un ambiente multithread, tutte le scommesse sono disattivate.
Mike Miller,

26

La libreria commons-collections contiene una soluzione chiamata TreeBidiMap . In alternativa, puoi dare un'occhiata all'API delle raccolte di Google. Ha TreeMultimap che puoi usare.

E se non vuoi usare questi framework ... arrivano con il codice sorgente.


Non devi usare la raccolta comune. Java viene fornito con il proprio java.util.TreeMap.
yoliho,

2
sì, ma TreeMap è molto meno flessibile quando si ordina sulla parte di valore delle mappe.
p3t0r,

9
Il problema con BidiMap è che aggiunge un vincolo di relazione 1: 1 tra chiavi e valori per rendere la relazione invertibile (cioè sia le chiavi che i valori devono essere univoci). Ciò significa che non puoi usarlo per memorizzare qualcosa come un oggetto conteggio parole poiché molte parole avranno lo stesso conteggio.
Doug

26

Ho esaminato le risposte fornite, ma molte sono più complicate del necessario o rimuovono gli elementi della mappa quando più chiavi hanno lo stesso valore.

Ecco una soluzione che ritengo si adatti meglio:

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

Nota che la mappa è ordinata dal valore più alto al più basso.


6
PROBLEMA: se si desidera utilizzare la mappa restituita in un secondo momento, ad esempio per verificare se contiene un determinato elemento, si otterrà sempre falso, a causa del proprio comparatore personalizzato! Una possibile soluzione: sostituire l'ultima riga con: return new LinkedHashMap <K, V> (SortByValues);
Erel Segal-Halevi,

Questa mi sembra una soluzione chiara, tranne per il fatto che @ErelSegalHalevi ha sottolineato che controllare che i valori esistano nella Mappa non sarà possibile come hai specificato il comparatore. map.put ("1", "One"); map.put ("2", "Two"); map.put ("3", "Three"); map.put ("4", "Four"); map.put ("5", "Five"); map.containsKey ("1") restituirà sempre false, se si restituisce un nuovo oggetto nella funzione sortByValues ​​() come return new TreeMap <K, V> (sortByValues); risolve il problema. Grazie Abhi
abhi

praticamente uguale alla risposta di user157196 e Carter Page. Carter Page contiene la correzione di LinkedHashMap
Kirby,

La quarta riga della soluzione dovrebbe essere int compare = map.get (k1) .compareTo (map.get (k2)); se hai bisogno di un ordine crescente
www.Decompiler.com

19

Per fare ciò con le nuove funzionalità di 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());
}

Le voci sono ordinate in base ai loro valori utilizzando il comparatore indicato. In alternativa, se i valori sono reciprocamente comparabili, non è necessario alcun comparatore esplicito:

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

L'elenco restituito è un'istantanea della mappa data nel momento in cui viene chiamato questo metodo, quindi nessuno dei due rifletterà le successive modifiche all'altra. Per una visualizzazione iterabile live della mappa:

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

L'iterabile restituito crea una nuova istantanea della mappa data ogni volta che viene ripetuta, quindi, salvo modifiche simultanee, rifletterà sempre lo stato corrente della mappa.


Ciò restituisce un Elenco di voci anziché una mappa ordinata per valore. Altra versione che restituisce una mappa: stackoverflow.com/a/22132422/829571
assylias

17

Crea un comparatore personalizzato e utilizzalo durante la creazione di un nuovo oggetto 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));

    }
}

Utilizzare il codice seguente nella funzione principale

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

Produzione:

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

Nel caso in cui i valori siano uguali, ho modificato la riga "return 1" per confrontare le chiavi: "return ((String) o1) .compareTo ((String) o2);"
gjgjgj

14

Mentre sono d'accordo sul fatto che la costante necessità di ordinare una mappa sia probabilmente un odore, penso che il seguente codice sia il modo più semplice per farlo senza utilizzare una diversa struttura di dati.

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

}

Ed ecco un test unitario imbarazzantemente incompleto:

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

}

Il risultato è un elenco ordinato di oggetti Map.Entry, da cui è possibile ottenere chiavi e valori.


Questo metodo è molto più semplice e intuitivo della creazione di un oggetto Mappa <V, Elenco <K>> con praticamente lo stesso effetto. Non si suppone che i valori siano chiavi in ​​un oggetto Mappa, quello che stai davvero cercando è un elenco in questa situazione, IMHO.
Jeff Wu,

Questa soluzione non funziona con molti valori, ha rovinato i miei conteggi (il valore associato a ciascuna chiave)
Sam Levin,

1
È strano. Potresti elaborare? Qual è stato il tuo output e qual è stato l'output che ti aspettavi?
Lyudmil,

12

Utilizzare un comparatore generico come:

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

    private Map<K,V> map;

    private MapValueComparator() {
        super();
    }

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

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

11

La risposta votata per il massimo non funziona quando hai 2 elementi uguali. la TreeMap lascia fuori valori uguali.

l'esempio: mappa non ordinata

chiave / valore: D / 67.3
chiave / valore: A / 99.5
chiave / valore: B / 67.4
chiave / valore: C / 67.5
chiave / valore: E / 99.5

risultati

chiave / valore: A / 99.5
chiave / valore: C / 67.5
chiave / valore: B / 67.4
chiave / valore: D / 67.3

Quindi lascia fuori E !!

Per me ha funzionato bene per regolare il comparatore, se uguale non restituisce 0 ma -1.

nell'esempio:

class ValueComparator implementa Comparator {

Base della mappa; public ValueComparator (Map base) {this.base = base; }

public int compare (Oggetto a, Oggetto 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;
}

}}

ora ritorna:

mappa non ordinata:

chiave / valore: D / 67.3
chiave / valore: A / 99.5
chiave / valore: B / 67.4
chiave / valore: C / 67.5
chiave / valore: E / 99.5

i risultati:

chiave / valore: A / 99.5
chiave / valore: E / 99.5
chiave / valore: C / 67.5
chiave / valore: B / 67.4
chiave / valore: D / 67.3

come risposta ad Aliens (22 novembre 2011): sto usando questa soluzione per una mappa di numeri e ID di numeri interi, ma l'idea è la stessa, quindi potrebbe essere che il codice sopra non sia corretto (lo scriverò in un test e ti dà il codice corretto), questo è il codice per un ordinamento Mappa, basato sulla soluzione sopra:

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


}

e questa è la classe di test (l'ho appena testata e funziona per 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);
    }
}

ecco il codice per il comparatore di una mappa:

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

e questo è il testcase per questo:

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

di coraggio puoi renderlo molto più generico, ma ne avevo solo bisogno per 1 caso (la Mappa)


avevi ragione, c'è stato qualche errore nel codice che ho dato all'inizio! Spero che la mia recente modifica ti possa aiutare.
michel.iamit,

9

Invece di usare Collections.sortcome alcuni suggerirei di usare Arrays.sort. In realtà ciò che Collections.sortfa è qualcosa del genere:

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

Richiama toArrayl'elenco e quindi lo utilizza Arrays.sort. In questo modo tutte le voci della mappa verranno copiate tre volte: una volta dalla mappa all'elenco temporaneo (sia esso un LinkedList o ArrayList), quindi all'array temporaneo e infine alla nuova mappa.

La mia soluzione consente di eseguire questo passaggio in quanto non crea LinkedList non necessario. Ecco il codice, generico e ottimizzato per le prestazioni:

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

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

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

    return result;
}

8

Questa è una variante della risposta di Anthony, che non funziona se ci sono valori duplicati:

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

Si noti che è piuttosto in alto come gestire i null.

Un importante vantaggio di questo approccio è che in realtà restituisce una mappa, a differenza di alcune delle altre soluzioni offerte qui.


Non è corretto, il mio metodo funziona se ci sono valori duplicati. L'ho usato con mappe con più di 100 chiavi con "1" come valore.
Anthony,

8

Il miglior approccio

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

Produzione

java ==== 20

MAC ==== 26

C++ ==== 45

Unix ==== 67

Why this kolavari ==== 93

7

Problema principale. Se usi la prima risposta (Google ti porta qui), modifica il comparatore per aggiungere una clausola uguale, altrimenti non puoi ottenere i valori dalla sort_map con le chiavi:

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
    }

Ora quando aggiungi due voci con valori uguali verranno unite, dovresti restituire 0 solo se sei sicuro che gli oggetti sono uguali (uguali)
Masood_mj

7

Esistono già molte risposte a questa domanda, ma nessuna mi ha fornito ciò che stavo cercando, un'implementazione della mappa che restituisce chiavi e voci ordinate in base al valore associato e mantiene questa proprietà man mano che chiavi e valori vengono modificati nella mappa. Altre due domande chiedono questo in particolare.

Ho elaborato un esempio amichevole generico che risolve questo caso d'uso. Questa implementazione non rispetta tutti i contratti dell'interfaccia di Map, come il riflesso delle variazioni di valore e le rimozioni nei set restituiti da keySet () e entrySet () nell'oggetto originale. Ho pensato che una soluzione del genere sarebbe troppo grande per essere inclusa in una risposta Stack Overflow. Se riesco a creare un'implementazione più completa, forse la posterò su Github e quindi al suo link in una versione aggiornata di questa risposta.

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

Se Comparable e Comparator non sono consentiti, come si fa?
Ved Prakash,

Non sono sicuro se capisco il tuo caso d'uso, forse puoi elaborarlo. Se l'oggetto che si desidera utilizzare come valore non è confrontabile, è necessario convertirlo in un oggetto che lo sia.
David Bleckmann,

6

Ingresso ritardato.

Con l'avvento di Java-8, possiamo usare gli stream per la manipolazione dei dati in un modo molto semplice / succinto. È possibile utilizzare gli stream per ordinare le voci della mappa in base al valore e creare una LinkedHashMap che preserva l' iterazione dell'ordine di inserzione .

Per esempio:

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

Per l'ordinazione inversa, sostituire:

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

con

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

Grazie per questa versione commentata. Una domanda: Qual è la differenza di usare Entry.comparingByValue()(come assylias risposta sopra stackoverflow.com/a/22132422/1480587 ), o comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey)che hai usato? Capisco che confronti anche le chiavi se i valori sono identici, giusto? Ho notato che l'ordinamento mantiene l'ordine degli elementi con lo stesso valore - quindi l'ordinamento per chiavi è necessario se le chiavi sono già state ordinate prima?
Peter T.

6

Mappa fornita

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

Ordina la mappa in base al valore in ordine crescente

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

Ordina la mappa in base al valore nell'ordine desiderato

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

Produzione:

{software = 50, tecnologia = 70, USA = 100, posti di lavoro = 200, opportunità = 200}

{posti di lavoro = 200, opportunità = 200, USA = 100, tecnologia = 70, software = 50}


Immagino che riduci veramente la mappa "Ridotta" ... buona soluzione.
ha9u63ar,

grazie @ ha9u63ar
Saini il

Funziona ma non capisco come l'ordine degli elementi entra in gioco in una HashMap?
Ali Tou

5

A seconda del contesto, usando java.util.LinkedHashMap<T>quale ricordo l'ordine in cui gli oggetti vengono posizionati nella mappa. Altrimenti, se è necessario ordinare i valori in base al loro ordinamento naturale, consiglierei di mantenere un Elenco separato che può essere ordinato tramite Collections.sort().


Non vedo perché questo fosse -1, finora LinkedHashMap è probabilmente la soluzione migliore per me, sto solo cercando di capire quanto costa buttare via e creare un nuovo LinkedHashMap.
NobleUplift,

5

Poiché TreeMap <> non funziona per valori che possono essere uguali, ho usato questo:

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

Potresti voler mettere un elenco in una LinkedHashMap , ma se hai intenzione di iterare subito su di esso, è superfluo ...


esatto, ma il tuo comparatore non gestisce il caso di valori uguali
Sebastien Lorber,

5

Questo è troppo complicato. Le mappe non dovevano svolgere un lavoro del genere ordinandole per Valore. Il modo più semplice è creare la tua classe in modo che si adatti alle tue esigenze.

Nell'esempio in basso dovresti aggiungere TreeMap un comparatore nel posto dove * è. Ma tramite l'API Java fornisce al comparatore solo chiavi, non valori. Tutti gli esempi qui riportati si basano su 2 mappe. Un hash e un nuovo albero. Che è strano.

L'esempio:

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

Quindi cambia la mappa in un set in questo modo:

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

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

e la classe 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;
        }
    }
}

In questo modo è possibile aggiungere facilmente più dipendenze.

E come ultimo punto aggiungerò un semplice iteratore:

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

4

Basato sul codice @devinmoore, metodi di ordinamento di mappe che utilizzano generici e supportano sia l'ordinamento crescente che decrescente.

/**
 * 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
}

Quindi, forse, una soluzione migliore sarebbe quella di utilizzare solo una mappa di auto-ordinamento, nel caso utilizzare org.apache.commons.collections.bidimap.TreeBidiMap
Maxim Veksler

4

Ecco una soluzione OO (cioè non utilizza staticmetodi):

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

Con la presente donato al pubblico dominio.


4

Afaik il modo più pulito è utilizzare le raccolte per ordinare la mappa in base al valore:

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

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

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

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

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

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

4

Alcune semplici modifiche per avere una mappa ordinata con coppie che hanno valori duplicati. Nel metodo di confronto (classe ValueComparator) quando i valori sono uguali non restituiscono 0 ma restituiscono il risultato del confronto delle 2 chiavi. Le chiavi sono distinte in una mappa in modo da riuscire a mantenere valori duplicati (che sono ordinati in base alle chiavi). Quindi l'esempio sopra potrebbe essere modificato in questo modo:

    public int compare(Object a, Object b) {

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

4

Sicuramente la soluzione di Stephen è davvero eccezionale, ma per coloro che non possono usare Guava:

Ecco la mia soluzione per ordinare per valore una mappa. Questa soluzione gestisce il caso in cui vi sia il doppio dello stesso valore, ecc ...

// 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

Il exec: http://www.ideone.com/dq3Lu

Il risultato:

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

Spero che possa aiutare alcune persone


3

Se hai chiavi duplicate e solo un piccolo set di dati (<1000) e il tuo codice non è critico per le prestazioni, puoi semplicemente fare quanto segue:

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 è l'input per il codice.

La variabile sortOutputMap conterrà i dati in ordine decrescente quando ripetuti. Per cambiare ordine basta cambiare> in a <nell'istruzione if.

Non è l'ordinamento più veloce ma fa il lavoro senza dipendenze aggiuntive.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.