Sortieren Sie eine Karte nach Werten

Lesezeit: 13 Minuten

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 arrayund 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?

  • 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()) und Collections.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) zu verwenden. Das Sortieren nach der Anzahl der Vorkommen wäre dann eine übliche Operation. Eine Sprache wie Python hat eine eingebaute Counter-Datenstruktur. Für eine alternative Art der Implementierung in Java, Hier ist ein Beispiel

    – 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


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}

  • Nicht mehr (stackoverflow.com/questions/109383/…). Außerdem, warum gab es eine Besetzung für Double? Sollte es nicht einfach sein return ((Comparable)base.get(a).compareTo(((Comparable)base.get(b)))?

    – Stefan

    11. August 2010 um 21:50 Uhr

  • @Stephen: Nein. In diesem Fall werden alle Schlüssel, die dem Wert nach gleich sind, gelöscht (Unterschied zwischen Gleichheit und Vergleich nach Referenz). Zusätzlich: Auch dieser Code hat Probleme mit der folgenden Sequenz map.put("A","1d");map.put("B","1d");map.put("C",67d);map.put("D",99.5d);

    – mlwida

    20. August 2010 um 7:00 Uhr

  • Der für die Treemap verwendete Komparator ist inkonsistent mit equals (siehe javadox sortMap). Das bedeutet, dass das Zurückziehen von Elementen aus der Baumkarte nicht funktioniert. sorted_map.get(“A”) gibt null zurück. Das bedeutet, dass diese Verwendung von Treemaps gebrochen ist.

    – mR_fr0g

    1. Dezember 2010 um 14:36 ​​Uhr

  • Nur für den Fall, dass es den Leuten nicht klar ist: Diese Lösung wird wahrscheinlich nicht das tun, was Sie wollen, wenn Sie mehrere Schlüssel haben, die demselben Wert zugeordnet sind – nur einer dieser Schlüssel wird im sortierten Ergebnis angezeigt.

    – Maxy-B

    24. November 2011 um 4:37 Uhr

  • Louis Wasserman (ja, einer der Google-Guava-Jungs) mag diese Antwort eigentlich gar nicht: „Es bricht auf mehrere wirklich verwirrende Arten, wenn man es auch nur komisch ansieht. Wenn sich die Backingmap ändert, wird sie brechen. Wenn mehrere Schlüssel map auf den gleichen Wert setzen, wird es brechen.Wenn Sie get auf einen Schlüssel aufrufen, der nicht in der Backingmap ist, wird es brechen.Wenn Sie irgendetwas tun, was dazu führen würde, dass ein Lookup auf einem Schlüssel erfolgt, der nicht drin ist die Karte — ein Map.equals-Aufruf, containsKey, irgendetwas — es wird mit wirklich seltsamen Stack-Traces brechen.” plus.google.com/102216152814616302326/posts/bEQLDK712MJ

    – Haylem

    3. Juli 2012 um 21:19 Uhr

  • Schön, aber was ist mit der Verwendung von parallelStream() in diesem Fall ?

    – Benj

    9. Dezember 2014 um 18:21 Uhr


  • Es funktioniert parallel, aber Sie werden möglicherweise feststellen, dass die Kosten für das Zusammenführen von Karten zum Kombinieren der Teilergebnisse zu hoch sind und die parallele Version möglicherweise nicht die erhoffte Leistung erbringt. Aber es funktioniert und liefert die richtige Antwort.

    – Brian Götz

    9. Dezember 2014 um 18:37 Uhr

  • Müssen Sie im Top10-Beispiel nicht “compareByValue” verwenden?

    – Löwe

    19. August 2016 um 23:34 Uhr

  • Der Teil zum Erstellen einer Top Ten ist falsch, Sie müssen zwei weitere Parameter hinzufügen, wie hier gepostet: stackoverflow.com/a/19671853/5655767

    – Stefan

    21. August 2017 um 11:19 Uhr

  • @Benj es wird in Bezug auf das Extrahieren der Top-10 funktionieren, aber die resultierende Karte wird nicht mehr bestellt.

    – Orangenhund

    18. Juni 2019 um 14:30 Uhr

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:

  1. muss bei einer vollständig fertigen Karte durchgeführt werden
  2. 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));

  • Hallo @Stephen, kannst du ein Beispiel für die Verwendung von Ordering geben? Ich schaue mir den Quellcode von Ordering an und kann absolut nicht herausfinden, was .natural().onResultOf(…) zurückgibt! Der Quellcode ist “public Ordering onResultOf” , ich weiß nicht einmal, wie er kompiliert wird! Am wichtigsten ist, wie man “ Ordering” verwendet, um eine Karte zu sortieren? Ist das ein Komparator oder so? Danke.

    – Kleinufo

    10. November 2010 um 10:58 Uhr

  • Ordering ist einfach reich Comparator. Ich habe versucht, jedes Beispiel zu kommentieren (die Kursivschrift unter jedem). “natürlich” gibt an, dass die Objekte sind Comparable; Es ist wie der ComparableComparator von Apache Common. onResultOf wendet eine Funktion auf das zu vergleichende Element an. Wenn Sie also eine Funktion hätten, die 1 zu einer Ganzzahl addiert, dann natural().onResultOf(add1Function).compare(1,2) würde am Ende tun 2.compareTo(3)

    – Stefan

    11. November 2010 um 11:44 Uhr

  • ImmutableSortedMap.copyOf löst IllegalArgumentException aus, wenn doppelte Werte in der ursprünglichen Zuordnung vorhanden sind.

    – lbalazscs

    30. April 2013 um 9:39 Uhr

  • @Ibalazscs Ja, das wird es – Sie sollten es verwenden können ImmutableSetMultiMap oder ImmutableListMultiMap um die Sammlung von doppelten Variablen zu enthalten.

    – Stefan

    1. Mai 2013 um 0:31 Uhr

  • Vielen Dank dafür, ich habe Ihre Lösung in einem Projekt verwendet. Ich denke, es gibt ein Problem in put: Um sich wie eine Karte zu verhalten, muss sie den zuvor mit dem Schlüssel verknüpften Wert zurückgeben, falls vorhanden, aber so wird es nie funktionieren. Die von mir verwendete Lösung besteht darin, den entfernten Wert zurückzugeben, falls vorhanden.

    – Alex

    21. August 2013 um 9:22 Uhr

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

  • Hallo @Stephen, kannst du ein Beispiel für die Verwendung von Ordering geben? Ich schaue mir den Quellcode von Ordering an und kann absolut nicht herausfinden, was .natural().onResultOf(…) zurückgibt! Der Quellcode ist “public Ordering onResultOf” , ich weiß nicht einmal, wie er kompiliert wird! Am wichtigsten ist, wie man “ Ordering” verwendet, um eine Karte zu sortieren? Ist das ein Komparator oder so? Danke.

    – Kleinufo

    10. November 2010 um 10:58 Uhr

  • Ordering ist einfach reich Comparator. Ich habe versucht, jedes Beispiel zu kommentieren (die Kursivschrift unter jedem). “natürlich” gibt an, dass die Objekte sind Comparable; Es ist wie der ComparableComparator von Apache Common. onResultOf wendet eine Funktion auf das zu vergleichende Element an. Wenn Sie also eine Funktion hätten, die 1 zu einer Ganzzahl addiert, dann natural().onResultOf(add1Function).compare(1,2) würde am Ende tun 2.compareTo(3)

    – Stefan

    11. November 2010 um 11:44 Uhr

  • ImmutableSortedMap.copyOf löst IllegalArgumentException aus, wenn doppelte Werte in der ursprünglichen Zuordnung vorhanden sind.

    – lbalazscs

    30. April 2013 um 9:39 Uhr

  • @Ibalazscs Ja, das wird es – Sie sollten es verwenden können ImmutableSetMultiMap oder ImmutableListMultiMap um die Sammlung von doppelten Variablen zu enthalten.

    – Stefan

    1. Mai 2013 um 0:31 Uhr

  • Vielen Dank dafür, ich habe Ihre Lösung in einem Projekt verwendet. Ich denke, es gibt ein Problem in put: Um sich wie eine Karte zu verhalten, muss sie den zuvor mit dem Schlüssel verknüpften Wert zurückgeben, falls vorhanden, aber so wird es nie funktionieren. Die von mir verwendete Lösung besteht darin, den entfernten Wert zurückzugeben, falls vorhanden.

    – Alex

    21. August 2013 um 9:22 Uhr

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

  • Wie sortiere ich es in umgekehrter Reihenfolge?

    – Vlad Holubiev

    1. Juni 2014 um 19:18 Uhr

  • eine Lösung gefunden – Collections.reverseOrder(comparing(Entry::getValue))

    – Vlad Holubiev

    9. August 2014 um 8:26 Uhr


  • Ich glaube, ich sehe dort einen Tippfehler – sollte “toMap” nicht als “Collectors.toMap()” bezeichnet werden?

    – Jake Stokes

    2. Oktober 2017 um 14:31 Uhr

  • @JakeStokes Oder verwende einen statischen Import 🙂

    – Assylias

    3. Oktober 2017 um 5:59 Uhr

  • Eine bessere Möglichkeit, nach Eingabewert in umgekehrter Reihenfolge zu sortieren, ist: Entry.comparingByValue(Comparator.reverseOrder())

    – Gediminas Rimsa

    24. November 2018 um 9:20 Uhr


979290cookie-checkSortieren Sie eine Karte nach Werten

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy