Como classificar os valores do mapa por chave em Java?

362

Eu tenho um mapa que possui seqüências de caracteres para chaves e valores.

Os dados são como a seguir:

"pergunta1", "1"
"pergunta9", "1"
"pergunta2", "4"
"pergunta5", "2"

Quero classificar o mapa com base em suas chaves. Então, no final, terei question1, question2, question3... e assim por diante.


Eventualmente, estou tentando obter duas seqüências de caracteres deste mapa.

  • Primeira seqüência: perguntas (na ordem 1 ..10)
  • Segunda String: Respostas (na mesma ordem que a pergunta)

No momento, tenho o seguinte:

Iterator it = paramMap.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry pairs = (Map.Entry) it.next();
    questionAnswers += pairs.getKey() + ",";
}

Isso me faz as perguntas em uma sequência, mas elas não estão em ordem.

n00bstackie
fonte

Respostas:

613

Resposta curta

Use a TreeMap. É exatamente para isso que serve.

Se esse mapa for passado para você e você não puder determinar o tipo, poderá fazer o seguinte:

SortedSet<String> keys = new TreeSet<>(map.keySet());
for (String key : keys) { 
   String value = map.get(key);
   // do something
}

Isso irá percorrer o mapa na ordem natural das chaves.


Resposta mais longa

Tecnicamente, você pode usar qualquer coisa que implemente SortedMap, mas, exceto em casos raros, isso equivale a TreeMap, assim como o uso de uma Mapimplementação normalmente equivale a HashMap.

Para casos em que suas chaves são de um tipo complexo que não implementa Comparable ou você não deseja usar a ordem natural TreeMape TreeSetpossui construtores adicionais que permitem transmitir Comparator:

// placed inline for the demonstration, but doesn't have to be a lambda expression
Comparator<Foo> comparator = (Foo o1, Foo o2) -> {
        ...
    }

SortedSet<Foo> keys = new TreeSet<>(comparator);
keys.addAll(map.keySet());

Lembre-se ao usar um TreeMapou TreeSetque ele terá características de desempenho diferentes de HashMapou HashSet. Grosso modo, as operações que encontrarem ou inserirem um elemento passarão de O (1) para O (Log (N)) .

Em um caso HashMap, mover de 1000 itens para 10.000 não afeta realmente o tempo de pesquisa de um elemento, mas por um TreeMaptempo o tempo de pesquisa será cerca de três vezes mais lento (assumindo o Log 2 ). Passar de 1000 para 100.000 será cerca de 6 vezes mais lento para cada pesquisa de elemento.

