java - ordered - Ordenar un mapa<clave, valor> por valores
ordenar hashmap java (30)
Nota IMPORTANTE:
Este código puede romperse de múltiples maneras. Si pretende utilizar el código provisto, asegúrese de leer los comentarios y de estar al tanto de las implicaciones. Por ejemplo, los valores ya no se pueden recuperar por su clave. ( get
siempre devuelve null
).
Parece mucho más fácil que todo lo anterior. Use un TreeMap de la siguiente manera:
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
}
}
Salida:
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}
Soy relativamente nuevo en Java, y a menudo encuentro que necesito ordenar un Map<Key, Value>
en los valores.
Dado que los valores no son únicos, me encuentro convirtiendo keySet
en una array
, y clasificando esa matriz a través de la clasificación de matriz con un comparador personalizado que ordena el valor asociado con la clave.
hay una manera mas facil?
Aquí hay una versión genérica:
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;
}
}
Con Java 8, puede utilizar la API de flujos para hacerlo de una manera significativamente menos detallada:
Map<K, V> sortedMap = map.entrySet().stream()
.sorted(Entry.comparingByValue())
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
Cree un comparador personalizado y utilícelo mientras crea un nuevo objeto TreeMap.
class MyComparator implements Comparator<Object> {
Map<String, Integer> map;
public MyComparator(Map<String, Integer> map) {
this.map = map;
}
public int compare(Object o1, Object o2) {
if (map.get(o2) == map.get(o1))
return 1;
else
return ((Integer) map.get(o2)).compareTo((Integer)
map.get(o1));
}
}
Use el siguiente código en su función principal
Map<String, Integer> lMap = new HashMap<String, Integer>();
lMap.put("A", 35);
lMap.put("B", 75);
lMap.put("C", 50);
lMap.put("D", 50);
MyComparator comparator = new MyComparator(lMap);
Map<String, Integer> newMap = new TreeMap<String, Integer>(comparator);
newMap.putAll(lMap);
System.out.println(newMap);
Salida:
{B=75, D=50, C=50, A=35}
Desde 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;
}
En lugar de usar Collections.sort
como algunos lo hacen, sugeriría usar Arrays.sort
.En realidad lo que Collections.sort
hace es algo como esto:
public static <T extends Comparable<? super T>> void sort(List<T> list) {
Object[] a = list.toArray();
Arrays.sort(a);
ListIterator<T> i = list.listIterator();
for (int j=0; j<a.length; j++) {
i.next();
i.set((T)a[j]);
}
}
Simplemente llama toArray
a la lista y luego usa Arrays.sort
. De esta manera, todas las entradas del mapa se copiarán tres veces: una vez desde el mapa a la lista temporal (ya sea una Lista vinculada o Lista de Array), luego a la matriz temporal y finalmente al nuevo mapa.
Mi solución omite este paso, ya que no crea LinkedList innecesario. Aquí está el código, genérico y de rendimiento óptimo:
public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map)
{
@SuppressWarnings("unchecked")
Map.Entry<K,V>[] array = map.entrySet().toArray(new Map.Entry[map.size()]);
Arrays.sort(array, new Comparator<Map.Entry<K, V>>()
{
public int compare(Map.Entry<K, V> e1, Map.Entry<K, V> e2)
{
return e1.getValue().compareTo(e2.getValue());
}
});
Map<K, V> result = new LinkedHashMap<K, V>();
for (Map.Entry<K, V> entry : array)
result.put(entry.getKey(), entry.getValue());
return result;
}
He mirado las respuestas dadas, pero muchas de ellas son más complicadas de lo necesario o eliminan elementos del mapa cuando varias claves tienen el mismo valor.
Aquí hay una solución que creo que encaja mejor:
public static <K, V extends Comparable<V>> Map<K, V> sortByValues(final Map<K, V> map) {
Comparator<K> valueComparator = new Comparator<K>() {
public int compare(K k1, K k2) {
int compare = map.get(k2).compareTo(map.get(k1));
if (compare == 0) return 1;
else return compare;
}
};
Map<K, V> sortedByValues = new TreeMap<K, V>(valueComparator);
sortedByValues.putAll(map);
return sortedByValues;
}
Tenga en cuenta que el mapa está ordenado desde el valor más alto al más bajo.
Java 8 ofrece una nueva respuesta: convierta las entradas en una secuencia y use los combinadores de comparación de Map.Entry:
Stream<Map.Entry<K,V>> sorted =
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue());
Esto le permitirá consumir las entradas ordenadas en orden ascendente de valor. Si desea un valor descendente, simplemente invierta el comparador:
Stream<Map.Entry<K,V>> sorted =
map.entrySet().stream()
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));
Si los valores no son comparables, puede pasar un comparador explícito:
Stream<Map.Entry<K,V>> sorted =
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue(comparator));
Luego puede proceder a utilizar otras operaciones de transmisión para consumir los datos. Por ejemplo, si quieres los 10 primeros en un nuevo mapa:
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));
O imprimir a System.out
:
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.forEach(System.out::println);
La biblioteca de colecciones comunes contiene una solución llamada TreeBidiMap . O bien, puede echar un vistazo a la API de colecciones de Google. Tiene TreeMultimap que puedes usar.
Y si no quieres usar estos marcos ... vienen con código fuente.
La clasificación de las teclas requiere que el Comparador busque cada valor para cada comparación. Una solución más escalable usaría el entrySet directamente, ya que entonces el valor estaría disponible de inmediato para cada comparación (aunque no lo he respaldado con números).
Aquí hay una versión genérica de tal cosa:
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());
}
}
Hay formas de disminuir la rotación de memoria para la solución anterior. El primer ArrayList creado podría, por ejemplo, reutilizarse como valor de retorno; esto requeriría la supresión de algunas advertencias genéricas, pero podría valer la pena para el código de biblioteca reutilizable. Además, el comparador no tiene que volver a asignarse en cada invocación.
Aquí hay una versión más eficiente aunque menos atractiva:
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();
Finalmente, si necesita acceder continuamente a la información ordenada (en lugar de solo ordenarla de vez en cuando), puede usar un mapa múltiple adicional. Déjeme saber si usted necesita más detalles...
La respuesta votada por la mayoría no funciona cuando tienes 2 elementos iguales. El TreeMap deja valores iguales fuera.
el ejemplo: mapa sin clasificar
key/value: D/67.3 key/value: A/99.5 key/value: B/67.4 key/value: C/67.5 key/value: E/99.5
resultados
key/value: A/99.5 key/value: C/67.5 key/value: B/67.4 key/value: D/67.3
Así que deja fuera a E !!
Para mí funcionó bien para ajustar el comparador, si es igual no devuelve 0 sino -1.
en el ejemplo:
La clase ValueComparator implementa Comparator {
Mapa base ValueComparator público (Mapa base) {this.base = base; }
comparador de int público (objeto a, objeto b) {
if((Double)base.get(a) < (Double)base.get(b)) { return 1; } else if((Double)base.get(a) == (Double)base.get(b)) { return -1; } else { return -1; }
}}
ahora vuelve:
mapa sin clasificar
key/value: D/67.3 key/value: A/99.5 key/value: B/67.4 key/value: C/67.5 key/value: E/99.5
resultados:
key/value: A/99.5 key/value: E/99.5 key/value: C/67.5 key/value: B/67.4 key/value: D/67.3
como respuesta a Aliens (2011 nov. 22): Estoy usando esta solución para un mapa de los ID y los nombres de los enteros, pero la idea es la misma, por lo que podría ser que el código anterior no sea correcto (lo escribiré en una prueba y le proporcionamos el código correcto), este es el código para una clasificación de mapas, basado en la solución anterior:
package nl.iamit.util;
import java.util.Comparator;
import java.util.Map;
public class Comparators {
public static class MapIntegerStringComparator implements Comparator {
Map<Integer, String> base;
public MapIntegerStringComparator(Map<Integer, String> base) {
this.base = base;
}
public int compare(Object a, Object b) {
int compare = ((String) base.get(a))
.compareTo((String) base.get(b));
if (compare == 0) {
return -1;
}
return compare;
}
}
}
y esta es la clase de prueba (acabo de probarlo, y esto funciona para el mapa de cadenas Integer:
package test.nl.iamit.util;
import java.util.HashMap;
import java.util.TreeMap;
import nl.iamit.util.Comparators;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
public class TestComparators {
@Test
public void testMapIntegerStringComparator(){
HashMap<Integer, String> unSoretedMap = new HashMap<Integer, String>();
Comparators.MapIntegerStringComparator bvc = new Comparators.MapIntegerStringComparator(
unSoretedMap);
TreeMap<Integer, String> sorted_map = new TreeMap<Integer, String>(bvc);
//the testdata:
unSoretedMap.put(new Integer(1), "E");
unSoretedMap.put(new Integer(2), "A");
unSoretedMap.put(new Integer(3), "E");
unSoretedMap.put(new Integer(4), "B");
unSoretedMap.put(new Integer(5), "F");
sorted_map.putAll(unSoretedMap);
Object[] targetKeys={new Integer(2),new Integer(4),new Integer(3),new Integer(1),new Integer(5) };
Object[] currecntKeys=sorted_map.keySet().toArray();
assertArrayEquals(targetKeys,currecntKeys);
}
}
Aquí está el código para el comparador de un mapa:
public static class MapStringDoubleComparator implements Comparator {
Map<String, Double> base;
public MapStringDoubleComparator(Map<String, Double> base) {
this.base = base;
}
//note if you want decending in stead of ascending, turn around 1 and -1
public int compare(Object a, Object b) {
if ((Double) base.get(a) == (Double) base.get(b)) {
return 0;
} else if((Double) base.get(a) < (Double) base.get(b)) {
return -1;
}else{
return 1;
}
}
}
Y este es el testcase para esto:
@Test
public void testMapStringDoubleComparator(){
HashMap<String, Double> unSoretedMap = new HashMap<String, Double>();
Comparators.MapStringDoubleComparator bvc = new Comparators.MapStringDoubleComparator(
unSoretedMap);
TreeMap<String, Double> sorted_map = new TreeMap<String, Double>(bvc);
//the testdata:
unSoretedMap.put("D",new Double(67.3));
unSoretedMap.put("A",new Double(99.5));
unSoretedMap.put("B",new Double(67.4));
unSoretedMap.put("C",new Double(67.5));
unSoretedMap.put("E",new Double(99.5));
sorted_map.putAll(unSoretedMap);
Object[] targetKeys={"D","B","C","E","A"};
Object[] currecntKeys=sorted_map.keySet().toArray();
assertArrayEquals(targetKeys,currecntKeys);
}
Por supuesto, puede hacer esto mucho más genérico, pero solo lo necesitaba para 1 caso (el Mapa)
Para lograr esto con las nuevas características en Java 8:
import static java.util.Map.Entry.comparingByValue;
import static java.util.stream.Collectors.toList;
<K, V> List<Entry<K, V>> sort(Map<K, V> map, Comparator<? super V> comparator) {
return map.entrySet().stream().sorted(comparingByValue(comparator)).collect(toList());
}
Las entradas están ordenadas por sus valores utilizando el comparador dado. Alternativamente, si sus valores son comparables entre sí, no se necesita un comparador explícito:
<K, V extends Comparable<? super V>> List<Entry<K, V>> sort(Map<K, V> map) {
return map.entrySet().stream().sorted(comparingByValue()).collect(toList());
}
La lista devuelta es una instantánea del mapa dado en el momento en que se llama a este método, por lo que ninguno reflejará los cambios posteriores en el otro. Para una vista iterable en vivo del mapa:
<K, V extends Comparable<? super V>> Iterable<Entry<K, V>> sort(Map<K, V> map) {
return () -> map.entrySet().stream().sorted(comparingByValue()).iterator();
}
El iterable devuelto crea una instantánea nueva del mapa dado cada vez que se itera, por lo que, a menos que se realice una modificación simultánea, siempre reflejará el estado actual del mapa.
Si bien estoy de acuerdo en que la necesidad constante de ordenar un mapa es probablemente un olor, creo que el siguiente código es la forma más fácil de hacerlo sin usar una estructura de datos diferente.
public class MapUtilities {
public static <K, V extends Comparable<V>> List<Entry<K, V>> sortByValue(Map<K, V> map) {
List<Entry<K, V>> entries = new ArrayList<Entry<K, V>>(map.entrySet());
Collections.sort(entries, new ByValue<K, V>());
return entries;
}
private static class ByValue<K, V extends Comparable<V>> implements Comparator<Entry<K, V>> {
public int compare(Entry<K, V> o1, Entry<K, V> o2) {
return o1.getValue().compareTo(o2.getValue());
}
}
}
Y aquí hay una prueba unitaria vergonzosamente incompleta:
public class MapUtilitiesTest extends TestCase {
public void testSorting() {
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("One", 1);
map.put("Two", 2);
map.put("Three", 3);
List<Map.Entry<String, Integer>> sorted = MapUtilities.sortByValue(map);
assertEquals("First", "One", sorted.get(0).getKey());
assertEquals("Second", "Two", sorted.get(1).getKey());
assertEquals("Third", "Three", sorted.get(2).getKey());
}
}
El resultado es una lista ordenada de objetos Map.Entry, desde donde puede obtener las claves y los valores.
Use un comparador genérico como:
final class MapValueComparator<K,V extends Comparable<V>> implements Comparator<K> {
private Map<K,V> map;
private MapValueComparator() {
super();
}
public MapValueComparator(Map<K,V> map) {
this();
this.map = map;
}
public int compare(K o1, K o2) {
return map.get(o1).compareTo(map.get(o2));
}
}
El mejor enfoque
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
public class OrderByValue {
public static void main(String a[]){
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("java", 20);
map.put("C++", 45);
map.put("Unix", 67);
map.put("MAC", 26);
map.put("Why this kolavari", 93);
Set<Entry<String, Integer>> set = map.entrySet();
List<Entry<String, Integer>> list = new ArrayList<Entry<String, Integer>>(set);
Collections.sort( list, new Comparator<Map.Entry<String, Integer>>()
{
public int compare( Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2 )
{
return (o1.getValue()).compareTo( o2.getValue() );//Ascending order
//return (o2.getValue()).compareTo( o1.getValue() );//Descending order
}
} );
for(Map.Entry<String, Integer> entry:list){
System.out.println(entry.getKey()+" ==== "+entry.getValue());
}
}}
Salida
java ==== 20
MAC ==== 26
C++ ==== 45
Unix ==== 67
Why this kolavari ==== 93
Tres respuestas de 1 línea ...
Yo usaría Google Collections Guava para hacer esto: si sus valores son Comparable
entonces puede usar
valueComparator = Ordering.natural().onResultOf(Functions.forMap(map))
Lo que creará una función (objeto) para el mapa [que toma cualquiera de las teclas como entrada, devolviendo el valor respectivo], y luego les aplica un orden natural (comparable) [los valores].
Si no son comparables, entonces tendrás que hacer algo en la línea de
valueComparator = Ordering.from(comparator).onResultOf(Functions.forMap(map))
Estos se pueden aplicar a un TreeMap (a medida que Ordena extiende Comparator
), o un LinkedHashMap después de una cierta clasificación
NB : si va a utilizar un TreeMap, recuerde que si la comparación es == 0, entonces el elemento ya está en la lista (lo que ocurrirá si tiene varios valores que comparan lo mismo). Para aliviar esto, puede agregar su clave al comparador como tal (suponiendo que sus claves y valores son Comparable
):
valueComparator = Ordering.natural().onResultOf(Functions.forMap(map)).compound(Ordering.natural())
= Aplicar orden natural al valor asignado por la clave, y compuesto con el orden natural de la clave
Tenga en cuenta que esto aún no funcionará si sus claves se comparan con 0, pero debería ser suficiente para la mayoría de comparable
elementos comparable
(como hashCode
, equals
y compareTo
menudo están sincronizados ...)
Consulte Ordering.onResultOf() y Functions.forMap() .
Implementación
Así que ahora que tenemos un comparador que hace lo que queremos, necesitamos obtener un resultado de ello.
map = ImmutableSortedMap.copyOf(myOriginalMap, valueComparator);
Ahora esto probablemente funcionará, pero:
- Debe hacerse dado un mapa completo terminado
- No intentes los comparadores de arriba en un
TreeMap
; No tiene sentido intentar comparar una clave insertada cuando no tiene un valor hasta después de la puesta, es decir, se romperá muy rápido
El punto 1 es un factor decisivo para mí; Google Collections es increíblemente vago (lo que es bueno: puedes hacer prácticamente todas las operaciones en un instante; el trabajo real se realiza cuando empiezas a usar el resultado), ¡y esto requiere copiar un mapa completo !
Respuesta "completa" / Mapa ordenado en vivo por valores
Aunque no te preocupes; Si estuviera lo suficientemente obsesionado con tener un mapa "en vivo" ordenado de esta manera, no podría resolver uno sino ambos (!) de los problemas anteriores con algo loco como el siguiente:
Nota: Esto ha cambiado significativamente en junio de 2012: el código anterior nunca podría funcionar: se requiere un HashMap interno para buscar los valores sin crear un bucle infinito entre el 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"));
}
}
Cuando colocamos, nos aseguramos de que el mapa hash tenga el valor para el comparador, y luego lo colocamos en el TreeSet para su clasificación. Pero antes de eso, verificamos el mapa hash para ver que la clave no es realmente un duplicado. Además, el comparador que creamos también incluirá la clave para que los valores duplicados no eliminen las claves no duplicadas (debido a la comparación ==). Estos 2 elementos son vitales para garantizar que se mantenga el contrato del mapa; Si crees que no quieres eso, entonces estás casi a punto de revertir el mapa por completo (a Map<V,K>
).
El constructor necesitaría ser llamado como
new ValueComparableMap(Ordering.natural());
//or
new ValueComparableMap(Ordering.from(comparator));
Afaik, la forma más limpia es utilizar colecciones para ordenar el mapa en función del valor:
Map<String, Long> map = new HashMap<String, Long>();
// populate with data to sort on Value
// use datastructure designed for sorting
Queue queue = new PriorityQueue( map.size(), new MapComparable() );
queue.addAll( map.entrySet() );
// get a sorted map
LinkedHashMap<String, Long> linkedMap = new LinkedHashMap<String, Long>();
for (Map.Entry<String, Long> entry; (entry = queue.poll())!=null;) {
linkedMap.put(entry.getKey(), entry.getValue());
}
public static class MapComparable implements Comparator<Map.Entry<String, Long>>{
public int compare(Entry<String, Long> e1, Entry<String, Long> e2) {
return e1.getValue().compareTo(e2.getValue());
}
}
Basado en el código de @devinmoore, un método de clasificación de mapas que utiliza genéricos y admite tanto el orden ascendente como el descendente.
/**
* Sort a map by it''s keys in ascending order.
*
* @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
* @author Maxim Veksler
*/
public static <K, V> LinkedHashMap<K, V> sortMapByKey(final Map<K, V> map) {
return sortMapByKey(map, SortingOrder.ASCENDING);
}
/**
* Sort a map by it''s values in ascending order.
*
* @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
* @author Maxim Veksler
*/
public static <K, V> LinkedHashMap<K, V> sortMapByValue(final Map<K, V> map) {
return sortMapByValue(map, SortingOrder.ASCENDING);
}
/**
* Sort a map by it''s keys.
*
* @param sortingOrder {@link SortingOrder} enum specifying requested sorting order.
* @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
* @author Maxim Veksler
*/
public static <K, V> LinkedHashMap<K, V> sortMapByKey(final Map<K, V> map, final SortingOrder sortingOrder) {
Comparator<Map.Entry<K, V>> comparator = new Comparator<Entry<K,V>>() {
public int compare(Entry<K, V> o1, Entry<K, V> o2) {
return comparableCompare(o1.getKey(), o2.getKey(), sortingOrder);
}
};
return sortMap(map, comparator);
}
/**
* Sort a map by it''s values.
*
* @param sortingOrder {@link SortingOrder} enum specifying requested sorting order.
* @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
* @author Maxim Veksler
*/
public static <K, V> LinkedHashMap<K, V> sortMapByValue(final Map<K, V> map, final SortingOrder sortingOrder) {
Comparator<Map.Entry<K, V>> comparator = new Comparator<Entry<K,V>>() {
public int compare(Entry<K, V> o1, Entry<K, V> o2) {
return comparableCompare(o1.getValue(), o2.getValue(), sortingOrder);
}
};
return sortMap(map, comparator);
}
@SuppressWarnings("unchecked")
private static <T> int comparableCompare(T o1, T o2, SortingOrder sortingOrder) {
int compare = ((Comparable<T>)o1).compareTo(o2);
switch (sortingOrder) {
case ASCENDING:
return compare;
case DESCENDING:
return (-1) * compare;
}
return 0;
}
/**
* Sort a map by supplied comparator logic.
*
* @return new instance of {@link LinkedHashMap} contained sorted entries of supplied map.
* @author Maxim Veksler
*/
public static <K, V> LinkedHashMap<K, V> sortMap(final Map<K, V> map, final Comparator<Map.Entry<K, V>> comparator) {
// Convert the map into a list of key,value pairs.
List<Map.Entry<K, V>> mapEntries = new LinkedList<Map.Entry<K, V>>(map.entrySet());
// Sort the converted list according to supplied comparator.
Collections.sort(mapEntries, comparator);
// Build a new ordered map, containing the same entries as the old map.
LinkedHashMap<K, V> result = new LinkedHashMap<K, V>(map.size() + (map.size() / 20));
for(Map.Entry<K, V> entry : mapEntries) {
// We iterate on the mapEntries list which is sorted by the comparator putting new entries into
// the targeted result which is a sorted map.
result.put(entry.getKey(), entry.getValue());
}
return result;
}
/**
* Sorting order enum, specifying request result sort behavior.
* @author Maxim Veksler
*
*/
public static enum SortingOrder {
/**
* Resulting sort will be from smaller to biggest.
*/
ASCENDING,
/**
* Resulting sort will be from biggest to smallest.
*/
DESCENDING
}
Esta es una variación de la respuesta de Anthony, que no funciona si hay valores duplicados:
public static <K, V extends Comparable<V>> Map<K, V> sortMapByValues(final Map<K, V> map) {
Comparator<K> valueComparator = new Comparator<K>() {
public int compare(K k1, K k2) {
final V v1 = map.get(k1);
final V v2 = map.get(k2);
/* Not sure how to handle nulls ... */
if (v1 == null) {
return (v2 == null) ? 0 : 1;
}
int compare = v2.compareTo(v1);
if (compare != 0)
{
return compare;
}
else
{
Integer h1 = k1.hashCode();
Integer h2 = k2.hashCode();
return h2.compareTo(h1);
}
}
};
Map<K, V> sortedByValues = new TreeMap<K, V>(valueComparator);
sortedByValues.putAll(map);
return sortedByValues;
}
Tenga en cuenta que está bastante arriba en el aire cómo manejar nulos.
Una ventaja importante de este enfoque es que realmente devuelve un Mapa, a diferencia de algunas de las otras soluciones que se ofrecen aquí.
He fusionado las soluciones de user157196 y Carter Page:
class MapUtil {
public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue( Map<K, V> map ){
ValueComparator<K,V> bvc = new ValueComparator<K,V>(map);
TreeMap<K,V> sorted_map = new TreeMap<K,V>(bvc);
sorted_map.putAll(map);
return sorted_map;
}
}
class ValueComparator<K, V extends Comparable<? super V>> implements Comparator<K> {
Map<K, V> base;
public ValueComparator(Map<K, V> base) {
this.base = base;
}
public int compare(K a, K b) {
int result = (base.get(a).compareTo(base.get(b)));
if (result == 0) result=1;
// returning 0 would merge keys
return result;
}
}
Puedes probar los multimaps de guayaba:
TreeMap<Integer, Collection<String>> sortedMap = new TreeMap<>(
Multimaps.invertFrom(Multimaps.forMap(originalMap),
ArrayListMultimap.<Integer, String>create()).asMap());
Como resultado, obtiene un mapa de valores originales a colecciones de claves que se corresponden con ellos. Este enfoque puede utilizarse incluso si hay varias claves para el mismo valor.
Si tiene claves duplicadas y solo un pequeño conjunto de datos (<1000) y su código no es crítico para el rendimiento, puede hacer lo siguiente:
Map<String,Integer> tempMap=new HashMap<String,Integer>(inputUnsortedMap);
LinkedHashMap<String,Integer> sortedOutputMap=new LinkedHashMap<String,Integer>();
for(int i=0;i<inputUnsortedMap.size();i++){
Map.Entry<String,Integer> maxEntry=null;
Integer maxValue=-1;
for(Map.Entry<String,Integer> entry:tempMap.entrySet()){
if(entry.getValue()>maxValue){
maxValue=entry.getValue();
maxEntry=entry;
}
}
tempMap.remove(maxEntry.getKey());
sortedOutputMap.put(maxEntry.getKey(),maxEntry.getValue());
}
inputUnsortedMap es la entrada al código.
La variable sortedOutputMap contendrá los datos en orden decreciente cuando se repita. Para cambiar el orden solo cambia> a <en la sentencia if.
No es el tipo más rápido, pero hace el trabajo sin dependencias adicionales.
Sin duda, la solución de Stephen es realmente genial, pero para aquellos que no pueden usar Guava:
Aquí está mi solución para ordenar por valor un mapa. Esta solución maneja el caso donde hay dos veces el mismo valor, etc ...
// If you want to sort a map by value, and if there can be twice the same value:
// here is your original map
Map<String,Integer> mapToSortByValue = new HashMap<String, Integer>();
mapToSortByValue.put("A", 3);
mapToSortByValue.put("B", 1);
mapToSortByValue.put("C", 3);
mapToSortByValue.put("D", 5);
mapToSortByValue.put("E", -1);
mapToSortByValue.put("F", 1000);
mapToSortByValue.put("G", 79);
mapToSortByValue.put("H", 15);
// Sort all the map entries by value
Set<Map.Entry<String,Integer>> set = new TreeSet<Map.Entry<String,Integer>>(
new Comparator<Map.Entry<String,Integer>>(){
@Override
public int compare(Map.Entry<String,Integer> obj1, Map.Entry<String,Integer> obj2) {
Integer val1 = obj1.getValue();
Integer val2 = obj2.getValue();
// DUPLICATE VALUE CASE
// If the values are equals, we can''t return 0 because the 2 entries would be considered
// as equals and one of them would be deleted (because we use a set, no duplicate, remember!)
int compareValues = val1.compareTo(val2);
if ( compareValues == 0 ) {
String key1 = obj1.getKey();
String key2 = obj2.getKey();
int compareKeys = key1.compareTo(key2);
if ( compareKeys == 0 ) {
// what you return here will tell us if you keep REAL KEY-VALUE duplicates in your set
// if you want to, do whatever you want but do not return 0 (but don''t break the comparator contract!)
return 0;
}
return compareKeys;
}
return compareValues;
}
}
);
set.addAll(mapToSortByValue.entrySet());
// OK NOW OUR SET IS SORTED COOL!!!!
// And there''s nothing more to do: the entries are sorted by value!
for ( Map.Entry<String,Integer> entry : set ) {
System.out.println("Set entries: " + entry.getKey() + " -> " + entry.getValue());
}
// But if you add them to an hashmap
Map<String,Integer> myMap = new HashMap<String,Integer>();
// When iterating over the set the order is still good in the println...
for ( Map.Entry<String,Integer> entry : set ) {
System.out.println("Added to result map entries: " + entry.getKey() + " " + entry.getValue());
myMap.put(entry.getKey(), entry.getValue());
}
// But once they are in the hashmap, the order is not kept!
for ( Integer value : myMap.values() ) {
System.out.println("Result map values: " + value);
}
// Also this way doesn''t work:
// Logic because the entryset is a hashset for hashmaps and not a treeset
// (and even if it was a treeset, it would be on the keys only)
for ( Map.Entry<String,Integer> entry : myMap.entrySet() ) {
System.out.println("Result map entries: " + entry.getKey() + " -> " + entry.getValue());
}
// CONCLUSION:
// If you want to iterate on a map ordered by value, you need to remember:
// 1) Maps are only sorted by keys, so you can''t sort them directly by value
// 2) So you simply CAN''T return a map to a sortMapByValue function
// 3) You can''t reverse the keys and the values because you have duplicate values
// This also means you can''t neither use Guava/Commons bidirectionnal treemaps or stuff like that
// SOLUTIONS
// So you can:
// 1) only sort the values which is easy, but you loose the key/value link (since you have duplicate values)
// 2) sort the map entries, but don''t forget to handle the duplicate value case (like i did)
// 3) if you really need to return a map, use a LinkedHashMap which keep the insertion order
El exec: http://www.ideone.com/dq3Lu
La salida:
Set entries: E -> -1
Set entries: B -> 1
Set entries: A -> 3
Set entries: C -> 3
Set entries: D -> 5
Set entries: H -> 15
Set entries: G -> 79
Set entries: F -> 1000
Added to result map entries: E -1
Added to result map entries: B 1
Added to result map entries: A 3
Added to result map entries: C 3
Added to result map entries: D 5
Added to result map entries: H 15
Added to result map entries: G 79
Added to result map entries: F 1000
Result map values: 5
Result map values: -1
Result map values: 1000
Result map values: 79
Result map values: 3
Result map values: 1
Result map values: 3
Result map values: 15
Result map entries: D -> 5
Result map entries: E -> -1
Result map entries: F -> 1000
Result map entries: G -> 79
Result map entries: A -> 3
Result map entries: B -> 1
Result map entries: C -> 3
Result map entries: H -> 15
Espero que ayude a algunas personas
Algunos cambios simples para tener un mapa ordenado con pares que tienen valores duplicados. En el método de comparación (clase ValueComparator) cuando los valores son iguales, no se devuelve 0, sino que se obtiene el resultado de comparar las 2 teclas. Las claves son distintas en un mapa, por lo que puede mantener valores duplicados (que, por cierto, están ordenados por claves). Así que el ejemplo anterior podría ser modificado así:
public int compare(Object a, Object b) {
if((Double)base.get(a) < (Double)base.get(b)) {
return 1;
} else if((Double)base.get(a) == (Double)base.get(b)) {
return ((String)a).compareTo((String)b);
} else {
return -1;
}
}
}
Aquí hay una solución OO (es decir, no usa static
métodos):
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class SortableValueMap<K, V extends Comparable<V>>
extends LinkedHashMap<K, V> {
public SortableValueMap() { }
public SortableValueMap( Map<K, V> map ) {
super( map );
}
public void sortByValue() {
List<Map.Entry<K, V>> list = new LinkedList<Map.Entry<K, V>>( entrySet() );
Collections.sort( list, new Comparator<Map.Entry<K, V>>() {
public int compare( Map.Entry<K, V> entry1, Map.Entry<K, V> entry2 ) {
return entry1.getValue().compareTo( entry2.getValue() );
}
});
clear();
for( Map.Entry<K, V> entry : list ) {
put( entry.getKey(), entry.getValue() );
}
}
private static void print( String text, Map<String, Double> map ) {
System.out.println( text );
for( String key : map.keySet() ) {
System.out.println( "key/value: " + key + "/" + map.get( key ) );
}
}
public static void main( String[] args ) {
SortableValueMap<String, Double> map =
new SortableValueMap<String, Double>();
map.put( "A", 67.5 );
map.put( "B", 99.5 );
map.put( "C", 82.4 );
map.put( "D", 42.0 );
print( "Unsorted map", map );
map.sortByValue();
print( "Sorted map", map );
}
}
Por la presente donado al dominio público.
Como TreeMap <> no funciona con valores que pueden ser iguales, usé esto:
private <K, V extends Comparable<? super V>> List<Entry<K, V>> sort(Map<K, V> map) {
List<Map.Entry<K, V>> list = new LinkedList<Map.Entry<K, V>>(map.entrySet());
Collections.sort(list, new Comparator<Map.Entry<K, V>>() {
public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
return list;
}
Es posible que desee poner la lista en un LinkedHashMap , pero si solo va a iterar sobre él de inmediato, eso es superfluo ...
Dependiendo del contexto, usando java.util.LinkedHashMap<T>
qué recuerda el orden en que se colocan los elementos en el mapa. De lo contrario, si necesita ordenar los valores según su orden natural, le recomendaría mantener una Lista separada que se pueda ordenar a través Collections.sort()
.
Esto es demasiado complicado. Los mapas no debían hacer tal trabajo como clasificarlos por Valor. La forma más fácil es crear su propia clase para que se ajuste a sus necesidades.
En el ejemplo inferior, se supone que debe agregar un comparador de TreeMap en el lugar donde está *. Pero mediante la API de Java, proporciona solo claves de comparación, no valores. Todos los ejemplos indicados aquí se basan en 2 mapas. Un hash y un nuevo árbol. Lo cual es extraño.
El ejemplo:
Map<Driver driver, Float time> map = new TreeMap<Driver driver, Float time>(*);
Así que cambia el mapa en un conjunto de esta manera:
ResultComparator rc = new ResultComparator();
Set<Results> set = new TreeSet<Results>(rc);
Crearás clase Results
,
public class Results {
private Driver driver;
private Float time;
public Results(Driver driver, Float time) {
this.driver = driver;
this.time = time;
}
public Float getTime() {
return time;
}
public void setTime(Float time) {
this.time = time;
}
public Driver getDriver() {
return driver;
}
public void setDriver (Driver driver) {
this.driver = driver;
}
}
y la clase comparadora:
public class ResultsComparator implements Comparator<Results> {
public int compare(Results t, Results t1) {
if (t.getTime() < t1.getTime()) {
return 1;
} else if (t.getTime() == t1.getTime()) {
return 0;
} else {
return -1;
}
}
}
De esta manera puede agregar fácilmente más dependencias.
Y como último punto agregaré un iterador simple:
Iterator it = set.iterator();
while (it.hasNext()) {
Results r = (Results)it.next();
System.out.println( r.getDriver().toString
//or whatever that is related to Driver class -getName() getSurname()
+ " "
+ r.getTime()
);
}
Importante problema. Si usa la primera respuesta (Google lo lleva aquí), cambie el comparador para agregar una cláusula igual, de lo contrario no podrá obtener los valores del mapa ordenado por teclas:
public int compare(String a, String b) {
if (base.get(a) > base.get(b)) {
return 1;
} else if (base.get(a) < base.get(b)){
return -1;
}
return 0;
// returning 0 would merge keys
}
Ya hay muchas respuestas para esta pregunta, pero ninguna me proporcionó lo que estaba buscando, una implementación de mapa que devuelve claves y entradas ordenadas por el valor asociado, y mantiene esta propiedad como claves y los valores se modifican en el mapa. Otras other questions hacen esto específicamente.
Cociné un ejemplo genérico que resuelve este caso de uso. Esta implementación no respeta todos los contratos de la interfaz del Mapa, como reflejar cambios de valor y eliminaciones en los conjuntos devueltos desde keySet () y entrySet () en el objeto original. Sentí que tal solución sería demasiado grande para incluirla en una respuesta de desbordamiento de pila. Si logro crear una implementación más completa, tal vez la publique en Github y luego en el enlace en una versión actualizada de esta respuesta.
import java.util.*;
/**
* A map where {@link #keySet()} and {@link #entrySet()} return sets ordered
* by associated values based on the the comparator provided at construction
* time. The order of two or more keys with identical values is not defined.
* <p>
* Several contracts of the Map interface are not satisfied by this minimal
* implementation.
*/
public class ValueSortedMap<K, V> extends HashMap<K, V> {
protected Map<V, Collection<K>> valueToKeysMap;
// uses natural order of value object, if any
public ValueSortedMap() {
this((Comparator<? super V>) null);
}
public ValueSortedMap(Comparator<? super V> valueComparator) {
this.valueToKeysMap = new TreeMap<V, Collection<K>>(valueComparator);
}
public boolean containsValue(Object o) {
return valueToKeysMap.containsKey(o);
}
public V put(K k, V v) {
V oldV = null;
if (containsKey(k)) {
oldV = get(k);
valueToKeysMap.get(oldV).remove(k);
}
super.put(k, v);
if (!valueToKeysMap.containsKey(v)) {
Collection<K> keys = new ArrayList<K>();
keys.add(k);
valueToKeysMap.put(v, keys);
} else {
valueToKeysMap.get(v).add(k);
}
return oldV;
}
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
public V remove(Object k) {
V oldV = null;
if (containsKey(k)) {
oldV = get(k);
super.remove(k);
valueToKeysMap.get(oldV).remove(k);
}
return oldV;
}
public void clear() {
super.clear();
valueToKeysMap.clear();
}
public Set<K> keySet() {
LinkedHashSet<K> ret = new LinkedHashSet<K>(size());
for (V v : valueToKeysMap.keySet()) {
Collection<K> keys = valueToKeysMap.get(v);
ret.addAll(keys);
}
return ret;
}
public Set<Map.Entry<K, V>> entrySet() {
LinkedHashSet<Map.Entry<K, V>> ret = new LinkedHashSet<Map.Entry<K, V>>(size());
for (Collection<K> keys : valueToKeysMap.values()) {
for (final K k : keys) {
final V v = get(k);
ret.add(new Map.Entry<K,V>() {
public K getKey() {
return k;
}
public V getValue() {
return v;
}
public V setValue(V v) {
throw new UnsupportedOperationException();
}
});
}
}
return ret;
}
}