web-dev-qa-db-fra.com

Trier une carte <clé, valeur> par valeurs

Je suis relativement nouveau en Java et trouve souvent que je dois trier un Map<Key, Value> sur les valeurs.

Comme les valeurs ne sont pas uniques, je me trouve à convertir keySet en array et à trier ce tableau par le biais de array sort avec un custom comparator qui trie sur la valeur associée à la clé.

Y a-t-il un moyen plus facile?

1473
Abe

Voici une version générique:

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;
    }
}
824
Carter Page

Note importante:

Ce code peut casser de plusieurs façons. Si vous avez l'intention d'utiliser le code fourni, assurez-vous de lire les commentaires également pour en prendre conscience. Par exemple, les valeurs ne peuvent plus être récupérées par leur clé. (get retourne toujours null.)


Cela semble beaucoup plus facile que tout ce qui précède. Utilisez une TreeMap comme suit:

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

Sortie:

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

Java 8 offre une nouvelle réponse: convertissez les entrées en flux et utilisez les combinateurs de comparaison de Map.Entry:

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

Cela vous permettra de consommer les entrées triées par ordre croissant de valeur. Si vous voulez une valeur décroissante, inversez simplement le comparateur:

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

Si les valeurs ne sont pas comparables, vous pouvez passer un comparateur explicite:

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

Vous pouvez ensuite utiliser d'autres opérations de flux pour utiliser les données. Par exemple, si vous souhaitez que le top 10 d'une nouvelle carte:

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

Ou imprimez sur System.out:

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

Trois réponses d'une ligne ...

J'utiliserais Collections Google Guava faire ceci - si vos valeurs sont Comparable, alors vous pouvez utiliser

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

Ce qui créera une fonction (un objet) pour la carte [prenant une des clés en entrée, renvoyant la valeur respective], puis leur appliquant un ordre naturel (comparable) [les valeurs].

Si elles ne sont pas comparables, vous devrez faire quelque chose dans le sens de

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

Ceux-ci peuvent être appliqués à une TreeMap (comme Ordering s'étend Comparator), ou à LinkedHashMap après un tri

NB: Si vous allez utiliser une TreeMap, rappelez-vous que si une comparaison == 0, l'élément est déjà dans la liste (ce qui se produira si vous avez plusieurs valeurs comparant les mêmes) . Pour remédier à cela, vous pouvez ajouter votre clé au comparateur de la manière suivante (en supposant que vos clés et vos valeurs sont Comparable):

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

= Appliquez un ordre naturel à la valeur mappée par la clé et composez-le avec l'ordre naturel de la clé

Notez que cela ne fonctionnera toujours pas si vos clés se comparent à 0, mais cela devrait être suffisant pour la plupart des éléments comparable (car hashCode, equals et compareTo sont souvent synchronisés ...)

Voir Ordering.onResultOf () et Functions.forMap () .

La mise en oeuvre

Alors, maintenant que nous avons un comparateur qui fait ce que nous voulons, nous devons en obtenir un résultat. 

map = ImmutableSortedMap.copyOf(myOriginalMap, valueComparator);

Maintenant, cela fonctionnera probablement, mais:

  1. doit être fait étant donné une carte complète et finie
  2. N'essayez pas les comparateurs ci-dessus sur une TreeMap; il ne sert à rien d'essayer de comparer une clé insérée quand elle n'a pas de valeur avant la fin, c'est-à-dire qu'elle se cassera très vite

Le point 1 est un peu une affaire pour moi; google collections est incroyablement paresseux (ce qui est bien: vous pouvez effectuer pratiquement toutes les opérations en un instant; le vrai travail est fait lorsque vous commencez à utiliser le résultat), ce qui nécessite la copie d'une carte entière!

"Complet" réponse/Live carte triée par valeurs

Ne t'inquiète pas cependant; si vous étiez assez obsédé par le fait d'avoir une carte "en direct" triée de cette manière, vous pourriez résoudre non pas l'un mais les deux (!) des problèmes ci-dessus avec quelque chose de fou comme celui-ci:

Remarque: cela a considérablement changé en juin 2012 - le code précédent ne pouvait jamais fonctionner: une HashMap interne était nécessaire pour rechercher les valeurs sans créer de boucle infinie entre les fonctions TreeMap.get() -> compare() et 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"));
    }
 }

