Ordina una mappa per valori


Sono relativamente nuovo in Java e spesso trovo che ho bisogno di ordinare un Map<Key, Value> sui valori.

Poiché i valori non sono univoci, mi ritrovo a convertire keySet in un array e a ordinare quell'array attraverso array sort con un comparatore personalizzato che ordina il valore associato alla chiave.

C'è un modo più semplice?

Author: Lii, 2008-09-21

30 answers

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;
    }
}
 794
Author: Carter Page, 2018-04-03 00:00:27

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. (get restituisce sempre null.)


Sembra molto più facile di tutto quanto sopra. Utilizzare una mappa ad albero 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
    }
}

Uscita:

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}
 405
Author: user157196, 2016-04-18 23:41:20

Java 8 offre una nuova risposta: convertire le voci in un flusso e utilizzare i combinatori di comparatori da Map.Voce:

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 discendente, è 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 all'utilizzo di altre operazioni di flusso per utilizzare i dati. Ad esempio, se si desidera che il top 10 in un 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));

O stampare su System.out:

map.entrySet().stream()
   .sorted(Map.Entry.comparingByValue())
   .forEach(System.out::println);
 230
Author: Brian Goetz, 2017-11-25 22:16:36

Tre risposte a 1 riga...

Vorrei usare Google Collections Guaiava per fare ciò, se i tuoi valori sono Comparable, puoi usare

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

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

Se non sono comparabili, allora dovrai fare qualcosa sulla falsariga di

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

Questi possono essere applicati a una mappa ad albero (come Ordering estende Comparator) o una LinkedHashMap dopo un po ' di ordinamento

NB: Se hai intenzione di usare una mappa ad albero, ricorda che se un confronto == 0, allora l'elemento è già nella lista (cosa che accadrà se hai più valori che confrontano lo stesso). Per alleviare questo, potresti 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 ordinamento naturale al valore mappato dalla chiave e composto con l'ordinamento naturale della chiave

Nota che questo non funzionerà ancora se le tue chiavi si confrontano con 0, ma questo dovrebbe essere sufficiente per la maggior parte degli elementi comparable (come hashCode, equals e compareTo sono spesso sincronizzati...)

Vedere Ordinazione.onResultOf () e Funzioni.forMap().

Attuazione

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 fatto dato una mappa completa finita
  2. Non provare i comparatori sopra su un TreeMap; non ha senso cercare di confrontare una chiave inserita quando non ha un valore fino a dopo il put, cioè si romperà molto velocemente

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

Risposta"Completa" / Mappa ordinata in tempo reale per valori

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

Nota: questo è cambiato in modo significativo nel giugno 2012 - il codice precedente non potrebbe mai funzionare: un interno HashMap è necessario per cercare i valori senza creare un ciclo infinito tra il 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 inseriamo, ci assicuriamo che la mappa hash abbia il valore per il comparatore, quindi inseriamo il 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 eliminino le chiavi non duplicate (a causa di == confronto). Questi 2 elementi sono vitali per garantire che il contratto della mappa sia mantenuto; se pensi di non volerlo, allora sei quasi al 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));
 207
Author: Stephen, 2017-05-23 12:26:34

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;
}
 174
Author: devinmoore, 2016-06-14 07:51:07

Con Java 8, è possibile utilizzare l'api streams 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));
 29
Author: assylias, 2018-09-18 06:04:52

L'ordinamento delle chiavi richiede che il Comparatore cerchi ogni valore per ogni confronto. Una soluzione più scalabile utilizzerebbe direttamente l'entrySet, da allora il valore sarebbe immediatamente disponibile per ogni confronto (anche se non l'ho eseguito 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());
    }
}

Ci sono modi per ridurre la rotazione della memoria per la soluzione di cui sopra. Il primo ArrayList creato potrebbe ad esempio essere riutilizzato come valore di ritorno; ciò richiede la soppressione di alcuni avvisi generici, ma potrebbe valerne la pena per il codice di libreria riutilizzabile. Inoltre, il Comparatore non deve essere riassegnato ad ogni chiamata.

Ecco una versione più efficiente anche se meno attraente:

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 è necessario accedere continuamente alle informazioni ordinate (piuttosto che ordinarle una volta ogni tanto), è possibile utilizzare una mappa multipla aggiuntiva. Fatemi sapere se avete bisogno di maggiori dettagli...

 28
Author: volley, 2008-10-01 21:02:28

La libreria commons-collections contiene una soluzione chiamata TreeBidiMap. Oppure, potresti dare un'occhiata all'API di Google Collections. Ha TreeMultimap che potresti usare.

E se non si desidera utilizzare questi framework... vengono con il codice sorgente.

 25
Author: p3t0r, 2011-05-30 18:59:51

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

Ecco una soluzione che penso 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;
}

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

 23
Author: Anthony, 2012-02-25 16:44:42

Per ottenere questo risultato con le nuove funzionalità in 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 specificato. In alternativa, se i tuoi 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 al momento in cui viene chiamato questo metodo, quindi nessuno dei due rifletterà le modifiche successive all'altro. Per una vista iterabile dal vivo 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 di la mappa data ogni volta che viene iterata, quindi escludendo la modifica simultanea, rifletterà sempre lo stato corrente della mappa.

 17
Author: gdejohn, 2014-06-24 22:16:19

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

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 imbarazzante 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 Mappa.Oggetti Entry, da cui è possibile ottenere le chiavi e valori.

 14
Author: Lyudmil, 2008-09-24 00:58:27

Crea comparatore personalizzato e usalo 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));

    }
}

Usa il seguente codice nella tua 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);

Uscita:

{B=75, D=50, C=50, A=35}
 14
Author: Sujan Reddy A, 2013-02-10 06:10:09

La risposta votata di più non funziona quando hai 2 elementi uguali. la mappa ad albero lascia fuori valori uguali.

L'exmaple: mappa non ordinata

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

Risultati

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

Quindi lascia fuori E!!

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

Nell'esempio:

Classe ValueComparator implementa Comparatore {

Base della mappa; public ValueComparator(Mappa di base) { questo.base = base; }

Confronto pubblico int (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 restituisce:

Mappa non ordinata:

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

Risultati:

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

Come risposta agli alieni (2011 novembre. 22): Sto usando questa soluzione per una mappa di ID interi e nomi, ma l'idea è la stessa, quindi potrebbe essere che il codice sopra non sia corretto (lo scriverò in un test e ti darò il codice corretto), questo è il codice per un ordinamento della 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 test (l'ho appena testata, e funziona per il numero intero, 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 cource puoi renderlo molto più generico, ma ne avevo solo bisogno per 1 caso (la mappa)

 11
Author: michel.iamit, 2011-11-23 13:11:46

Usa 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
Author: RoyalBigorno, 2014-09-09 06:43:09

Invece di usare Collections.sort come alcuni suggerirei di usare Arrays.sort. In realtà ciò che fa Collections.sort è 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]);
    }
}

Chiama semplicemente toArray nell'elenco e quindi usa 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 ommits questo passaggio in quanto non crea LinkedList non necessaria. Ecco il codice, generico-friendly e prestazioni ottimali:

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;
}
 9
Author: ciamej, 2012-05-30 00:47:27

Questa è una variazione 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 aria come gestire i null.

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

 8
Author: Roger, 2011-02-09 10:21:25

Grave problema. Se usi la prima risposta (Google ti porta qui), cambia il comparatore per aggiungere una clausola uguale, altrimenti non puoi ottenere valori da sorted_map by keys:

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
    }
 7
Author: cuneyt, 2012-11-18 06:37:50

Ci sono già molte risposte per 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à poiché chiavi e valori vengono modificati nella mappa. Due altri domande chiedere questo specificamente.

Ho preparato un esempio generico amichevole che risolve questo caso d'uso. Questa implementazione non rispetta tutti i contratti dell'interfaccia Mappa, ad esempio riflettendo le modifiche e le rimozioni di valore nei set return from keySet() e entrySet () nell'oggetto originale. Sentivo che una tale soluzione sarebbe stata troppo grande per includere in una risposta di overflow dello stack. Se riesco a creare un'implementazione più completa, forse lo pubblicherò su Github e poi ad esso 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;
    }
}
 6
Author: David Bleckmann, 2017-06-05 21:36:27

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

Si potrebbe desiderare di mettere elenco in un Mappa di scorrimento collegata, ma se si sta solo andando a iterare su di esso subito, che è superfluo...

 5
Author: malix, 2011-08-31 20:40:25

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

Nell'esempio inferiore si suppone di aggiungere TreeMap un comparatore nel punto in cui si trova*. Ma con l'API java fornisce al comparatore solo chiavi, non valori. Tutti gli esempi qui indicati 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 questo modo:

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

Si creerà la 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 di confronto:

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 puoi facilmente aggiungere 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()
        );
}
 5
Author: Darkless, 2011-12-29 21:33:51

Approccio migliore

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

Uscita

java ==== 20

MAC ==== 26

C++ ==== 45

Unix ==== 67

Why this kolavari ==== 93
 5
Author: Nilesh Jadav, 2016-12-05 22:29:17

A seconda del contesto, utilizzando java.util.LinkedHashMap<T> che rememebers l'ordine in cui gli elementi vengono inseriti 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().

 4
Author: Ryan Delucchi, 2008-09-20 21:07:49

Basato sul codice @devinmoore, un metodo di ordinamento delle mappe che utilizza generici e supporta sia l'ordine ascendente che discendente.

/**
 * 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
}
 4
Author: Maxim Veksler, 2009-04-14 13:44:29

Ecco una soluzione OO (cioè non usa metodi 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 );
  }
}

Con la presente donato al pubblico dominio.

 4
Author: Dave Jarvis, 2011-01-02 05:53:22

Afaik il modo più pulito è utilizzare le raccolte per ordinare la mappa sul 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
Author: lisak, 2011-06-08 15:23:04

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 i valori duplicati (che sono ordinati per chiavi a proposito). 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
Author: dimkar, 2011-09-01 09:53:33

Di sicuro la soluzione di Stephen è davvero ottima, ma per chi non sa usare Guava:

Ecco la mia soluzione per ordinare per valore una mappa. Questa soluzione gestisce il caso in cui ci sono due volte lo 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

L'exec: http://www.ideone.com/dq3Lu

L'uscita:

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

 4
Author: Sebastien Lorber, 2011-12-27 17:43:44

Se hai chiavi duplicate e solo un piccolo set di dati (

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 sortedOutputMap conterrà i dati in ordine decrescente quando iterati. Per cambiare l'ordine basta cambiare > in a

Non è l'ordinamento più veloce ma esegue il lavoro senza ulteriori dipendenze.

 3
Author: nibor, 2011-09-26 21:39:32

Ho unito le soluzioni di user157196 e Carter Page:

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;
    }
}
 3
Author: RobotMan, 2014-04-22 09:09:59

Puoi provare le multimaps di Guava:

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

Di conseguenza si ottiene una mappa dai valori originali alle raccolte di chiavi che corrispondono a loro. Questo approccio può essere utilizzato anche se ci sono più chiavi per lo stesso valore.

 3
Author: Vitalii Fedorenko, 2014-11-29 19:46:28