Ordenar un mapa <Clave, valor> por valores

1636

Soy relativamente nuevo en Java y, a menudo, encuentro que necesito ordenar un Map<Key, Value>valor.

Dado que los valores no son únicos, me encuentro convirtiendo el keySeten un array, y ordenando esa matriz a través de la ordenación de matriz con un comparador personalizado que clasifica el valor asociado con la clave.

hay una manera mas facil?

Lii
fuente
24
Un mapa no está destinado a ser ordenado, sino que se accede rápidamente. Los valores iguales del objeto rompen la restricción del mapa. Use el conjunto de entradas, me gusta List<Map.Entry<...>> list =new LinkedList(map.entrySet())y Collections.sort ....así.
Hannes
1
Un caso en el que esto podría surgir cuando intentamos hacer uso de un contador en Java (Map <Object, Integer>). Ordenar por número de ocurrencias sería una operación común. Un lenguaje como Python tiene una estructura de datos de contador incorporada. Para una forma alternativa de implementación en Java, aquí hay un ejemplo
demongolem
77
Hay muchos casos de uso para mapas ordenados, por eso tienes TreeMap y ConcurrentSkipListMap en jdk.
alobodzk
1
TreeMap y ConcurrentSkipListMap ordenar por clave. La pregunta es sobre ordenar por valor.
Peter

Respuestas:

901

Aquí hay una versión genérica amigable:

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;
    }
}
Carter Page
fuente
10
Me alegra que esto ayude. John, LinkedHashMap es importante para la solución, ya que proporciona un orden de iteración predecible.
Carter Página
3
@ buzz3791 Verdadero. Ese será el caso en cualquier algoritmo de clasificación. Cambiar el valor de los nodos en una estructura durante una ordenación crea resultados impredecibles (y casi siempre malos).
Página de Carter el
3
@Sheagorath Lo probé en Android y también funciona. No es un problema específico de la plataforma, teniendo en cuenta que está utilizando la versión Java 6. ¿Ha implementado Comparable correctamente en su objeto de valor?
saiyancoder
66
¿No debería usar la versión Java 8 en forEachOrderedlugar de forEach, ya que los documentos de forEachestados: "El comportamiento de esta operación es explícitamente no determinista"?
robar
1
rasgué totalmente esto, pero acredité @CarterPage en los comentarios (de todos modos estará en un proyecto de código abierto). muchas gracias.
Nathan Beach
420

Nota IMPORTANTE:

