1788 votes

Trier une Map<Key, Value> par valeurs

Je suis relativement novice en Java, et je me retrouve souvent à devoir trier un fichier de type Map<Key, Value> sur les valeurs.

Étant donné que les valeurs ne sont pas uniques, je me retrouve à convertir le fichier keySet dans un array et de trier ce tableau par Triage par tableau avec un comparateur personnalisé qui trie sur la valeur associée à la clé.

Y a-t-il un moyen plus facile ?

28 votes

Une carte n'est pas faite pour être triée, mais pour être consultée rapidement. Les valeurs égales des objets rompent la contrainte de la carte. Utilisez l'ensemble des entrées, comme List<Map.Entry<...>> list =new LinkedList(map.entrySet()) y Collections.sort .... c'est comme ça.

2 votes

Un cas où cela peut se produire est celui où nous essayons d'utiliser un compteur en Java (Map<Object, Integer>). Le tri par nombre d'occurrences serait alors une opération courante. Un langage comme Python possède une structure de données Counter intégrée. Pour une autre façon de l'implémenter en Java, aquí est un exemple

11 votes

Il y a beaucoup de cas d'utilisation pour les cartes triées, c'est pourquoi vous avez TreeMap et ConcurrentSkipListMap dans le jdk.

969voto

Carter Page Points 905

Voici une version générique conviviale :

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

0 votes

Pourquoi utiliser Collections.sort s'il existe Arrays.sort ? voir ma réponse pour plus de détails

0 votes

Cela m'a fait gagner beaucoup de temps ! Le cas d'essai est également génial :) Y a-t-il une raison pour laquelle vous avez utilisé un LinkedHashMap plutôt qu'un HashMap classique ?

11 votes

Content que ça vous aide. John, le LinkedHashMap est important pour la solution car il fournit un ordre d'itération prévisible.

431voto

user157196 Points 1010

Remarque importante :

Ce code peut être cassé de plusieurs façons. Si vous avez l'intention d'utiliser le code fourni, veillez à lire également les commentaires pour être conscient des implications. 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 un 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}

18 votes

