Ich bin relativ neu in Java und finde oft, dass ich a sortieren muss Map<Key, Value>
auf die Werte.
Da die Werte nicht eindeutig sind, befinde ich mich beim Umrechnen keySet
In ein array
und das Array durchsortieren Array-Sortierung mit einer benutzerdefinierter Komparator die nach dem Wert sortiert, der dem Schlüssel zugeordnet ist.
Gibt es einen einfacheren Weg?
Wichtiger Hinweis:
Dieser Code kann auf verschiedene Weise brechen. Wenn Sie beabsichtigen, den bereitgestellten Code zu verwenden, lesen Sie unbedingt auch die Kommentare, um sich der Auswirkungen bewusst zu sein. Beispielsweise können Werte nicht mehr anhand ihres Schlüssels abgerufen werden. (get
kehrt immer zurück null
.)
Es scheint viel einfacher als alles zuvor Gesagte. Verwenden Sie eine TreeMap wie folgt:
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
}
}
Ausgabe:
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}
Drei einzeilige Antworten…
ich würde … benutzen Google-Sammlungen Guave dies zu tun – wenn Ihre Werte sind Comparable
dann kannst du verwenden
valueComparator = Ordering.natural().onResultOf(Functions.forMap(map))
Dadurch wird eine Funktion (Objekt) für die Karte erstellt [that takes any of the keys as input, returning the respective value]und wenden Sie dann eine natürliche (vergleichbare) Reihenfolge auf sie an [the values].
Wenn sie nicht vergleichbar sind, müssen Sie etwas in der Art von tun
valueComparator = Ordering.from(comparator).onResultOf(Functions.forMap(map))
Diese können auf eine TreeMap (wie Ordering
erweitert Comparator
) oder eine LinkedHashMap nach einiger Sortierung
NBHinweis: Wenn Sie eine TreeMap verwenden, denken Sie daran, dass bei einem Vergleich == 0 das Element bereits in der Liste ist (was passieren wird, wenn Sie mehrere Werte haben, die dasselbe vergleichen). Um dies abzumildern, könnten Sie Ihren Schlüssel wie folgt zum Komparator hinzufügen (vorausgesetzt, Ihre Schlüssel und Werte sind Comparable
):
valueComparator = Ordering.natural().onResultOf(Functions.forMap(map)).compound(Ordering.natural())
= Wenden Sie die natürliche Reihenfolge auf den Wert an, der durch den Schlüssel abgebildet wird, und verbinden Sie diese mit der natürlichen Reihenfolge des Schlüssels
Beachten Sie, dass dies immer noch nicht funktioniert, wenn Ihre Schlüssel mit 0 verglichen werden, aber dies sollte für die meisten ausreichen comparable
Gegenstände (wie hashCode
, equals
und compareTo
sind oft synchron…)
Sehen Bestellung.onResultOf() und Funktionen.forMap().
Implementierung
Jetzt, wo wir einen Komparator haben, der tut, was wir wollen, müssen wir ein Ergebnis daraus ziehen.
map = ImmutableSortedMap.copyOf(myOriginalMap, valueComparator);
Jetzt wird dies höchstwahrscheinlich funktionieren, aber:
- muss bei einer vollständig fertigen Karte durchgeführt werden
- Probieren Sie die obigen Komparatoren nicht an a aus
TreeMap
; Es macht keinen Sinn, einen eingefügten Schlüssel zu vergleichen, wenn er erst nach dem Put einen Wert hat, dh er wird sehr schnell kaputt gehen
Punkt 1 ist für mich ein bisschen ein Deal-Breaker; google collections ist unglaublich faul (was gut ist: Sie können so ziemlich jeden Vorgang sofort ausführen; die eigentliche Arbeit ist erledigt, wenn Sie anfangen, das Ergebnis zu verwenden), und dies erfordert das Kopieren von a ganz Karte!
“Vollständige” Antwort / Live sortierte Karte nach Werten
Aber keine Sorge; Wenn Sie davon besessen wären, eine “lebende” Karte auf diese Weise zu sortieren, könnten Sie nicht nur eines, sondern beide (!) der oben genannten Probleme mit etwas Verrücktem wie dem Folgenden lösen:
Hinweis: Dies hat sich im Juni 2012 erheblich geändert – der vorherige Code konnte nie funktionieren: Eine interne HashMap ist erforderlich, um die Werte nachzuschlagen, ohne eine Endlosschleife zwischen den zu erstellen TreeMap.get()
-> compare()
und 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"));
}
}
Wenn wir setzen, stellen wir sicher, dass die Hash-Map den Wert für den Komparator hat, und legen sie dann zum Sortieren in das TreeSet. Aber vorher überprüfen wir die Hash-Map, um zu sehen, dass der Schlüssel nicht wirklich ein Duplikat ist. Außerdem enthält der von uns erstellte Komparator auch den Schlüssel, damit doppelte Werte die nicht doppelten Schlüssel nicht löschen (aufgrund des ==-Vergleichs). Diese 2 Artikel sind lebenswichtig um sicherzustellen, dass der Kartenvertrag eingehalten wird; Wenn Sie denken, dass Sie das nicht wollen, dann sind Sie fast an dem Punkt, die Karte vollständig umzukehren (zu Map<V,K>
).
Der Konstruktor müsste als aufgerufen werden
new ValueComparableMap(Ordering.natural());
//or
new ValueComparableMap(Ordering.from(comparator));
Von 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;
}
Mit Java 8 können Sie die streamt api um es deutlich weniger ausführlich zu machen:
Map<K, V> sortedMap = map.entrySet().stream()
.sorted(Entry.comparingByValue())
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
Eine Karte soll nicht sortiert, sondern schnell aufgerufen werden. Gleiche Objektwerte brechen die Einschränkung der Karte. Verwenden Sie den Eintragssatz, wie
List<Map.Entry<...>> list =new LinkedList(map.entrySet())
undCollections.sort ....
es auf diese Weise.– Hannes
9. Februar 2014 um 17:34 Uhr
Ein Fall, in dem dies auftreten kann, wenn wir versuchen, einen Zähler in Java (Map
– Dämongolem
21. Dezember 2017 um 20:03 Uhr
Es gibt viele Anwendungsfälle für sortierte Karten, deshalb haben Sie TreeMap und ConcurrentSkipListMap in jdk.
– alobodzk
22. April 2018 um 19:10 Uhr
TreeMap und ConcurrentSkipListMap sortieren nach Schlüssel. Die Frage bezieht sich auf die Sortierung nach Wert.
– Petrus
28. April 2020 um 12:28 Uhr
Ich möchte das hinzufügen abhängig von Ihrem Anwendungsfall, kann es sinnvoll sein, einfach eine doppelte TreeMap zu behalten, die Ihren Wert Ihren Schlüsseln zuordnet. Zum Beispiel kann Ihre normale Karte “a” -> 5, “b” -> 7″ haben. Und Ihre “sortierte” Karte kann 5 -> “a”, 7 -> “b” haben. Sie würden einfach was auch immer verwenden Karte ist an verschiedenen Orten angebracht und bemüht sich, die beiden Karten immer zusammen zu modifizieren.Es ist nicht schön und es gibt viele Vorbehalte und Annahmen, aber für etwas In einigen Fällen kann dies eine einfache und effiziente Antwort im Vergleich zu allen Top-Antworten hier sein, die auf einer aktiven Sortierung Ihrer Werte beruhen.
– Rokoko
12. Juli 2021 um 6:45 Uhr