1890 Stimmen

Sortieren einer Map<Schlüssel, Wert> nach Werten

Ich bin relativ neu in Java und stelle oft fest, dass ich eine Map<Key, Value> auf die Werte.

Da die Werte nicht eindeutig sind, muss ich die keySet in eine array und die Sortierung dieses Arrays durch Array-Sortierung mit einer benutzerdefinierter Komparator die nach dem mit dem Schlüssel verbundenen Wert sortiert.

Gibt es einen einfacheren Weg?

1014voto

Carter Page Punkte 2485

Hier ist eine allgemeinverständliche Version:

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

496voto

Brian Goetz Punkte 83148

Java 8 bietet eine neue Lösung: Konvertieren Sie die Einträge in einen Stream, und verwenden Sie die Komparator-Kombinatoren von Map.Entry:

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

Auf diese Weise können Sie die Einträge in aufsteigender Reihenfolge ihres Wertes konsumieren. Wenn Sie absteigende Werte wünschen, kehren Sie den Komparator einfach um:

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

Wenn die Werte nicht vergleichbar sind, können Sie einen expliziten Komparator übergeben:

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

Sie können dann mit anderen Stream-Operationen fortfahren, um die Daten zu konsumieren. Wenn Sie zum Beispiel die Top 10 in einer neuen Karte haben möchten:

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

Oder drucken Sie auf System.out :

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

430voto

user157196 Punkte 1005

Wichtiger Hinweis:

Dieser Code kann auf verschiedene Weise gebrochen werden. Wenn Sie den bereitgestellten Code verwenden wollen, sollten Sie auch die Kommentare lesen, um sich über die Auswirkungen im Klaren zu sein. Zum Beispiel können Werte nicht mehr über ihren Schlüssel abgerufen werden. ( get gibt immer zurück null .)


Es scheint viel einfacher zu sein als all das oben Genannte. 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
    }
}

出力します。

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}

214voto

Stephen Punkte 18898

Drei 1-zeilige Antworten...

Ich würde verwenden Google-Sammlungen Guave um dies zu tun - wenn Ihre Werte sind Comparable dann können Sie

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

Dadurch wird eine Funktion (ein Objekt) für die Map erstellt [die jeden der Schlüssel als Eingabe annimmt und den entsprechenden Wert zurückgibt], und dann eine natürliche (vergleichbare) Ordnung auf sie [die Werte] angewendet.

Wenn sie nicht vergleichbar sind, müssen Sie etwas tun, das in etwa wie folgt aussieht

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

Diese können auf eine TreeMap angewendet werden (als Ordering erweitert Comparator ), oder eine LinkedHashMap nach einiger Sortierung

NB : Wenn Sie eine TreeMap verwenden wollen, denken Sie daran, dass, wenn ein Vergleich == 0 ist, das Element bereits in der Liste ist (was passiert, wenn Sie mehrere Werte haben, die gleich sind). Um dies zu vermeiden, können Sie Ihren Schlüssel dem Vergleicher wie folgt hinzufügen (vorausgesetzt, Ihre Schlüssel und Werte sind Comparable ) :

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

\= Natürliche Ordnung auf den durch den Schlüssel abgebildeten Wert anwenden und diese mit der natürlichen Ordnung des Schlüssels verbinden

Beachten Sie, dass dies immer noch nicht funktioniert, wenn Ihre Schlüssel mit 0 verglichen werden, aber dies sollte für die meisten Fälle ausreichend sein comparable Artikel (wie hashCode , equals y compareTo sind oft synchron...)

Siehe Bestellung.onResultOf() y Functions.forMap() .

Umsetzung

Da wir nun einen Komparator haben, der das tut, was wir wollen, müssen wir ein Ergebnis von ihm erhalten.

map = ImmutableSortedMap.copyOf(myOriginalMap, valueComparator);

Das wird höchstwahrscheinlich auch funktionieren, aber:

  1. muss angesichts einer vollständigen fertigen Karte durchgeführt werden
  2. Versuchen Sie nicht, die obigen Komparatoren an einem TreeMap Es macht keinen Sinn, einen eingefügten Schlüssel zu vergleichen, wenn er erst nach dem Put einen Wert hat, d. h., er bricht sehr schnell ab.

Punkt 1 ist ein bisschen ein Deal-Breaker für mich; Google Sammlungen ist unglaublich faul (was gut ist: Sie können so ziemlich jede Operation in einem Augenblick zu tun; die eigentliche Arbeit ist getan, wenn Sie beginnen, das Ergebnis zu verwenden), und dies erfordert das Kopieren einer ganz Karte!

"Vollständige" Antwort/Live sortierte Karte nach Werten

Aber keine Sorge, wenn Sie so besessen davon wären, eine "Live"-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 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"));
    }
 }

Wenn wir put, stellen wir sicher, dass die Hash-Map den Wert für den Komparator hat, und dann put zum TreeSet für die Sortierung. Aber vorher überprüfen wir die Hash-Map, um zu sehen, dass der Schlüssel kein Duplikat ist. Außerdem wird der Komparator, den wir erstellen, auch den Schlüssel enthalten, damit doppelte Werte nicht die nicht doppelten Schlüssel löschen (aufgrund des == Vergleichs). Diese 2 Elemente sind vital um sicherzustellen, dass der Kartenvertrag eingehalten wird; wenn Sie meinen, dass Sie das nicht wollen, dann sind Sie fast an dem Punkt, an dem Sie die Karte komplett umkehren müssen (zu Map<V,K> ).

Der Konstruktor müsste aufgerufen werden als

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

189voto

devinmoore Punkte 2691

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X