TreeMap ordenar por valor

10 minutos de lectura

TreeMap ordenar por valor
vito huang

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

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

import java.util.*;

class treeMap {
    public static void main(String[] args) {
        System.out.println("the main");
        byValue cmp = new byValue();
        Map<String, Integer> map = new TreeMap<String, Integer>(cmp);
        map.put("de",10);
        map.put("ab", 20);
        map.put("a",5);

        for (Map.Entry<String,Integer> pair: map.entrySet()) {
            System.out.println(pair.getKey()+":"+pair.getValue());
        }
    }
}

class byValue implements Comparator<Map.Entry<String,Integer>> {
    public int compare(Map.Entry<String,Integer> e1, Map.Entry<String,Integer> e2) {
        if (e1.getValue() < e2.getValue()){
            return 1;
        } else if (e1.getValue() == e2.getValue()) {
            return 0;
        } else {
            return -1;
        }
    }
}

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

  • quizás esto te ayude stackoverflow.com/questions/109383/…

    – Inv3r53

    19 de mayo de 2010 a las 10:59

  • posible duplicado de ¿Cómo ordenar un mapa de árbol según sus valores?

    – nandeesh

    18 de agosto de 2012 a las 15:41

  • Ver también: stackoverflow.com/questions/30425836/…

    – Pablo Jackson

    29/01/2016 a las 20:24

1646757131 332 TreeMap ordenar por valor
poligenelubricantes

no puedes tener el TreeMap sí mismo ordena los valores, ya que eso desafía la SortedMap especificación:

A Map que además proporciona una pedidos totales en sus teclas.

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

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

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

Ahora puedes hacer lo siguiente:

    Map<String,Integer> map = new TreeMap<String,Integer>();
    map.put("A", 3);
    map.put("B", 2);
    map.put("C", 1);   

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

Tenga en cuenta que sucederán cosas extrañas si intenta modificar el SortedSet mismo, o el Map.Entry dentro, porque esto ya no es una “vista” del mapa original como entrySet() es.

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


Nota sobre == por Integer

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

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

Preguntas relacionadas

  • Al comparar dos enteros en Java, ¿se produce el desempaquetado automático? (¡¡¡NO!!!)
  • ¿Está garantizado que new Integer(i) == i en Java? (¡¡¡SÍ!!!)

  • si agrega map.put(“D”, 2); el resultado sigue siendo “[C=1, B=2, A=3]” y no “[C=1, B=2, D=2, A=3]”

    – Ígor Milla

    21 de abril de 2011 a las 8:49

  • Corrección: int res = e2.getValue().compareTo(e1.getValue()); devolver res != 0 ? resolución: 1;

    – beshkenadze

    24 de febrero de 2012 a las 6:44


  • nuevo entero (0) == nuevo entero (0) ¡Usted compara una referencia a un objeto y crea dos objetos! La salida es absolutamente correcta y predecible.

    – Yuriy Chernyshov

    12 de junio de 2014 a las 20:17

  • “En términos generales, la necesidad de ordenar las entradas de un mapa por sus valores es atípica” Soy un principiante aquí, pero creo que debería ser algo muy común. Por ejemplo, si quiero recuperar las 3 personas más antiguas en un mapa {“ana”=37, “peter”=43, “john”=4, “mary”=15, “Matt”=78}

    – fartagaintuxedo

    9 de julio de 2015 a las 11:50


  • @igormilla Hay una razón simple por la que su código funciona: Autoboxing usa Integer.valueOf. ¡Y eso tiene un caché de instancias de -128 a 127!

    – bromista

    24 de agosto de 2018 a las 6:14

La respuesta de lubricantes poligenéticos es casi Perfecto. Sin embargo, tiene un error importante. No manejará las entradas del mapa donde los valores son los mismos.

Este código:…

Map<String, Integer> nonSortedMap = new HashMap<String, Integer>();
nonSortedMap.put("ape", 1);
nonSortedMap.put("pig", 3);
nonSortedMap.put("cow", 1);
nonSortedMap.put("frog", 2);

for (Entry<String, Integer> entry  : entriesSortedByValues(nonSortedMap)) {
    System.out.println(entry.getKey()+":"+entry.getValue());
}

Daría salida:

ape:1
frog:2
pig:3