Plus maintenant ( stackoverflow.com/questions/109383/ ). Aussi, pourquoi y avait-il un casting à Double ? Cela ne devrait-il pas être simplement return ((Comparable)base.get(a).compareTo(((Comparable)base.get(b))‌​) ?

12 votes

@Stephen : Non. Dans ce cas, toutes les clés égales par valeur sont abandonnées (différence entre égaux et comparaison par référence). En outre : Même ce code a des problèmes avec la séquence suivante map.put("A","1d");map.put("B","1d");map.put("C",67d);map.put‌​("D",99.5d);

43 votes

Le comparateur utilisé pour le treemap est incompatible avec equals (voir la javadox sortMap). Cela signifie que le retrait d'éléments de la carte arborescente ne fonctionnera pas. sorted_map.get("A") retournera null. Cela signifie que cette utilisation du treemap est cassée.

410voto

Brian Goetz Points 6062

Java 8 offre une nouvelle réponse : convertir les entrées en un flux, et utiliser les combinateurs de comparateurs 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, il suffit d'inverser 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 consommer les données. Par exemple, si vous voulez le top 10 dans 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 imprimer à System.out :

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

0 votes

Bien, mais qu'en est-il de l'utilisation de parallelStream() dans ce cas ?

11 votes

Il fonctionnera en parallèle, mais vous constaterez peut-être que le coût de la fusion des cartes pour combiner les résultats partiels est trop élevé et que la version parallèle ne sera pas aussi performante que vous l'espériez. Mais elle fonctionne et produit la bonne réponse.

0 votes

Merci pour vos conseils utiles. C'est exactement ce que je me demandais, bien que cela dépende du type de clé que vous utilisez et de tant de paramètres... L'important est que "ça marche et que ça donne la bonne réponse".

215voto

Stephen Points 8670

Trois réponses d'une ligne...

J'utiliserais Collections Google Goyave pour le faire - si vos valeurs sont Comparable alors vous pouvez utiliser

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

Ce qui créera une fonction (objet) pour la carte [qui prend n'importe laquelle des clés en entrée et renvoie la valeur correspondante], puis leur appliquera un ordre naturel (comparable) [les valeurs].

S'ils ne sont pas comparables, vous devrez faire quelque chose du type suivant

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

Celles-ci peuvent être appliquées à un TreeMap (en tant que Ordering étend Comparator ), ou un LinkedHashMap après un certain tri

NB : Si vous comptez utiliser un TreeMap, n'oubliez pas que si une comparaison == 0, alors l'élément se trouve déjà dans la liste (ce qui se produira si vous avez plusieurs valeurs dont la comparaison est identique). Pour pallier ce problème, vous pouvez ajouter votre clé au comparateur comme suit (en supposant que vos clés et valeurs sont Comparable ):

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

\= Appliquer l'ordre naturel à la valeur représentée par la clé, et le combiner avec l'ordre naturel de la clé.

Notez que cela ne fonctionnera toujours pas si vos clés sont comparées à 0, mais cela devrait être suffisant pour la plupart des cas. comparable articles (comme hashCode , equals y compareTo sont souvent synchronisées...)

Véase Commande.onResultOf() y Fonctions.forMap() .

Mise en œuvre

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 va très probablement fonctionner, mais :

  1. doit être fait pour obtenir une carte complète et finie
  2. N'essayez pas les comparateurs ci-dessus sur une TreeMap il est inutile d'essayer de comparer une clé insérée si elle n'a pas de valeur avant le put, c'est-à-dire qu'il y aura une rupture très rapide.

Le point 1 est un peu rédhibitoire pour moi ; google collections est incroyablement paresseux (ce qui est bien : vous pouvez faire pratiquement toutes les opérations en un instant ; le vrai travail est fait lorsque vous commencez à utiliser le résultat), et cela nécessite de copier un fichier todo carte !

Réponse "complète" / carte vivante triée par valeurs

Mais ne vous inquiétez pas : si vous êtes suffisamment obsédé par le fait d'avoir une carte "vivante" triée de cette manière, vous pouvez résoudre non pas l'un mais les deux ( !) des problèmes ci-dessus avec quelque chose de fou comme ce qui suit :

Note : Ceci a changé de manière significative en juin 2012 - le code précédent ne pourrait jamais fonctionner : un HashMap interne est nécessaire pour rechercher les valeurs sans créer une boucle infinie entre le TreeMap.get() -> compare() y 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 carte de hachage a la valeur pour le comparateur, puis nous mettons dans le TreeSet pour le tri. Mais avant cela, nous vérifions la carte de hachage pour voir si la clé n'est pas en fait un doublon. De plus, le comparateur que nous créons inclura également la clé afin que les valeurs dupliquées ne suppriment pas les clés non dupliquées (en raison de la comparaison ==). Ces 2 éléments sont vital pour s'assurer que le contrat de la carte est conservé ; si vous pensez que vous ne voulez pas cela, alors vous êtes presque au point d'inverser la carte entièrement (à Map<V,K> ).

Le constructeur devrait être appelé comme

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

0 votes

Bonjour @Stephen , pouvez-vous donner un exemple d'utilisation de la commande ? J'ai regardé dans le code source de Ordering , et je n'arrive pas à comprendre ce que .natural().onResultOf(...) retourne ! Le code source est "public <F> Ordering<F> onResultOf" , je ne sais même pas comment il se compile ! Plus important, comment utiliser "<F> Ordering<F>" pour trier une carte ? Est-ce un comparateur ou autre chose ? Merci.

0 votes

Ordering est simplement un riche Comparator . J'ai essayé de commenter chaque exemple (l'italique en dessous de chacun). "naturel" indique que les objets sont Comparable c'est comme le ComparableComparator d'Apache Common. onResultOf applique une fonction à l'élément en cours de comparaison. Donc, si vous avez une fonction qui ajoute 1 à un nombre entier, alors natural().onResultOf(add1Function).compare(1,2) finiraient par faire 2.compareTo(3)

0 votes

ImmutableSortedMap.copyOf lève l'exception IllegalArgumentException si la carte d'origine contient des valeurs en double.

188voto

devinmoore Points 2172

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

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

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

    return result;
}

16 votes

La liste à trier est "new LinkedList" ? ?? Gee. Heureusement, Collections.sort() vide d'abord la liste dans un tableau, pour éviter précisément ce genre d'erreur (mais quand même, vider une ArrayList dans un tableau devrait être plus rapide que de faire la même chose pour une LinkedList).

0 votes

Ne peut pas convertir Iterator en TernaryTree.Iterator

0 votes

Pourquoi utiliser Collections.sort s'il existe Arrays.sort ? voir ma réponse pour plus de détails

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X