Este código puede romperse de múltiples maneras. Si tiene la intención de utilizar el código proporcionado, asegúrese de leer los comentarios también para estar al tanto de las implicaciones. Por ejemplo, los valores ya no pueden ser recuperados por su clave. ( getsiempre regresa 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}
usuario157196
fuente
18
Ya no más ( stackoverflow.com/questions/109383/… ). Además, ¿por qué había un elenco para Doblar? ¿No debería ser así return ((Comparable)base.get(a).compareTo(((Comparable)base.get(b)))?
Stephen
12
@Stephen: No. En este caso, todas las teclas iguales por valor se eliminan (diferencia entre iguales y comparación por referencia). Además: incluso este código tiene problemas con la siguiente secuencia map.put("A","1d");map.put("B","1d");map.put("C",67d);map.put("D",99.5d);
steffen
43
El comparador utilizado para el mapa de árbol es inconsistente con iguales (ver el Javamap de sortMap). Esto significa que retirar elementos del mapa de árbol no funcionará. sorted_map.get ("A") devolverá nulo. Eso significa que este uso de treemap está roto.
mR_fr0g
87
En caso de que no esté claro para las personas: esta solución probablemente no hará lo que desea si tiene la asignación de múltiples claves al mismo valor: solo una de esas claves aparecerá en el resultado ordenado.
Maxy-B
63
Louis Wasserman (sí, uno de los chicos de Google Guava), en realidad no le gusta esta respuesta: "Se rompe de varias maneras realmente confusas si lo miras divertido. Si el mapa de respaldo cambia, se romperá. Si hay varias teclas se asignará al mismo valor, se interrumpirá. Si llama a obtener una clave que no está en el mapa de respaldo, se interrumpirá. Si hace algo que pueda causar una búsqueda en una clave que no está en el mapa, una llamada de Map.equals, contiene clave, cualquier cosa, se romperá con rastros de pila realmente extraños ". plus.google.com/102216152814616302326/posts/bEQLDK712MJ
haylem
339

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 flujo para consumir los datos. Por ejemplo, si desea los 10 mejores 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 imprima a System.out:

map.entrySet().stream()
   .sorted(Map.Entry.comparingByValue())
   .forEach(System.out::println);
Brian Goetz
fuente
Bien, pero ¿qué pasa con el uso de parallelStream()en este caso?
Benj
11
Funcionará en paralelo, sin embargo, es posible que el costo de combinar mapas para combinar los resultados parciales sea demasiado costoso y que la versión paralela no funcione tan bien como cabría esperar. Pero funciona y produce la respuesta correcta.
Brian Goetz
Gracias por tu útil consejo. Era exactamente lo que me preguntaba, aunque depende del tipo de clave que use y de tantos parámetros ... Lo importante es "funciona y produce la respuesta correcta".
Benj
2
¿no tienes que usar compareByValue en el ejemplo de top10?
Leo
1
@Benj funcionará en términos de extracción del top 10, pero el mapa resultante ya no se ordenará.
OrangeDog
211

Tres respuestas de 1 línea ...

Me gustaría utilizar Google Colecciones de guayaba para hacer esto - si sus valores son Comparableentonces usted puede utilizar

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

Lo cual 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 deberá hacer algo similar a

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

Estos pueden aplicarse a un TreeMap (como se Orderingextiende Comparator), o un LinkedHashMap después de ordenarlos

NB : Si va a utilizar un TreeMap, recuerde que si se compara == 0, el elemento ya está en la lista (lo que sucederá si tiene varios valores que comparan lo mismo). Para aliviar esto, puede agregar su clave al comparador de esta manera (suponiendo que sus claves y valores sean Comparable):

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

= Aplicar ordenamiento natural al valor mapeado por la clave, y combinarlo con el ordenamiento natural de la clave

Tenga en cuenta que esto aún no funcionará si sus claves se comparan con 0, pero esto debería ser suficiente para la mayoría de los comparableelementos (como hashCode, equalsy a compareTomenudo están sincronizados ...)

Consulte Ordering.onResultOf () y Functions.forMap () .

Implementación

Entonces, ahora que tenemos un comparador que hace lo que queremos, necesitamos obtener un resultado.

map = ImmutableSortedMap.copyOf(myOriginalMap, valueComparator);

Ahora esto probablemente funcionará, pero:

  1. debe hacerse con un mapa completo terminado
  2. No intentes los comparadores anteriores en a TreeMap; no tiene sentido intentar comparar una clave insertada cuando no tiene un valor hasta después de la colocación, es decir, se romperá realmente rápido

El punto 1 es un poco decisivo para mí; Las colecciones de Google son increíblemente perezosas (lo cual es bueno: puedes hacer casi todas las operaciones en un instante; el trabajo real se hace cuando comienzas a usar el resultado), ¡y esto requiere copiar un mapa completo !

Respuesta "completa" / Mapa ordenado en vivo por valores

Sin embargo, no te preocupes; Si estaba lo suficientemente obsesionado con tener un mapa "en vivo" ordenado de esta manera, podría resolver no 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 necesita un HashMap interno para buscar los valores sin crear un bucle infinito entre 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 lo ponemos, nos aseguramos de que el mapa hash tenga el valor para el comparador, y luego lo colocamos en el TreeSet para ordenarlo. 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 == 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 invertir 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));
Stephen
fuente
Hola @Stephen, ¿puedes dar un ejemplo de cómo usar Ordering? ¡Miro el código fuente de Ordering y no puedo entender qué devuelve .natural (). OnResultOf (...)! El código fuente es "public <F> Ordering <F> onResultOf", ¡ni siquiera sé cómo se compila! Lo más importante, ¿cómo usar "<F> Ordenar <F>" para ordenar un mapa? ¿Es un comparador o algo así? Gracias.
smallufo
OrderingEs simplemente un rico Comparator. He intentado comentar cada ejemplo (las cursivas debajo de cada uno). "natural" indica que los objetos son Comparable; es como el ComparableComparator de apache common. onResultOfaplica una función al elemento que se compara. Entonces, si tuviera una función que agregara 1 a un entero, natural().onResultOf(add1Function).compare(1,2)terminaría haciendo2.compareTo(3)
Stephen
ImmutableSortedMap.copyOf arroja IllegalArgumentException si hay valores duplicados en el mapa original.
lbalazscs
@Ibalazscs Sí, lo hará: debe poder usar ImmutableSetMultiMapo ImmutableListMultiMapcontener la colección de variables duplicadas.
Stephen
1
Gracias por esto, usé tu solución en un proyecto. Sin embargo, creo que hay un problema: para comportarse como un mapa, necesita devolver el valor previamente asociado con la clave, si existe, pero de esta manera nunca funcionará. La solución que utilicé es devolver el valor eliminado si existe.
alex
185

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;
}
devinmoore
fuente
16
La lista a ordenar es "nueva LinkedList"? Caramba. Afortunadamente, Collections.sort () volca la lista en una matriz primero, para evitar precisamente este tipo de error (pero aún así, volcar una ArrayList en una matriz debería ser más rápido que hacer lo mismo para LinkedList).
Dimitris Andreou
No se puede convertir de iterador a TernaryTree.Iterator
Lisak
44
@ gg.kaspersky No estoy diciendo "es malo ordenar una LinkedList", sino que LinkedList en sí es una mala elección aquí, independientemente de la clasificación. Es mucho mejor usar una ArrayList, y para obtener puntos adicionales, dimensione exactamente en map.size (). Consulte también code.google.com/p/memory-measurer/wiki/… costo promedio por elemento en ArrayList: 5 bytes costo promedio por elemento en LinkedList: 24 bytes. Para una ArrayList de tamaño exacto, el costo promedio sería de 4 bytes. Es decir, LinkedList toma SEIS veces la cantidad de memoria que necesita ArrayList. Es solo hinchazón
Dimitris Andreou
2
El uso de los valores anteriores se ha ordenado en orden ascendente. ¿Cómo ordenar en descendente?
ram
1
Reemplace o1 y o2 para ordenar descendente.
Soheil
68

