В Java мне нужно сгруппировать ключи хэш-карты на основе их значений.

Мне нужно найти ключи topN.

У меня есть ввод в виде HashMap в виде (ключ: значение):

Banana : 13  
Apple: 12  
Mango : 32  
Orange : 12  
Grape : 18  
Pear : 12  
Peach : 18  

Я создал связанную HapMap, отсортированную по значениям:

private static <K extends Comparable, V extends Comparable> Map<K, V> sortByValues(Map<K, V> map) {
    List<Map.Entry<K, V>> entries = new LinkedList<Map.Entry<K, V>>(map.entrySet());

    Collections.sort(entries, new Comparator<Map.Entry<K, V>>() {

        @Override
        public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) {
            return o2.getValue().compareTo(o1.getValue());
        }
    });
    Map<K, V> sortedMap = new LinkedHashMap<K, V>();
    for (Map.Entry<K, V> entry : entries) {
        sortedMap.put(entry.getKey(), entry.getValue());
    }
    return sortedMap;
}

это дало мне вывод как:

Mango : 32  
Grape : 18  
Peach : 18  
Banana : 13  
Apple: 12  
Orange : 12  
Pear : 12  

Теперь, если я хочу получить 4 лучших плода, как мне подойти, если я хочу, чтобы результат был:

Mango :32  
Grape, Peach : 18  
Banana :13  
Apple, Orange, Pear: 12  

Я попытался выполнить итерацию по отсортированному hashMap и сравнил значения последующих элементов, выполнив

int sizeOfMap = myValueSortedMap.size();
ArrayList<String> keyArr = new ArrayList<String>();
int cnt=0,keyVal=0;

while(cnt<(sizeOfMap-1)){

    if(myValueSortedMap.values().toArray()[cnt] == myValueSortedMap.values().toArray()[cnt+1]){

        keyArr.add((String) myValueSortedMap.keySet().toArray()[cnt]+ " , " +(String) myValueSortedMap.keySet().toArray()[cnt+1]);
    }
    else{
        keyArr.add((String) myValueSortedMap.keySet().toArray()[cnt]);
        keyVal = (int) myValueSortedMap.values().toArray()[cnt];
    }
    cnt++;
}

но это не всегда работает.

Я не могу придумать способ обойти это. Может кто-нибудь, пожалуйста, дайте мне руководство?


person nirdesh80    schedule 04.07.2016    source источник
comment
Вы можете попробовать перевернуть карту. Новые ключи карты будут number значениями из первой карты, а новые значения карты будут list первых ключей карты, имеющих это значение.   -  person tfosra    schedule 04.07.2016


Ответы (5)


Вы можете использовать двухэтапный потоковый процесс: на первом этапе записи группируются по числовому значению, на втором сортируется содержимое потока, применяется ограничение 4 и печатается результат.

map.entrySet()
   .stream()
   .collect(Collectors.groupingBy(Map.Entry::getValue))
   // the map now contains integers mapped to a list of map entries
   .entrySet()
   .stream()
   // sort the stream by descending numeric value
   .sorted((o1, o2) -> o2.getKey().compareTo(o1.getKey()))
   // use the first four elements of the stream
   .limit(4)
   .forEach(entry -> System.out.println(entry.getKey() + " " + entry.getValue().stream().map(Map.Entry::getKey).collect(Collectors.joining(", "))));

Это приводит к

32 Mango
18 Grape, Peach
13 Banana
12 Apple, Pear, Orange
person Steve Chaloner    schedule 04.07.2016

Как я сказал в своем комментарии, сначала вам нужно будет перевернуть карту: так Map<String, Integer> станет Map<Integer, List<String>>.

Затем вы можете получить 4 максимальных ключа из вашей новой карты (ранее значения вашей первой карты) и распечатать их значения.

Обратный метод карты

public static <K extends Comparable, V extends Comparable> Map<V, List<K>> reverseMap(Map<K, V> map) {
    Map<V, List<K>> result = new HashMap<>();
    map.entrySet().stream().forEach((entry) -> {
        List<K> lst = result.get(entry.getValue());
        if (lst == null) {
            lst = new ArrayList<>();
            result.put(entry.getValue(), lst);
        }
        lst.add(entry.getKey());
    });
    return result;
}

Как это использовать

Map<String, Integer> map = new HashMap<>();
map.put("Banana", 13);
map.put("Apple", 12);
map.put("Mango", 32);
map.put("Orange", 12);
map.put("Grape", 18);
map.put("Pear", 12);
map.put("Peach", 18); 

Map<Integer, List<String>> flip = reverseMap(map);

flip.entrySet().stream()
            .sorted(Map.Entry.<Integer, List<String>>comparingByKey().reversed())
            .limit(4)
            .forEach(e -> System.out.println(String.join(", ", e.getValue()) + " : " + e.getKey()));

Вот результат

Mango : 32
Grape, Peach : 18
Banana : 13
Apple, Pear, Orange : 12
person tfosra    schedule 04.07.2016

Вы можете использовать SortedMap с "Fruit" Объект, реализующий Compareable.

Карта упорядочивается в соответствии с естественным порядком ее ключей или с помощью компаратора, который обычно предоставляется во время создания отсортированной карты. Этот порядок отражается при переборе представлений коллекций отсортированной карты (возвращаемых методами entrySet, keySet и values). Предусмотрено несколько дополнительных операций, позволяющих воспользоваться преимуществами заказа. (Этот интерфейс является аналогом карты SortedSet.)

person hiaclibe    schedule 04.07.2016
comment
SortedMap<String, Integer> map = new TreeMap<>(); — или посмотреть в javadoc классы реализации SortedMap. И перебрать entrySet. - person Joop Eggen; 04.07.2016

Просто повторите HashMap и сохраните первые k элементов.

private HashMap<String, Integer> select_k_element(HashMap sortedMap,int k){
    HashMap<String, Integer> res=new HashMap();
    int i=0;
    for (Map.Entry<String, Integer> e : sortedMap.entrySet()) {
        String key=e.getKey();
        int value=e.getValue();
        res.put(key,value);
        i++;
        if(i>=k)
            break;
    }
    return res;
}
person Adam Lyu    schedule 04.07.2016

Вы должны использовать Map с перевернутой парой ключ-значение (типами данных). Ниже приведена базовая реализация:

private static Map<String, Integer> fruits = new HashMap<>();

static {
    fruits.put("Banana",13);  
    fruits.put("Apple", 12);
    fruits.put("Mango",32);
    fruits.put("Orange",12); 
    fruits.put("Grape",18);
    fruits.put("Pear",12);
    fruits.put("Peach",18); 
}

public static void main(String[] args) {
    TreeMap<Integer, String> freq = new TreeMap<>();

    // Populate the new Map
    fruits.entrySet().forEach(e -> {
        Integer key = e.getValue();
        if(freq.containsKey(key)) { // Update entry if key is already present
            String curVal = freq.get(key);
            String newVal = e.getKey();

            freq.put(key, curVal + ", " + newVal);
        } else { // Add an entry if key is not present
            freq.put(key, e.getKey());
        }
    });

    // Print the new Map
    freq.descendingMap().entrySet().forEach(e -> {
        System.out.println(e.getValue() + " : " + e.getKey());
    });
}

Использовал TreeMap для сортировки по ключу и использовал descendingMap() для печати в порядке убывания.

Вывод:

Mango : 32
Grape,Peach : 18
Banana : 13
Apple,Pear,Orange : 12
person Bilesh Ganguly    schedule 04.07.2016