Jherico
fonte
Estou tentando usar o Treemap e classificar as chaves String com base no comprimento. Acho que estou obtendo resultados de recuperação inconsistentes. Aparentemente, porque o TreeMap considera um resultado compareTo de 0 como "igual"? Não tenho certeza de como usá-lo neste caso.
Marc
11
O resultado compareTo () de 0 é 'igual'. Se você estiver escrevendo um comparador que classifique por comprimento de sequência, será necessário retornar um valor positivo ou negativo com base em qual sequência for mais longa e retornar apenas 0 se as duas sequências tiverem o mesmo comprimento. se a e b são strings, você pode fazer isso da seguinte maneira: `retorne a.length () - b.length () '(ou inverta os valores, se desejar que eles sejam classificados na outra direção).
Jherico
Olá pessoal, se ele / ela gostaria que o mapa fosse ordenado por chaves, que aqui é 1,2,3,4, qual é a ordem de inserção .... por que não estamos usando o LinkedHashSet? Apenas colocamos as perguntas uma por uma, e elas estão sendo ordenadas pela ordem da inserção. Alguns podem me ajudar com isso?
22815 Karoly
O @Karoly LinkedHashSet funcionaria para recuperar os elementos na ordem em que você os inseriu. O que o OP está querendo é recuperar os elementos em alguma ordem de classificação predeterminada, independentemente da ordem de inserção.
David Berry
@ cricket_007 o código está demonstrando especificamente como iterar nas chaves para um mapa que ainda não está classificado.
Jherico
137

Supondo que o TreeMap não seja bom para você (e supondo que você não possa usar genéricos):

List sortedKeys=new ArrayList(yourMap.keySet());
Collections.sort(sortedKeys);
// Do what you need with sortedKeys.
TrayMan
fonte
3
Obrigado! Eu precisava fazer algo assim, pois minhas chaves eram um tipo complexo.
Ross Hambrick
3
Isso apenas classificará a lista de chaves, mas não classificará o próprio mapa com base nas chaves. Também estou procurando como classificar o mapa com base nas chaves e poderia encontrar uma maneira. Vou ter que tentar com TreeMap eu acho :)
Crenguta S
55

Usando o TreeMapvocê pode classificar o mapa.

Map<String, String> map = new HashMap<>();        
Map<String, String> treeMap = new TreeMap<>(map);
for (String str : treeMap.keySet()) {
    System.out.println(str);
}
Manoj Singh
fonte
11
Mapear <String, Lista <>> treeMap = novo TreeMap <String, Lista <>> (printHashMap); for (String str: treeMap.keySet ()) {System.out.println (str + "" + treeMap.get (str)); }
vikramvi 21/08/16
37

Use um TreeMap !

AgileJon
fonte
31
+1 - Não é ágil o suficiente para derrotar Jherico, Jon, mas ainda é muito bom. 8)
duffymo 28/05
Eu não sei Java :-( Isso funciona 100%. Muito mais simples do que qualquer uma das soluções horríveis que eu vim com
peterchaula
36

Se você já possui um mapa e gostaria de classificá-lo nas teclas, basta usar:

Map<String, String> treeMap = new TreeMap<String, String>(yourMap);

Um exemplo de trabalho completo:

import java.util.HashMap;
import java.util.Set;
import java.util.Map;
import java.util.TreeMap;
import java.util.Iterator;

class SortOnKey {

public static void main(String[] args) {
   HashMap<String,String> hm = new HashMap<String,String>();
   hm.put("3","three");
   hm.put("1","one");
   hm.put("4","four");
   hm.put("2","two");
   printMap(hm);
   Map<String, String> treeMap = new TreeMap<String, String>(hm);
   printMap(treeMap);
}//main

public static void printMap(Map<String,String> map) {
    Set s = map.entrySet();
    Iterator it = s.iterator();
    while ( it.hasNext() ) {
       Map.Entry entry = (Map.Entry) it.next();
       String key = (String) entry.getKey();
       String value = (String) entry.getValue();
       System.out.println(key + " => " + value);
    }//while
    System.out.println("========================");
}//printMap

}//class
MD
fonte
36

Basta usar o TreeMap

new TreeMap<String, String>(unsortMap);

Esteja ciente de que o TreeMap é classificado de acordo com a ordem natural de suas 'chaves'

Aliti
fonte
19

Desde que você não possa usar TreeMap, no Java 8 , podemos usar o método toMap () no Collectorsqual utiliza os seguintes parâmetros:

  • keymapper : função de mapeamento para produzir chaves
  • valuemapper : função de mapeamento para produzir valores
  • mergeFunction : uma função de mesclagem, usada para resolver colisões entre valores associados à mesma chave
  • mapSupplier : uma função que retorna um novo mapa vazio no qual os resultados serão inseridos.

Exemplo de Java 8

Map<String,String> sample = new HashMap<>();  // push some values to map  
Map<String, String> newMapSortedByKey = sample.entrySet().stream()
                    .sorted(Map.Entry.<String,String>comparingByKey().reversed())
                    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
Map<String, String> newMapSortedByValue = sample.entrySet().stream()
                        .sorted(Map.Entry.<String,String>comparingByValue().reversed())
                        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1,e2) -> e1, LinkedHashMap::new));

Podemos modificar o exemplo para usar o comparador personalizado e classificar com base nas chaves como:

Map<String, String> newMapSortedByKey = sample.entrySet().stream()
                .sorted((e1,e2) -> e1.getKey().compareTo(e2.getKey()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1,e2) -> e1, LinkedHashMap::new));
akhil_mittal
fonte
só funciona no Android com Api 24 e superior.
Tina
10

Usando o Java 8:

Map<String, Integer> sortedMap = unsortMap.entrySet().stream()
            .sorted(Map.Entry.comparingByKey())
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                    (oldValue, newValue) -> oldValue, LinkedHashMap::new));
Taras Melnyk
fonte
5

Esse código pode classificar um mapa de valores-chave em ambas as ordens, por exemplo, crescente e decrescente.

<K, V extends Comparable<V>> Map<K, V> sortByValues
     (final Map<K, V> map, int ascending)
{
     Comparator<K> valueComparator =  new Comparator<K>() {         
        private int ascending;
        public int compare(K k1, K k2) {
            int compare = map.get(k2).compareTo(map.get(k1));
            if (compare == 0) return 1;
            else return ascending*compare;
        }
        public Comparator<K> setParam(int ascending)
        {
            this.ascending = ascending;
            return this;
        }
    }.setParam(ascending);

    Map<K, V> sortedByValues = new TreeMap<K, V>(valueComparator);
    sortedByValues.putAll(map);
    return sortedByValues;
}

Como um exemplo:

Map<Integer,Double> recommWarrVals = new HashMap<Integer,Double>();
recommWarrVals = sortByValues(recommWarrVals, 1);  // Ascending order
recommWarrVals = sortByValues(recommWarrVals,-1);  // Descending order
M. Mashaye
fonte
5

No Java 8

Para classificar uma Map<K, V>por chave, coloque as chaves em List<K>:

List<K> result = map.keySet().stream().sorted().collect(Collectors.toList());

Para classificar uma Map<K, V>por chave, coloque as entradas em List<Map.Entry<K, V>>:

List<Map.Entry<K, V>> result =
    map.entrySet()
       .stream()
       .sorted(Map.Entry.comparingByKey())
       .collect(Collectors.toList());

Por último, mas não menos importante: para classificar strings de maneira sensível ao código do idioma - use uma classe Collator (comparador):

Collator collator = Collator.getInstance(Locale.US);
collator.setStrength(Collator.PRIMARY); // case insensitive collator

List<Map.Entry<String, String>> result =
    map.entrySet()
       .stream()
       .sorted(Map.Entry.comparingByKey(collator))
       .collect(Collectors.toList());
Oleksandr Pyrohov
fonte
1
List<String> list = new ArrayList<String>();
Map<String, String> map = new HashMap<String, String>();
for (String str : map.keySet()) {
 list.add(str);
}
Collections.sort(list);
for (String str : list) {
 System.out.println(str);
}
Manoj Singh
fonte
1

No Java 8, você também pode usar .stream (). Sorted ():

myMap.keySet().stream().sorted().forEach(key -> {
        String value = myMap.get(key);

        System.out.println("key: " + key);
        System.out.println("value: " + value);
    }
);
Jonas_Hess
fonte
0

Também podemos classificar a chave usando o método Arrays.sort.

Map<String, String> map = new HashMap<String, String>();
Object[] objArr = new Object[map.size()];
for (int i = 0; i < map.size(); i++) {
objArr[i] = map.get(i);
}
Arrays.sort(objArr);
for (Object str : objArr) {
System.out.println(str);
}
Manoj Singh
fonte
Esta é a classificação por valor e não chave.
raaj
0

Apenas no caso de você não querer usar um TreeMap

public static Map<Integer, Integer> sortByKey(Map<Integer, Integer> map) {
    List<Map.Entry<Integer, Integer>> list = new ArrayList<>(map.entrySet());
    list.sort(Comparator.comparingInt(Map.Entry::getKey));
    Map<Integer, Integer> sortedMap = new HashMap<>();
    list.forEach(sortedMap.put(e.getKey(), e.getValue()));
    return sortedMap;
}

Além disso, caso você queira classificar seu mapa com base em valuesapenas alterar Map.Entry::getKeyparaMap.Entry::getValue

Ankit Sharma
fonte
Isso apareceu no meu registro de revisão. Alguém tentou corrigir alguns erros com nomes de variáveis ​​(mas falhou). Então, eu os corrigi corretamente, no entanto, esta resposta está errada fundamentalmente. A ordem na qual você adiciona valores a um HashMap é irrelevante. Os mapas não têm ordem. stackoverflow.com/questions/10710193/…
aepryus 24/02