Con Java 8, puede usar la API de secuencias 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));
asilias
fuente
¿Cómo ordenarlo en orden inverso?
Vlad Holubiev
66
encontró una solución -Collections.reverseOrder(comparing(Entry::getValue))
Vlad Holubiev
1
Creo que veo un error tipográfico allí, ¿no debería llamarse "toMap" como "Collectors.toMap ()"?
Jake Stokes
1
@JakeStokes O use una importación estática :-)
assylias
66
Una mejor manera de ordenar por valor de entrada en orden inverso es:Entry.comparingByValue(Comparator.reverseOrder())
Gediminas Rimsa
31

Ordenar las claves requiere que el Comparador busque cada valor para cada comparación. Una solución más escalable usaría el entrySet directamente, ya que el valor estaría disponible de inmediato para cada comparación (aunque no he respaldado esto por 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. La primera ArrayList creada 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 ser reasignado 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...

volley
fuente
La segunda versión puede ser más concisa si devuelve List <Map.Entry <K, V >> Esto también hace que sea más fácil iterar y obtener las claves y los valores sin tener que hacer muchos extra extra en el mapa. Todo esto supone que está bien con que este código no sea seguro para subprocesos. Si el mapa de respaldo o la lista ordenada se comparten en un entorno multiproceso, todas las apuestas están desactivadas.
Mike Miller
26

La biblioteca commons-collections contiene una solución llamada TreeBidiMap . O bien, puede echar un vistazo a la API de Google Collections. Tiene TreeMultimap que puedes usar.

Y si no desea utilizar este marco ... vienen con código fuente.

p3t0r
fuente
No tiene que usar la colección commons. Java viene con su propio java.util.TreeMap.
yoliho
2
sí, pero TreeMap es mucho menos flexible cuando se ordena en la parte de valor de los mapentries.
p3t0r
99
El problema con BidiMap es que agrega una restricción de relación 1: 1 entre claves y valores para hacer que la relación sea invertible (es decir, tanto las claves como los valores deben ser únicos). Esto significa que no puede usar esto para almacenar algo como un objeto de conteo de palabras ya que muchas palabras tendrán el mismo conteo.
Doug
26

He examinado 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 del valor más alto al más bajo.

Anthony
fuente
66
PROBLEMA: si desea usar el mapa devuelto más tarde, por ejemplo, para verificar si contiene un elemento determinado, ¡siempre obtendrá falso, debido a su comparador personalizado! Una posible solución: reemplace la última línea con: return new LinkedHashMap <K, V> (sortedByValues);
Erel Segal-Halevi
Esto me parece una solución limpia, excepto el hecho de que @ErelSegalHalevi señaló que verificar si los valores existen en el Mapa no será posible ya que especificó el comparador. map.put ("1", "One"); map.put ("2", "Dos"); map.put ("3", "Tres"); map.put ("4", "Cuatro"); map.put ("5", "Cinco"); map.containsKey ("1") siempre devolverá falso, si devuelve un nuevo objeto en la función sortByValues ​​() como return new TreeMap <K, V> (sortedByValues); resuelve el problema Gracias Abhi
abhi
más o menos lo mismo que la respuesta de user157196 y Carter Page. La página de Carter contiene la solución LinkedHashMap
Kirby
La cuarta línea de la solución debe ser int compare = map.get (k1) .compareTo (map.get (k2)); si necesita orden ascendente
www.Decompiler.com
19

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 se ordenan por sus valores utilizando el comparador dado. Alternativamente, si sus valores son mutuamente comparables, 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 al otro. Para una vista en vivo iterable 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 repite, por lo que, salvo modificaciones simultáneas, siempre reflejará el estado actual del mapa.

revs gdejohn
fuente
Esto devuelve una Lista de entradas en lugar de un mapa ordenado por valor. Otra versión que devuelve un mapa: stackoverflow.com/a/22132422/829571
assylias
17

Cree un comparador personalizado y úselo 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}
Sujan Reddy A
fuente
En el caso de que los valores sean iguales, cambié la línea "return 1" para comparar las claves: "return ((String) o1) .compareTo ((String) o2);"
gjgjgj
14

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, de la que puede obtener las claves y los valores.

