ordenar - map ordenado java
TreeMap ordena por valor (8)
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<String, Integer> map = new TreeMap<String, Integer>(cmp);
map.put("de",10);
map.put("ab", 20);
map.put("a",5);
for (Map.Entry<String,Integer> pair: map.entrySet()) {
System.out.println(pair.getKey()+":"+pair.getValue());
}
}
}
class byValue implements Comparator<Map.Entry<String,Integer>> {
public int compare(Map.Entry<String,Integer> e1, Map.Entry<String,Integer> 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?
En Java 8:
LinkedHashMap<Integer, String> sortedMap =
map.entrySet().stream().
sorted(Entry.comparingByValue()).
collect(Collectors.toMap(Entry::getKey, Entry::getValue,
(e1, e2) -> e1, LinkedHashMap::new));
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 <K,V extends Comparable<? super V>> SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
new Comparator<Map.Entry<K,V>>() {
@Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> 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.
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<Entry<?, Double>> DOUBLE_VALUE_COMPARATOR =
new Comparator<Entry<?, Double>>() {
@Override
public int compare(Entry<?, Double> o1, Entry<?, Double> o2) {
return o1.getValue().compareTo(o2.getValue());
}
};
static final List<Entry<?, Double>> sortHashMapByDoubleValue(HashMap temp)
{
Set<Entry<?, Double>> entryOfMap = temp.entrySet();
List<Entry<?, Double>> entries = new ArrayList<Entry<?, Double>>(entryOfMap);
Collections.sort(entries, DOUBLE_VALUE_COMPARATOR);
return entries;
}
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 <K,V extends Comparable<? super V>>
SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
new Comparator<Map.Entry<K,V>>() {
@Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> 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<String,Integer> map = new TreeMap<String,Integer>();
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
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<Map.Entry<Integer, Double>> set = new TreeSet<>(new Comparator<Map.Entry<Integer, Double>>()
{
@Override
public int compare(Map.Entry<Integer, Double> o1, Map.Entry<Integer, Double> 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));
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.
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<String, Integer> nonSortedMap = new HashMap<String, Integer>();
nonSortedMap.put("ape", 1);
nonSortedMap.put("pig", 3);
nonSortedMap.put("cow", 1);
nonSortedMap.put("frog", 2);
for (Entry<String, Integer> 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 <K,V extends Comparable<? super V>> SortedSet<Map.Entry<K,V>> entriesSortedByValues(Map<K,V> map) {
SortedSet<Map.Entry<K,V>> sortedEntries = new TreeSet<Map.Entry<K,V>>(
new Comparator<Map.Entry<K,V>>() {
@Override public int compare(Map.Entry<K,V> e1, Map.Entry<K,V> 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;
}