Frage Sortieren Sie eine Karte nach Werten


Ich bin relativ neu in Java und finde oft, dass ich eine sortieren muss Map<Key, Value> auf den Werten.

Da die Werte nicht eindeutig sind, konvergiere ich die keySet In ein arrayund Sortieren dieses Arrays durch Array-Sortierung mit einem benutzerdefinierter Vergleicher Das sortiert nach dem Wert, der dem Schlüssel zugeordnet ist.

Gibt es einen leichteren Weg?


1377


Ursprung


Antworten:


Hier ist eine generische 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;
    }
}

783



Wichtige Notiz:

Dieser Code kann auf verschiedene Arten unterbrochen werden. Wenn Sie beabsichtigen, den bereitgestellten Code zu verwenden, sollten Sie auch die Kommentare lesen, um sich der Auswirkungen bewusst zu sein. Zum Beispiel können Werte nicht mehr mit ihrem Schlüssel abgerufen werden. (get kommt immer zurück null.)


Es scheint viel einfacher als alles Vorhergehende. 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}

405



Java 8 bietet eine neue Antwort: Konvertieren Sie die Einträge in einen Stream und verwenden Sie die Vergleichskombinatoren 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 sortieren. Wenn Sie einen absteigenden Wert wünschen, drehen Sie einfach den Komparator 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 Vergleicher ü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. Zum Beispiel, wenn Sie die Top 10 in einer neuen Karte haben wollen:

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 nach System.out:

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

213



Drei 1-Zeilen-Antworten ...

ich würde ... benutzen Google Sammlungen  Guave um dies zu tun - wenn deine Werte sind Comparable dann kannst du verwenden

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

Das erstellt eine Funktion (ein Objekt) für die Karte [die einen der Schlüssel als Eingabe nimmt und den entsprechenden Wert zurückgibt] und wendet dann eine natürliche (vergleichbare) Reihenfolge auf sie an [die Werte].

Wenn sie nicht vergleichbar sind, dann müssen Sie etwas tun in der Art von

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

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

NB: Wenn Sie eine TreeMap verwenden, denken Sie daran, dass bei einem Vergleich == 0 das Element bereits in der Liste enthalten ist (dies passiert, wenn Sie mehrere Werte haben, die dasselbe vergleichen). Um dies zu erleichtern, könnten Sie Ihren Schlüssel dem Vergleicher 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, den der Schlüssel zugeordnet hat, und verbinden Sie ihn 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 Artikel (als hashCode, equals und compareTo sind oft synchron ...)

Sehen Bestellung.aufResultOf () und Funktionen.fürKarte ().

Implementierung

Jetzt, wo wir einen Komparator haben, der das macht, was wir wollen, müssen wir ein Ergebnis daraus bekommen.

map = ImmutableSortedMap.copyOf(myOriginalMap, valueComparator);

Nun wird dies höchstwahrscheinlich funktionieren, aber:

  1. Es muss eine fertige Karte erstellt werden
  2. Versuchen Sie nicht die Komparatoren oben auf a TreeMap; Es hat keinen Sinn, zu versuchen, einen eingefügten Schlüssel zu vergleichen, wenn er erst nach dem Setzen einen Wert hat, d. h. er wird wirklich schnell brechen

Punkt 1 ist ein bisschen ein Deal-Breaker für mich; google collections ist unglaublich faul (was gut ist: man kann so ziemlich jede Operation in einem Augenblick erledigen; die eigentliche Arbeit ist erledigt, wenn man das Ergebnis benutzt), und dazu muss man a kopieren ganze Karte!

"Volle" Antwort / Live sortierte Karte nach Werten

Mach dir aber keine Sorgen. Wenn Sie genug davon besessen wären, eine "Live" -Karte auf diese Art und Weise sortiert zu haben, könnten Sie nicht eine, sondern beide (!) der oben genannten Probleme mit etwas Verrücktem wie dem folgenden lösen:

Hinweis: Dies hat sich im Juni 2012 deutlich geändert - der vorherige Code konnte nie funktionieren: Eine interne HashMap ist erforderlich, um die Werte zu suchen, ohne eine Endlosschleife zwischen den beiden 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 stellen Sie dann das TreeSet zum Sortieren ein. Aber vorher überprüfen wir die Hash-Karte, um zu sehen, dass der Schlüssel kein Duplikat ist. Der von uns erstellte Vergleicher enthält auch den Schlüssel, sodass doppelte Werte die nicht doppelten Schlüssel nicht löschen (aufgrund von == Vergleich). Diese 2 Artikel sind wichtig um sicherzustellen, dass der Kartenvertrag eingehalten wird; Wenn Sie denken, dass Sie das nicht wollen, dann sind Sie fast an der Stelle, die Karte komplett umzukehren (to Map<V,K>).

Der Konstruktor müsste als aufgerufen werden

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

207



Von http://www.programsheaven.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;
}

171



Das Sortieren der Schlüssel erfordert, dass der Komparator jeden Wert für jeden Vergleich nachschlägt. Eine besser skalierbare Lösung würde das entrySet direkt verwenden, da dann der Wert für jeden Vergleich sofort verfügbar wäre (obwohl ich dies nicht durch Zahlen gesichert habe).

Hier ist eine generische Version von so etwas:

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

Es gibt Möglichkeiten, die Speicherrotation für die obige Lösung zu verringern. Die erste erstellte ArrayList könnte beispielsweise als Rückgabewert wiederverwendet werden; Dies würde die Unterdrückung einiger generischer Warnungen erfordern, aber es könnte sich für wiederverwendbaren Bibliothekscode lohnen. Außerdem muss der Comparator nicht bei jedem Aufruf neu zugewiesen werden.

Hier ist eine effizientere, wenn auch weniger ansprechende Version:

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

Wenn Sie fortlaufend auf die sortierten Informationen zugreifen müssen (anstatt sie nur einmal zu sortieren), können Sie eine zusätzliche Multi-Map verwenden. Lass es mich wissen, wenn du mehr Details brauchst ...


28



Mit Java 8 können Sie die Bäche api um es deutlich weniger wortreich zu machen:

Map<K, V> sortedMap = map.entrySet().stream()
                         .sorted(Entry.comparingByValue())
                         .collect(toMap(Entry::getKey, Entry::getValue,
                                  (e1,e2) -> e1, LinkedHashMap::new));

26



Die commons-collections-Bibliothek enthält eine Lösung namens BaumBidiMap. Oder Sie können sich das Google Collections-API ansehen. Es hat BaumMultimap welches du benutzen könntest.

Und wenn Sie dieses Framework nicht verwenden möchten ... kommen sie mit Quellcode.


25