Lorsque nous mettons, nous nous assurons que la table de hachage a la valeur pour le comparateur, puis nous plaçons dans le TreeSet pour le tri. Mais avant cela, nous vérifions la carte de hachage pour nous assurer que la clé n’est pas un doublon. De plus, le comparateur que nous créons inclura également la clé afin que les valeurs en double ne suppriment pas les clés non-en double (en raison de la comparaison ==) . Ces 2 éléments sont vital pour assurer la le contrat de carte est conservé; si vous pensez ne pas vouloir cela, vous êtes presque sur le point d'inverser complètement la carte (en Map<V,K>).

Le constructeur devrait être appelé en tant que 

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

De 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;
}
180
devinmoore

Avec Java 8, vous pouvez utiliser streams api pour le faire de manière beaucoup moins verbeuse:

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

Pour trier les clés, le comparateur doit rechercher chaque valeur pour chaque comparaison. Une solution plus évolutive utiliserait entrySet directement, car la valeur serait immédiatement disponible pour chaque comparaison (bien que je ne l'aie pas sauvegardée par des chiffres).

Voici une version générique d'une telle chose:

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

Il existe des moyens de réduire la rotation de la mémoire pour la solution ci-dessus. Le premier ArrayList créé pourrait par exemple être réutilisé comme valeur de retour; cela nécessiterait la suppression de certains avertissements génériques, mais cela pourrait en valoir la peine pour le code de bibliothèque réutilisable. De plus, il n'est pas nécessaire de réaffecter le comparateur à chaque invocation.

Voici une version plus efficace mais moins attrayante:

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

Enfin, si vous devez accéder en permanence aux informations triées (plutôt que de les trier de temps en temps), vous pouvez utiliser une multi-carte supplémentaire. Faites-moi savoir si vous avez besoin de plus de détails ...

30
volley

La bibliothèque commons-collections contient une solution appelée TreeBidiMap . Vous pouvez également consulter l'API Google Collections. Il a TreeMultimap que vous pouvez utiliser.

Et si vous ne voulez pas utiliser ce framework ... ils viennent avec le code source.

25
p3t0r

J'ai examiné les réponses fournies, mais beaucoup d'entre elles sont plus compliquées que nécessaire ou suppriment des éléments de la carte lorsque plusieurs clés ont la même valeur.

Voici une solution qui, à mon avis, convient mieux:

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

Notez que la carte est triée de la valeur la plus élevée à la plus basse.

24
Anthony

Pour ce faire, avec les nouvelles fonctionnalités de 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());
}

Les entrées sont classées par leurs valeurs à l'aide du comparateur donné. Alternativement, si vos valeurs sont mutuellement comparables, aucun comparateur explicite n’est nécessaire:

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

La liste renvoyée est un instantané de la carte donnée au moment où cette méthode est appelée. Par conséquent, aucune des deux ne reflétera les modifications ultérieures apportées à l'autre. Pour une vue itérable en direct de la carte:

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

L'itéré renvoyé crée un nouvel instantané de la carte donnée à chaque itération. Par conséquent, sauf modification simultanée, elle reflétera toujours l'état actuel de la carte.

17
gdejohn

Bien que je convienne que le besoin constant de trier une carte est probablement une odeur, je pense que le code suivant est le moyen le plus simple de le faire sans utiliser une structure de données différente.

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

}

Et voici un test unitaire embarrassant et incomplet:

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

}

