简体   繁体   中英

Java How to return top 10 items based on value in a HashMap

So I am very new to Java and as such I'm fighting my way through an exercise, converting one of my Python programs to Java.

I have run into an issue where I am trying to replicate the behavior, from python the following will return only the keys sorted (by values), not the values:

popular_numbers = sorted(number_dict, key = number_dict.get, reverse = True)

In Java, I have done a bit of research and have not yet found an easy enough sample for a n00b such as myself or a comparable method. I have found examples using Guava for sorting, but the sort appears to return a HashMap sorted by key.

In addition to the above, one of the other nice things about Python, that I have not found in Java is the ability to, easily, return a subset of the sorted values. In Python I can simply do the following:

print "Top 10 Numbers: %s" % popular_numbers[:10]

In this example, number_dict is a dictionary of key,value pairs where key represents numbers 1..100 and the value is the number of times the number (key) occurs:

for n in numbers:
 if not n == '':
   number_dict[n] += 1

The end result would be something like:

Top 10 Numbers: ['27', '11', '5', '8', '16', '25', '1', '24', '32', '20']

To clarify, in Java I have successfully created a HashMap, I have successfully examined numbers and increased the values of the key,value pair. I am now stuck at the sort and return the top 10 numbers (keys) based on value.

  1. Put the map's entrySet() into a List .
  2. Sort this list using Collections.sort and a Comparator which sorts Entry s based on their values.
  3. Use the subList(int, int) method of List to retrieve a new list containing the top 10 elements.

Yes, it will be much more verbose than Python :)

With Java 8+, to get the first 10 elements of a list of intergers:

list.stream().sorted().limit(10).collect(Collectors.toList());

To get the first 10 elements of a map's keys, that are integers:

map.keySet().stream().sorted().limit(10).collect(Collectors.toMap(Function.identity(), map::get));

Assuming your map is defined something like this and that you want to sort based on values :

HashMap<Integer, Integer> map= new HashMap<Integer, Integer>();
//add values
Collection<Integer> values= map.values();
ArrayList<Integer> list= new ArrayList<Integer>(values);
Collections.sort(list);

Now, print the first top 10 elements of the list.

for (int i=0; i<10; i++) {
    System.out.println(list.get(i));
}

The values in the map are not actually sorted, because the HashMap is not sorted at all (it stores the values in the buckets based on the hashCode of the key). This code is just displaying 10 smallest elements in the map.

EDIT sort without loosing the key-value pairs:

//sorted tree map
TreeMap<Integer, Integer> tree= new TreeMap<>();

//iterate over a map
Iteartor<Integer> it= map.keySet().iterator();
while (it.hasNext()) {
    Integer key= it.next();
    tree.put(map.get(key), key);
}

Now you have the TreeMap tree that is sorted and has reversed key-value pairs from the original map, so you don't lose the information.

HashMap s aren't ordered in Java, and so there isn't really a good way to order them short of a brute-force search through all the keys. Try using TreeMap :http://docs.oracle.com/javase/6/docs/api/java/util/TreeMap.html

Try the next:

public static void main(String[] args) {

    // Map for store the numbers
    Map<Integer, Integer> map = new HashMap<Integer, Integer>();

    // Populate the map ...

    // Sort by the more popular number
    Set<Entry<Integer, Integer>> set = map.entrySet();
    List<Entry<Integer, Integer>> list = new ArrayList<>(set);
    Collections.sort(list, new Comparator<Entry<Integer, Integer>>() {
        @Override
        public int compare(Entry<Integer, Integer> a,
                Entry<Integer, Integer> b) {
            return b.getValue() - a.getValue();
        }
    });


    // Output the top 10 numbers
    for (int i = 0; i < 10 && i < list.size(); i++) {
        System.out.println(list.get(i));
    }

}

Guava Multiset is a great fit for your use case, and would nicely replace your HashMap. It is a collection which counts the number of occurences of each element.

Multisets has a method copyHighestCountFirst , which returns an immutable Multiset ordered by count.

Now some code:

Multiset<Integer> counter = HashMultiset.create();
//add Integers 
ImmutableMultiset<Integer> sortedCount = Multisets.copyHighestCountFirst(counter);
//iterate through sortedCount as needed

Use a SortedMap , call values() . The docs indicate the following:

The collection's iterator returns the values in ascending order of the corresponding keys

So as long as your comparator is written correctly you can just iterate over the first n keys

  1. Build a list from the keyset.

  2. Sort the HashMap by values using the keys to access the value in the Collection.sort() method.

  3. Return a sub list of the sorted key set.

  4. if you care about the values, you can use the keys in step 3 and build value set.

    HashMap<String, Integer> hashMap = new HashMap<String, Integer>(); List list = new ArrayList(hashMap.keySet()); Collections.sort(list, (w1, w2) -> hashMap.get(w2) - hashMap.get(w1)); //sorted descending order by value;

    return list.subList(0, 10);

To preserve the ranking order and efficiently return top count, much smaller than the size of the map size:

map.entrySet().stream()
            .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
            .limit(count)
            .collect(toMap(Map.Entry::getKey, Map.Entry::getValue,
                    (e1, e2) -> e1,
                    LinkedHashMap::new))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM