TreeMap ordena por valor

Quiero escribir un comparador que me permita ordenar un TreeMap por valor en lugar del orden natural predeterminado.

Intenté algo como esto, pero no puedo descubrir qué salió mal:

import java.util.*; class treeMap { public static void main(String[] args) { System.out.println("the main"); byValue cmp = new byValue(); Map map = new TreeMap(cmp); map.put("de",10); map.put("ab", 20); map.put("a",5); for (Map.Entry pair: map.entrySet()) { System.out.println(pair.getKey()+":"+pair.getValue()); } } } class byValue implements Comparator<Map.Entry> { public int compare(Map.Entry e1, Map.Entry e2) { if (e1.getValue() < e2.getValue()){ return 1; } else if (e1.getValue() == e2.getValue()) { return 0; } else { return -1; } } } 

Supongo que lo que estoy preguntando es: ¿Puedo obtener un Map.Entry pasado al comparador?

No puede hacer que TreeMap ordene los valores, ya que eso desafía la especificación SortedMap :

Un Map que proporciona una ordenación total de sus claves .

Sin embargo, utilizando una colección externa, siempre puede ordenar Map.entrySet() como desee, ya sea por claves, valores o incluso una combinación (!!) de los dos.

Aquí hay un método genérico que devuelve un SortedSet de Map.Entry , dado un Map cuyos valores son Comparable :

 static > SortedSet> entriesSortedByValues(Map map) { SortedSet> sortedEntries = new TreeSet>( new Comparator>() { @Override public int compare(Map.Entry e1, Map.Entry e2) { int res = e1.getValue().compareTo(e2.getValue()); return res != 0 ? res : 1; } } ); sortedEntries.addAll(map.entrySet()); return sortedEntries; } 

Ahora puedes hacer lo siguiente:

  Map map = new TreeMap(); map.put("A", 3); map.put("B", 2); map.put("C", 1); System.out.println(map); // prints "{A=3, B=2, C=1}" System.out.println(entriesSortedByValues(map)); // prints "[C=1, B=2, A=3]" 

Tenga en cuenta que las cosas originales se producirán si intenta modificar el SortedSet mismo o el Map.Entry dentro, porque ya no es una “vista” del mapa original como entrySet() .

En términos generales, la necesidad de ordenar las entradas de un mapa por sus valores es atípica.


Nota sobre == para Integer

Su comparador original compara Integer usando == . Esto casi siempre es incorrecto, ya que == con operandos Integer es una igualdad de referencia, no una igualdad de valores.

  System.out.println(new Integer(0) == new Integer(0)); // prints "false"!!! 

Preguntas relacionadas

  • Cuando se comparan dos enteros en Java, ¿se produce un desempaquetado automático? (¡¡¡NO!!!)
  • ¿Se garantiza que el new Integer(i) == i en Java? (¡¡¡SÍ!!!)

la respuesta de polygenelubricants es casi perfecta. Sin embargo, tiene un error importante. No manejará las entradas de mapas donde los valores son los mismos.

Este código: …

 Map nonSortedMap = new HashMap(); nonSortedMap.put("ape", 1); nonSortedMap.put("pig", 3); nonSortedMap.put("cow", 1); nonSortedMap.put("frog", 2); for (Entry entry : entriesSortedByValues(nonSortedMap)) { System.out.println(entry.getKey()+":"+entry.getValue()); } 

Saldría:

 ape:1 frog:2 pig:3 

Observe cómo nuestra vaca desapareció, ya que compartió el valor “1” con nuestro simio: ¡O!

Esta modificación del código resuelve ese problema:

 static > SortedSet> entriesSortedByValues(Map map) { SortedSet> sortedEntries = new TreeSet>( new Comparator>() { @Override public int compare(Map.Entry e1, Map.Entry e2) { int res = e1.getValue().compareTo(e2.getValue()); return res != 0 ? res : 1; // Special fix to preserve items with equal values } } ); sortedEntries.addAll(map.entrySet()); return sortedEntries; } 

En Java 8:

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

Un TreeMap siempre está ordenado por las teclas, cualquier otra cosa es imposible. Un Comparator simplemente le permite controlar cómo se ordenan las claves.

Si desea los valores ordenados, debe extraerlos en una List y ordenarlos.

Esto no se puede hacer usando un Comparator , ya que siempre obtendrá la clave del mapa para comparar. TreeMap solo puede ordenar por la clave.

La respuesta de Olof es buena, pero necesita una cosa más antes de que sea perfecta. En los comentarios debajo de su respuesta, dacwe (correctamente) señala que su implementación viola el contrato de Comparar / Igual para Conjuntos. Si intentas llamar contiene o elimina en una entrada que está claramente en el conjunto, el conjunto no lo reconocerá debido al código que permite que las entradas con valores iguales se coloquen en el conjunto. Entonces, para arreglar esto, necesitamos probar la igualdad entre las claves:

 static > SortedSet> entriesSortedByValues(Map map) { SortedSet> sortedEntries = new TreeSet>( new Comparator>() { @Override public int compare(Map.Entry e1, Map.Entry e2) { int res = e1.getValue().compareTo(e2.getValue()); if (e1.getKey().equals(e2.getKey())) { return res; // Code will now handle equality properly } else { return res != 0 ? res : 1; // While still adding all entries } } } ); sortedEntries.addAll(map.entrySet()); return sortedEntries; } 

“Tenga en cuenta que la ordenación mantenida por un conjunto ordenado (ya sea que se proporcione o no un comparador explícito) debe ser consistente con equals si el conjunto ordenado implementa correctamente la interfaz Set … la interfaz Set se define en términos de la operación equals , pero un conjunto ordenado realiza todas las comparaciones de elementos utilizando su método compareTo (o comparar), por lo que dos elementos que se consideran iguales por este método son, desde el punto de vista del conjunto ordenado, iguales “. ( http://docs.oracle.com/javase/6/docs/api/java/util/SortedSet.html )

Dado que originalmente pasamos por alto la igualdad para obligar al conjunto a agregar entradas de igual valor, ahora tenemos que probar la igualdad en las claves para que el conjunto realmente devuelva la entrada que está buscando. Esto es un poco desordenado y definitivamente no es el modo en que se usaron los conjuntos, pero funciona.

Sé que esta publicación específicamente solicita ordenar TreeMap por valores, pero para aquellos de nosotros que realmente no les importa la implementación pero sí queremos una solución que mantenga la colección ordenada como elementos agregados, agradecería los comentarios sobre este TreeSet solución. Por un lado, los elementos no se recuperan fácilmente por la clave, pero para el caso de uso que tenía a mano (encontrar las n teclas con los valores más bajos), esto no era un requisito.

  TreeSet> set = new TreeSet<>(new Comparator>() { @Override public int compare(Map.Entry o1, Map.Entry o2) { int valueComparison = o1.getValue().compareTo(o2.getValue()); return valueComparison == 0 ? o1.getKey().compareTo(o2.getKey()) : valueComparison; } }); int key = 5; double value = 1.0; set.add(new AbstractMap.SimpleEntry<>(key, value)); 

Muchas personas oyen que se recomienda usar List y yo prefiero usarlo también

Aquí hay dos métodos que necesita para ordenar las entradas del mapa de acuerdo a sus valores.

  static final Comparator> DOUBLE_VALUE_COMPARATOR = new Comparator>() { @Override public int compare(Entry< ?, Double> o1, Entry< ?, Double> o2) { return o1.getValue().compareTo(o2.getValue()); } }; static final List> sortHashMapByDoubleValue(HashMap temp) { Set> entryOfMap = temp.entrySet(); List> entries = new ArrayList>(entryOfMap); Collections.sort(entries, DOUBLE_VALUE_COMPARATOR); return entries; }