Le résultat est une liste triée d'objets Map.Entry, à partir de laquelle vous pouvez obtenir les clés et les valeurs.

14
Lyudmil

Créez un comparateur personnalisé et utilisez-le lors de la création d'un nouvel objet 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));

    }
}

Utilisez le code ci-dessous dans votre fonction 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);

Sortie:

{B=75, D=50, C=50, A=35}
14
Sujan Reddy A

Utilisez un comparateur générique tel que:

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
RoyalBigorno

La réponse la plus votée ne fonctionne pas lorsque vous avez 2 éléments équivalents à . La TreeMap laisse les valeurs égales.

l'exemple: carte non triée

 clé/valeur: D/67.3 
 clé/valeur: A/99.5 
 clé/valeur: B/67.4 
 clé/valeur: C/67.5 
 clé/valeur: E/99.5 

résultats

 clé/valeur: A/99.5 
 clé/valeur: C/67.5 
 clé/valeur: B/67.4 
 clé/valeur: D/67.3 

Alors laisse E !!

Pour moi, le comparateur a bien fonctionné, s'il est égal, ne retournez pas 0 mais -1.

dans l'exemple:

la classe ValueComparator implémente Comparator {

Carte de base; public ValueComparator (Map base) { this.base = base; }

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

} }

maintenant il retourne:

carte non triée:

 clé/valeur: D/67.3 
 clé/valeur: A/99.5 
 clé/valeur: B/67.4 
 clé/valeur: C/67.5 
 clé/valeur: E/99.5 

résultats:

 clé/valeur: A/99.5 
 clé/valeur: E/99.5 
 clé/valeur: C/67.5 
 clé/valeur: B/67.4 
 clé/valeur: D/67.3 

en réponse à Aliens (22 novembre 2011): dans un test et vous donner le code correct), ceci est le code pour un tri sur la carte, basé sur la solution ci-dessus:

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


}

et c'est la classe de test (je viens de la tester, et cela fonctionne pour 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);
    }
}

voici le code pour le comparateur d'une carte:

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

et ceci est le testcase pour ceci:

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

bien sûr, vous pouvez rendre cela beaucoup plus générique, mais j'en avais juste besoin pour 1 cas (la carte)

11
michel.iamit

Au lieu d'utiliser Collections.sort comme certains le font, je suggérerais d'utiliser Arrays.sort. En fait, ce que Collections.sort fait ressemble à ceci:

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

Il appelle simplement toArray sur la liste et utilise ensuite Arrays.sort. Ainsi, toutes les entrées de la carte seront copiées trois fois: une fois de la carte dans la liste temporaire (LinkedList ou ArrayList), puis dans le tableau temporaire et enfin dans la nouvelle carte.

Ma solution omet cette étape car elle ne crée pas de LinkedList inutile. Voici le code, générique et performant:

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
ciamej

C'est une variante de la réponse d'Anthony, qui ne fonctionne pas s'il existe des valeurs en double:

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

Notez qu'il est plutôt difficile de gérer les valeurs nulles. 

Un avantage important de cette approche est qu’elle renvoie en réalité une carte, contrairement à certaines des autres solutions proposées ici.

8
Roger

Un problème majeur. Si vous utilisez la première réponse (Google vous emmène ici), modifiez le comparateur pour ajouter une clause equal, sinon vous ne pouvez pas obtenir les valeurs de la sort_map par clés:

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
cuneyt

Meilleure approche

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

Sortie

Java ==== 20

MAC ==== 26

C++ ==== 45

Unix ==== 67

Why this kolavari ==== 93
6
Nilesh Jadav

Il y a déjà beaucoup de réponses à cette question, mais aucune ne m'a fourni ce que je cherchais, une implémentation de carte qui renvoie les clés et les entrées triées selon la valeur associée et conserve cette propriété lorsque les clés et les valeurs sont modifiées dans la carte. Deux autresquestions le demandent spécifiquement. 

J'ai concocté un exemple convivial générique qui résout ce cas d'utilisation. Cette implémentation n'honore pas tous les contrats de l'interface Map, tels que la prise en compte des modifications de valeur et des suppressions dans les ensembles renvoyés par keySet () et entrySet () dans l'objet d'origine. Je pensais qu'une telle solution serait trop vaste pour être incluse dans une réponse Stack Overflow. Si je parviens à créer une implémentation plus complète, je la posterai peut-être sur Github, puis sur le lien dans une version mise à jour de cette réponse.

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
David Bleckmann

Selon le contexte, utilisez Java.util.LinkedHashMap<T> qui rappelle l'ordre dans lequel les éléments sont placés dans la carte. Sinon, si vous devez trier les valeurs en fonction de leur ordre naturel, je vous recommanderais de conserver une liste séparée pouvant être triée via Collections.sort().

5
Ryan Delucchi

PuisqueTreeMap <> ne fonctionne paspour des valeurs pouvant être égales, j'ai utilisé ceci:

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

Vous voudrez peut-être mettrelistdans unLinkedHashMap, mais si vous allez simplement le parcourir tout de suite, c'est superflu ...

5
malix

C'est trop compliqué. Les cartes n'étaient pas censées faire ce travail en les triant par valeur. Le moyen le plus simple est de créer votre propre classe afin qu’elle réponde à vos besoins.

Dans l'exemple inférieur, vous êtes censé ajouter TreeMap un comparateur à l'endroit où * est. Mais, avec l’API Java, il ne donne au comparateur que des clés, pas des valeurs. Tous les exemples cités ici sont basés sur 2 cartes. Un hachage et un nouvel arbre. Ce qui est étrange.

L'exemple:

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

Alors changez la carte en un ensemble de cette façon:

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

Vous allez créer 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;
    }
}

et la classe de comparaison:

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

De cette façon, vous pouvez facilement ajouter plus de dépendances.

Et comme dernier point, je vais ajouter un itérateur simple:

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
Darkless

La méthode la plus propre consiste à utiliser les collections pour trier les cartes en fonction de la valeur:

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
lisak

Voici une solution OO (c'est-à-dire, n'utilise pas les méthodes 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 );
  }
}

Donné par la présente au domaine public.

4
Dave Jarvis

Quelques changements simples pour avoir une carte triée avec des paires qui ont des valeurs en double. Dans la méthode de comparaison (classe ValueComparator) lorsque les valeurs sont égales, ne retournez pas 0, mais renvoyez le résultat de la comparaison des 2 clés. Les clés sont distinctes dans une carte, ce qui vous permet de conserver les valeurs en double (qui sont triées par clé en passant). Donc, l'exemple ci-dessus pourrait être modifié comme ceci:

    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
dimkar

Basé sur le code @devinmoore, une méthode de tri des cartes utilisant des génériques et prenant en charge les ordres croissant et décroissant.