Lyudmil
fuente
Este método es mucho más fácil y más intuitivo que crear un objeto Mapa <V, Lista <K>> con el mismo efecto. En realidad, no se supone que los valores sean claves en un objeto Map, lo que realmente está buscando es una lista en esta situación, en mi humilde opinión.
Jeff Wu
Esta solución no funciona con muchos valores, se atornilló con mis recuentos (el valor asociado con cada clave)
Sam Levin
1
Eso es extraño. ¿Podrías dar más detalles? ¿Cuál fue su salida y cuál fue la salida que esperaba?
Lyudmil
12

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));
    }
}
RoyalBigorno
fuente
11

La respuesta más votada no funciona cuando tienes 2 ítems iguales. TreeMap deja valores iguales.

el ejemplo: mapa sin clasificar

clave / valor: D / 67.3
clave / valor: A / 99.5
clave / valor: B / 67.4
clave / valor: C / 67.5
clave / valor: E / 99.5

resultados

clave / valor: A / 99.5
clave / valor: C / 67.5
clave / valor: B / 67.4
clave / valor: D / 67.3

Así que deja de lado E !!

Para mí funcionó bien para ajustar el comparador, si es igual no devuelve 0 sino -1.

en el ejemplo:

La clase ValueComparator implementa el Comparador {

Base del mapa; Public ValueComparator (base del mapa) {this.base = base; }

public int compare (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:

clave / valor: D / 67.3
clave / valor: A / 99.5
clave / valor: B / 67.4
clave / valor: C / 67.5
clave / valor: E / 99.5

resultados:

clave / valor: A / 99.5
clave / valor: E / 99.5
clave / valor: C / 67.5
clave / valor: B / 67.4
clave / valor: D / 67.3

como respuesta a Aliens (22 de noviembre de 2011): Estoy usando esta solución para un mapa de Id. y nombres de 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 darle el código correcto), este es el código para una clasificación de Mapa, 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 probarla, y esto funciona para Integer, String Map:

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 caso de prueba 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 que esto sea mucho más genérico, pero solo lo necesitaba para 1 caso (el Mapa)

revs michel.iamit
fuente
tenías razón, ¡hubo un error en el código que di al principio! Espero que mi edición reciente te ayude.
michel.iamit
9

En lugar de usar Collections.sortcomo algunos sugiero usar Arrays.sort. En realidad, lo que Collections.sorthace 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 toArraya la lista y luego usa Arrays.sort. De esta manera, todas las entradas del mapa se copiarán tres veces: una vez del mapa a la lista temporal (ya sea LinkedList o ArrayList), luego a la matriz temporal y finalmente al nuevo mapa.

Mi solución omite este paso ya que no crea LinkedList innecesaria. 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;
}
ciamej
fuente
8

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 en el aire cómo manejar nulos.

Una ventaja importante de este enfoque es que en realidad devuelve un Mapa, a diferencia de algunas de las otras soluciones que se ofrecen aquí.

Roger
fuente
Es incorrecto, mi método funciona si hay valores duplicados. Lo he usado con mapas que tienen más de 100 claves con "1" como valor.
Anthony
8

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
Nilesh Jadav
fuente
7

Problema importante. Si usa la primera respuesta (Google lo lleva aquí), cambie el comparador para agregar una cláusula igual; de lo contrario, no podrá obtener valores del sorted_map 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
    }
cuneyt
fuente
Ahora, cuando agregue dos entradas con valores iguales, se fusionarán, solo debe devolver 0 si está seguro de que los objetos son iguales (igual)
Masood_mj
7

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 a medida que las claves y los valores se modifican en el mapa. Otras dos preguntas piden esto específicamente.

Preparé un ejemplo amistoso genérico que resuelve este caso de uso. Esta implementación no cumple con todos los contratos de la interfaz Map, como reflejar los cambios de valor y las eliminaciones en los conjuntos devueltos por 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, quizás la publique en Github y luego la 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;
    }
}
David Bleckmann
fuente
Si Comparable y Comparator no están permitidos, ¿cómo hacerlo?
Ved Prakash
No estoy seguro si entiendo su caso de uso, tal vez pueda dar más detalles. Si el objeto que desea utilizar como valor no es Comparable, deberá convertirlo en un objeto que sí lo sea.
David Bleckmann
6

Entrada tardía.

Con el advenimiento de Java-8, podemos usar flujos para la manipulación de datos de una manera muy fácil y sucinta. Puede usar secuencias para ordenar las entradas del mapa por valor y crear un LinkedHashMap que conserve la iteración del orden de inserción .

P.ej:

LinkedHashMap sortedByValueMap = map.entrySet().stream()
                .sorted(comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey))     //first sorting by Value, then sorting by Key(entries with same value)
                .collect(LinkedHashMap::new,(map,entry) -> map.put(entry.getKey(),entry.getValue()),LinkedHashMap::putAll);

Para ordenar en reversa, reemplace:

comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey)

con

comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey).reversed()
rev. Pankaj Singhal
fuente
Gracias por esta versión comentada. Una pregunta: ¿Cuál es la diferencia de usar Entry.comparingByValue()(como la respuesta de asylias anterior stackoverflow.com/a/22132422/1480587 ) o la comparing(Entry<Key,Value>::getValue).thenComparing(Entry::getKey)que usó? Entiendo que también compara claves si los valores son idénticos, ¿verdad? Noté que la ordenación mantiene el orden de los elementos con el mismo valor, por lo que ¿es necesaria la ordenación por claves si las claves ya están ordenadas antes?
Peter T.
6

Mapa dado

   Map<String, Integer> wordCounts = new HashMap<>();
    wordCounts.put("USA", 100);
    wordCounts.put("jobs", 200);
    wordCounts.put("software", 50);
    wordCounts.put("technology", 70);
    wordCounts.put("opportunity", 200);

Ordenar el mapa en función del valor en orden ascendente

Map<String,Integer>  sortedMap =  wordCounts.entrySet().
                                                stream().
                                                sorted(Map.Entry.comparingByValue()).
        collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
    System.out.println(sortedMap);

Ordenar el mapa según el valor en orden de desecho

Map<String,Integer>  sortedMapReverseOrder =  wordCounts.entrySet().
            stream().
            sorted(Map.Entry.comparingByValue(Comparator.reverseOrder())).
            collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
    System.out.println(sortedMapReverseOrder);

Salida:

{software = 50, tecnología = 70, EE. UU. = 100, trabajos = 200, oportunidad = 200}

{empleos = 200, oportunidad = 200, EE. UU. = 100, tecnología = 70, software = 50}

Arpan Saini
fuente
Supongo que map-reduce realmente lo "redujo" ... buena solución.
ha9u63ar
gracias @ ha9u63ar
Arpan Saini
Funciona pero no entiendo cómo el orden de los elementos entra en juego en un HashMap.
Ali Tou
5

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 en función de su orden natural, recomendaría mantener una lista separada que se pueda ordenar a través de Collections.sort().

Ryan Delucchi
fuente
No veo por qué esto fue -1, hasta ahora LinkedHashMap es probablemente la mejor solución para mí, solo estoy tratando de descubrir lo costoso que es tirar y crear un nuevo LinkedHashMap.
NobleUplift
5

Como TreeMap <> no funciona para valores que pueden ser iguales, utilicé 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, es superfluo ...

malix
fuente
es correcto, pero su comparador no maneja el caso de valores iguales
Sebastien Lorber
5

Esto es demasiado complicado. Se suponía que los mapas no harían tal trabajo como ordenarlos 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 a TreeMap un comparador en el lugar donde está *. Pero mediante la API de Java solo proporciona 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 cambie el mapa a 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 Comparator:

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

Basado en el código @devinmoore, un método de clasificación de mapas que utiliza genéricos y admite el orden ascendente y 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
}
Maxim Veksler
fuente
Por otra parte, tal vez una mejor solución sería usar un mapa de auto clasificación, en el caso de usar org.apache.commons.collections.bidimap.TreeBidiMap
Maxim Veksler
4

Aquí hay una solución OO (es decir, no utiliza staticmé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.

Dave Jarvis
fuente
4

Afaik, la forma más limpia es utilizando colecciones para ordenar el mapa según el 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());
  }
}
Lisak
fuente
4

Algunos cambios simples para tener un mapa ordenado con pares que tengan valores duplicados. En el método de comparación (clase ValueComparator) cuando los valores son iguales, no devuelve 0 sino que devuelve el resultado de comparar las 2 claves. Las claves son distintas en un mapa, por lo que puede mantener valores duplicados (que por cierto se ordenan por claves). Entonces, el ejemplo anterior podría modificarse 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;
        }
      }
    }
dimkar
fuente
4

Seguro que 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 ejecutivo: 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

Sebastien Lorber
fuente
3

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 descendente cuando se repita. Para cambiar el orden, simplemente cambie> a <en la instrucción if.

No es el tipo más rápido, pero hace el trabajo sin ninguna dependencia adicional.

nibor
fuente