Note como nuestra vaca desapareció al compartir el valor “1” con nuestro simio :O!

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

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

  • -1. AFASIK no Set implementación contienen un elemento más de una vez. Acabas de violar esa restricción. Desde el SortedSet API: Tenga en cuenta que el orden mantenido por un conjunto ordenado debe ser consistente con iguales. La solución sería cambiar a un List implementación.

    – dacwe

    12 de diciembre de 2012 a las 17:42


  • @dacwe valores iguales no claves

    – Bellum

    8 de enero de 2013 a las 13:45

  • @bellum: Estamos hablando de Setestá aquí. Desde el Comparator viola el Set contrato Set.remove, Set.contains etc. no funciona! Mira esto ejemplo en ideone.

    – dacwe

    8 de enero de 2013 a las 15:47

  • Solo una nota, si cambias int res= e1.getValue().compareTo(e2.getValue()); dentro int res= e2.getValue().compareTo(e1.getValue());tiene un orden de valores descendente en lugar de ascendente.

    – tugcem

    15 de marzo de 2013 a las 9:55


  • prefiero volver res != 0 ? res : e1.getKey().compareTo(e2.getKey()) para conservar el orden de las claves con valores iguales.

    – Marco Lackovic

    1 mayo 2013 a las 15:53

1646757131 231 TreeMap ordenar por valor
Vitali Fedorenko

En Java 8:

LinkedHashMap<Integer, String> sortedMap = map.entrySet().stream()
  .sorted(Map.Entry.comparingByValue(/* Optional: Comparator.reverseOrder() */))
  .collect(Collectors.toMap(Map.Entry::getKey,
                            Map.Entry::getValue,
                            (e1, e2) -> e1, LinkedHashMap::new));

A TreeMap es siempre ordenados por las claves, cualquier otra cosa es imposible. A Comparator simplemente le permite controlar cómo las claves están ordenadas.

Si desea los valores ordenados, debe extraerlos en un List y ordenar eso.

Esto no se puede hacer usando un Comparatorya que siempre obtendrá el llave del mapa para comparar. TreeMap sólo puede ordenar por la clave.

  • si TreeMap solo pasa la clave a Comparator, ¿sería factible si hago una referencia de TreeMap en el constructor del comparador y luego uso la clave para obtener el valor, algo como esto (no estoy seguro de cómo pasar por referencia): class byValue implementa Comparator { TreeMap theTree; public byValue(TreeMap theTree) { this.theTree = theTree; } public int compare(String k1, String k2) { //use el método getKey de TreeMap para el valor } }

    – vito huang

    19 de mayo de 2010 a las 11:29

  • @vito: no, porque generalmente una de las dos claves aún no estará en el mapa y no podrá obtener su valor.

    – Joaquín Sauer

    19 mayo 2010 a las 11:31

  • ¿No es este un ejemplo de hacer esto dentro del Comparador de TreeMap? (aunque, como se mencionó anteriormente, esto rompe la especificación para SortedMap que especifica la ordenación por claves) principiantesbook.com/2014/07/…

    – Marco

    13 de septiembre de 2018 a las 16:06


  • @Marcus: eso Comparator usa un existente map para obtener los valores por los que ordenar. En otras palabras, no puede ordenar valores arbitrarios puestos en el TreeMap más tarde, sólo los valores que ya están en el Mapa original.

    – Joaquín Sauer

    18 de noviembre de 2019 a las 9:46

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

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

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

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

  • si TreeMap solo pasa la clave a Comparator, ¿sería factible si hago una referencia de TreeMap en el constructor del comparador y luego uso la clave para obtener el valor, algo como esto (no estoy seguro de cómo pasar por referencia): class byValue implementa Comparator { TreeMap theTree; public byValue(TreeMap theTree) { this.theTree = theTree; } public int compare(String k1, String k2) { //use el método getKey de TreeMap para el valor } }

    – vito huang

    19 de mayo de 2010 a las 11:29

  • @vito: no, porque generalmente una de las dos claves aún no estará en el mapa y no podrá obtener su valor.

    – Joaquín Sauer

    19 mayo 2010 a las 11:31

  • ¿No es este un ejemplo de hacer esto dentro del Comparador de TreeMap? (aunque, como se mencionó anteriormente, esto rompe la especificación para SortedMap que especifica la ordenación por claves) principiantesbook.com/2014/07/…

    – Marco

    13 de septiembre de 2018 a las 16:06


  • @Marcus: eso Comparator usa un existente map para obtener los valores por los que ordenar. En otras palabras, no puede ordenar valores arbitrarios puestos en el TreeMap más tarde, sólo los valores que ya están en el Mapa original.

    – Joaquín Sauer

    18 de noviembre de 2019 a las 9:46

TreeMap ordenar por valor
pablo jackson

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

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

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad