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?
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;
}
}
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}
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);
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 () .
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:
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 viteLe 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!
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));
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;
}
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));
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 ...
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.
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.
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.
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.
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}
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));
}
}
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)
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;
}
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.
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
}
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
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;
}
}
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()
.
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 ...
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()
);
}
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());
}
}
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.
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;
}
}
}
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
}
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
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.
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()
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;
}
}
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.
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(); }
}
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
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.
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);
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;
}
}
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}
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());
}
}
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);
}
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
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.