/**
 * 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
Maxim Veksler

Bien sûr, la solution de Stephen est vraiment géniale, mais pour ceux qui ne peuvent pas utiliser Guava:

Voici ma solution pour trier par valeur une carte ..__ Cette solution traite le cas où il existe deux fois la même valeur, etc.

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

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

Le résultat:

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

J'espère que cela aidera certaines personnes

4
Sebastien Lorber

Vous pouvez essayer les cartes multiples de Guava:

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

En conséquence, vous obtenez une carte des valeurs d'origine aux collections de clés qui leur correspondent. Cette approche peut être utilisée même s'il existe plusieurs clés pour la même valeur.

3
Vitalii Fedorenko

Entrée tardive.

Avec l'avènement de Java-8, nous pouvons utiliser des flux pour la manipulation de données de manière très simple/succincte. Vous pouvez utiliser des flux pour trier les entrées de la carte par valeur et créer une itération LinkedHashMap qui préserve insertion-order.

Par exemple:

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

Pour la commande inversée, remplacez:

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

avec

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

J'ai fusionné les solutions de user157196 et de 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
RobotMan

Si vous avez des clés en double et seulement un petit ensemble de données (<1000) et que votre code n'est pas critique en termes de performances, vous pouvez simplement procéder comme suit:

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 est l'entrée du code.

La variable savedOutputMap contiendra les données dans un ordre décroissant lors de leur itération. Pour changer d'ordre, il suffit de changer> en <dans l'instruction if.

Ce n'est pas le tri le plus rapide, mais fait le travail sans dépendances supplémentaires.

3
nibor

Lorsque je suis confronté à cela, je crée simplement une liste sur le côté. Si vous les assemblez dans une implémentation personnalisée de la carte, vous aurez l'impression d'être sympa ... Vous pouvez utiliser l'une des options suivantes, en effectuant le tri uniquement si nécessaire. (Remarque: je n'ai pas vraiment testé cela, mais ça compile ... ça pourrait être un petit bug idiot là-bas quelque part)

(Si vous voulez qu'il soit trié à la fois par clés et par valeurs, demandez à la classe d'étendre TreeMap, ne définissez pas les méthodes d'accès et demandez aux mutateurs d'appeler super.xxxxx au lieu de map_.xxxx)

package com.javadude.sample;

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

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

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

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

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

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

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

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

    // accessors
    public boolean containsKey(Object key)           { return map_.containsKey(key); }
    public boolean containsValue(Object value)       { return map_.containsValue(value); }
    public Set<Java.util.Map.Entry<K, V>> entrySet() { return map_.entrySet(); }
    public boolean equals(Object o)                  { return map_.equals(o); }
    public V get(Object key)                         { return map_.get(key); }
    public int hashCode()                            { return map_.hashCode(); }
    public boolean isEmpty()                         { return map_.isEmpty(); }
    public Set<K> keySet()                           { return map_.keySet(); }
    public int size()                                { return map_.size(); }
    public Collection<V> values()                    { return map_.values(); }
}
2
Scott Stanchfield

Cette méthode ne fera que servir le but. (le 'recul' signifie que les valeurs doivent implémenter l'interface Java.util.Comparable)

  /**

 * Sort a map according to values.

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

 * @return a map sorted on the values.

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

    entries.addAll(mapToSort.entrySet());

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

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

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

    }      

    return sortedMap;

}

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

2
didxga

Voici le code de Java 8 avec AbacusUtil

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

Déclaration : Je suis le développeur de AbacusUtil.

2
user_3380739

poster ma version de réponse

List<Map.Entry<String, Integer>> list = new ArrayList<>(map.entrySet());
    Collections.sort(list, (obj1, obj2) -> obj2.getValue().compareTo(obj1.getValue()));
    Map<String, Integer> resultMap = new LinkedHashMap<>();
    list.forEach(arg0 -> {
        resultMap.put(arg0.getKey(), arg0.getValue());
    });
    System.out.println(resultMap);
1

La méthode la plus simple brute-force sortHashMap pour HashMap<String, Long>: vous pouvez simplement copier-coller et utiliser comme ceci:

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

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

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

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

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

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

        return result;
    }
}
1
parsecer

J'ai réécrit la méthode de devinmoore qui effectue le tri d'une carte en fonction de sa valeur sans utiliser Iterator:

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

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

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

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

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

    return sortedMap;
}

Note: que nous avons utilisé LinkedHashMap comme mappe en sortie, car notre liste a été triée par valeur et nous devons maintenant stocker notre liste dans la mappe en sortie avec l'ordre des valeurs de clé insérée. Donc, si vous utilisez par exemple TreeMap comme carte en sortie, votre carte sera à nouveau triée par les clés de la carte!

C'est la méthode principale:

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

Enfin, voici le résultat:

Input Map:{1=one, 3=three, 5=five}
Sorted Map:{5=five, 1=one, 3=three}
0
smart-developer

Ma solution est une approche assez simple pour utiliser des API généralement données .. Nous utilisons la fonctionnalité de Map pour exporter son contenu sous la forme Set via entrySet () . Nous avons maintenant un Set contenant Map.Entry objets. 

D'accord, un ensemble ne comporte pas d'ordre, mais nous pouvons prendre le contenu et le placer dans un ArrayList . Il a maintenant un ordre random, mais nous allons quand même le trier.

Comme ArrayList est un Collection , nous utilisons maintenant la méthode Collections.sort () pour mettre de l’ordre au chaos. Comme nos objets Map.Entry ne réalisent pas le type de comparaison dont nous avons besoin, nous fournissons un Comparateur .

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

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

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

}
0
Alexander

Utiliser la bibliothèque de goyave:

public static <K,V extends Comparable<V>>SortedMap<K,V> sortByValue(Map<K,V> original){
    var comparator = Ordering.natural()
            .reverse() // highest first
            .nullsLast()
            .onResultOf(Functions.forMap(original, null))
            .compound(Ordering.usingToString());
    return ImmutableSortedMap.copyOf(original, comparator);
}
0
Stanislav Levental
public class Test {
  public static void main(String[] args) {
    TreeMap<Integer, String> hm=new TreeMap();
    hm.put(3, "arun singh");
    hm.put(5, "vinay singh");
    hm.put(1, "bandagi singh");
    hm.put(6, "vikram singh");
    hm.put(2, "panipat singh");
    hm.put(28, "jakarta singh");

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

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

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

SORTIE =

//sort by values 

3 arun singh
1 bandagi singh
28 jakarta singh
2 panipat singh
6 vikram singh
5 vinay singh
0
Arun Raaj

Si vous préférez une structure de données Map qui trie de manière inhérente sans valeur ni déclencher de méthode de tri ni passer explicitement à un utilitaire, vous pouvez appliquer les solutions suivantes:

(1) org.drools.chance.core.util.ValueSortedMap (projet JBoss) gère deux cartes en interne, une pour la recherche et une pour la gestion des valeurs triées. Tout à fait similaire aux réponses ajoutées précédemment, mais c'est probablement la partie abstraction et encapsulation (y compris le mécanisme de copie) qui rend l'utilisation plus sûre de l'extérieur. 

(2) http://techblog.molindo.at/2008/11/Java-map-sorted-by-value.html évite de conserver deux cartes et repose plutôt sur/étend à partir de LinkedMap d'Apache Common. (Note de l'auteur du blog: as all the code here is in the public domain):

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

// SNIP: imports

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

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

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

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

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

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

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

    public boolean isAscending() {
        return _asc;
    }
}

(3) Ecrivez un Map personnalisé ou une extension de LinkedHashMap qui triera uniquement pendant l'énumération (par exemple, values(), keyset(), entryset()) selon les besoins. L'implémentation/le comportement interne est abstrait de celui utilisant cette classe, mais il apparaît au client de cette classe que les valeurs sont toujours triées lorsque demandé pour l'énumération. Cette classe espère que le tri aura lieu une seule fois si toutes les opérations put ont été terminées avant les énumérations. La méthode de tri reprend certaines des réponses précédentes à cette question. 

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

    private boolean isSortingNeeded = false;

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

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

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

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

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

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

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

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

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

        Collections.sort(list);

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

        isSortingNeeded = false;
    }

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

(4) Offres de goyave ImmutableMap.Builder.orderEntriesByValue (Comparator valueComparator) bien que la carte obtenue soit immuable:

Configure ce générateur pour ordonner les entrées par valeur en fonction du comparateur spécifié.

L'ordre de tri est stable, c'est-à-dire si deux entrées ont des valeurs qui comparer comme équivalent, l'entrée qui a été insérée en premier sera d'abord dans l'ordre d'itération de la carte construite.

0
Kenston Choi