Home > Blockchain >  Get the 3 max value in a map
Get the 3 max value in a map

Time:06-16

I'm storing information of the lastByFirst variable.

{Peter=[Leigh], George=[Barron, Trickett,Evans], Paul-Courtenay=[Hyu], Ryan=[Smith], Toby=[Geller, Williams], Simon=[Bush, Milosic, Quarterman,Brown]

How can I print the first 3 which appeared the most and also the number of appereance.

I would like to list those which 3 value appeared the most. In the lastByFirst contains something like that. I would like to print in this way:

Simon: 4  
George: 3  
Toby:2
Map<String, List<String>> lastByFirst = PeopleProcessor.lastnamesByFirstname(PeopleSetup.people);

My attempt was something like that:

var store = lastByFirst.entrySet()
                .stream()
                .collect(groupingBy(Person::getLastName,
                        Collectors.counting())
                        .toString();

store should be equal with
Simon: 4
George: 3
Toby:2

CodePudding user response:

You can:

  1. sort in descending mode by size
  2. select the first three elements
  3. reduce to one string
//1
List<Map.Entry<String, List<String>>> entryList = lastByFirst.entrySet()
        .stream()
        .sorted((e2, e1) -> Integer.compare(e1.getValue().size(), e2.getValue().size()))
        .toList();
//2
String result = IntStream.range(0, 3)
        .mapToObj(entryList::get)
        .map(e -> String.format("%s: %d\n", e.getKey(), e.getValue().size()))
        .collect(Collectors.joining()); //3

CodePudding user response:

Here's one that first converts the map of lists to a map of the sizes of the list, and then picks the top three such sizes:

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Demo {
    public static void main(String[] args) {
        Map<String, List<String>> lastByFirst =
            Map.of("Peter", List.of("Leigh"), "George", List.of("Barron", "Trickett", "Evans"),
                   "Paul-Courtenay", List.of("Hyu"), "Ryan", List.of("Smith"),
                   "Toby", List.of("Geller", "Williams"), "Simon", List.of("Bush", "Milosic", "Quaterman", "Brown"));
        List<Map.Entry<String, Integer>> topThree =
            lastByFirst.entrySet().stream()
            .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, e -> e.getValue().size()))
            .entrySet()
            .stream()
            .sorted(Comparator.<Map.Entry<String, Integer>, Integer>comparing(Map.Entry::getValue).reversed())
            .limit(3)
            .collect(Collectors.toList());
        System.out.println(topThree);
    }
}

CodePudding user response:

If you already have a map of people grouped by first name, you can address the problem of finding the 3 most frequent first names in a linear time O(n). Which is faster than sorting the whole data set.

If instead of picking 3 most frequent first names it would be generalized to m most frequent, then the time complexity would be O(n m * log m) (which for small values of m would be close to linear time).

To implement it using streams, we can utilize a custom comparator, which can be created using the static method Collector.of().

As a mutable container of the collector, we can use a TreeMap sorted in the natural order, where the key would represent the of people having the same first name and the value would be a first name itself.

In order to retain only m most frequent names we need to track the size of the TreeMap and when it gets exceeded we have to remove the first entry (i.e. an entry having the lowest key).

public static <K, V> Collector<Map.Entry<K, List<V>>, ?, NavigableMap<Integer, K>>
                                                        getEntryCollector(int size) {
    return Collector.of(
        TreeMap::new,
        (NavigableMap<Integer, K> map, Map.Entry<K, List<V>> entry) -> {
            if (map.size() < size || map.firstKey() < entry.getValue().size()) {  // the container hasn't reached the maximum size of the frequency of the offered name is higher than the lowest existing frequency
                map.put(entry.getValue().size(), entry.getKey());
            }
            if (map.size() > size) map.remove(map.firstKey()); // the size of the container has been exceeded
        },
        (NavigableMap<Integer, K> left, NavigableMap<Integer, K> right) -> { // merging the two containers with partial results obtained during the parallel execution
            left.putAll(right);
            while (left.size() > size) left.remove(left.firstKey());
            return left;
        }
    );
}

main()

public static void main(String args[]) {

    Map<String, List<String>> lastByFirst =
    Map.of("Peter", List.of("Leigh"), "George", List.of("Barron", "Trickett", "Evans"),
    "Paul-Courtenay", List.of("Hyu"), "Ryan", List.of("Smith"), "Toby", List.of("Geller", "Williams"),
    "Simon", List.of("Bush", "Milosic", "Quarterman", "Brown"));

    NavigableMap<Integer, String> nameByFrequency =
        lastByFirst.entrySet().stream()
            .collect(getEntryCollector(3));
    
    nameByFrequency.entrySet().stream() // printing the result, sorting in reversed order applied only for demo purposes
        .sorted(Map.Entry.comparingByKey(Comparator.<Integer>naturalOrder().reversed()))
        .forEach(entry -> System.out.println(entry.getValue()   ": "   entry.getKey()));
}

Output:

Simon: 4
George: 3
Toby: 2

A link Online Demo

  • Related