In many software engineering and data analytics applications, standard sorting algorithms (which order elements based on their natural values or alphabetical characters) are insufficient. Often, you need to sort collections based on their frequency of occurrence—meaning the most popular, repeating elements appear at the beginning of the list.
Whether you are building a trending search query dashboard, prioritizing system alert logs, or solving LeetCode interview challenges like "Sort Characters By Frequency", grouping and sorting collections by their occurrences is a fundamental pattern. In Java, implementing this iteratively with loops and nested collections can quickly lead to verbose, error-prone code. Fortunately, Java 8 Streams and Collectors provide a clean, declarative way to perform this operation. In this guide, we will analyze the logic of frequency-based sorting and walk through a clean Java implementation.
To visualize frequency sorting, imagine a grocery store cashier sorting a customer's shopping cart:
- Alphabetical Sorting: If the customer buys 4 apples, 3 bananas, and 1 pear, an alphabetical sort lists them as: Apple, Apple, Apple, Apple, Banana, Banana, Banana, Pear.
- Frequency-Based Sorting: A popularity-based sort first groups identical fruits into separate checkout baskets, counts the quantity in each basket, and then arranges the baskets in descending order. The apples basket (count 4) is processed first, followed by bananas (count 3), and finally the single pear.
1. Step 1: Grouping and Counting
The first step is to transform our flat list of elements into a frequency lookup table. We use Collectors.groupingBy() alongside Collectors.counting() to build a map where keys are the unique list elements and values represent their respective frequency count:
Map<Integer, Long> frequencyMap = list.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
// Input: {2, 3, 2, 2, 5, 5, 6}
// Output Map: {2=3, 3=1, 5=2, 6=1}
2. Step 2: Sorting and Re-expanding
Once the map is generated, we stream its entry set and sort the entries in descending order based on their value (the counts). Finally, we iterate through the sorted entries and expand each key back into our output list according to its count:
frequencyMap.entrySet().stream()
.sorted((entry1, entry2) -> entry2.getValue().compareTo(entry1.getValue()))
.collect(Collectors.toList());
Implementation in Java
Below is the complete Java code that groups the elements, sorts the map, and expands the keys back into a sorted output list:
package io.practise.leetcode.medium;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class SortBasedOnOccurance {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(2, 3, 2, 2, 5, 5, 6, 5, 7, 5, 3, 1);
List<Integer> output = new ArrayList<>();
// 1. Group by element and count occurrences
list.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
// 2. Stream entry set and sort by occurrences descending
.entrySet().stream()
.sorted((entry1, entry2) -> entry2.getValue().compareTo(entry1.getValue()))
.collect(Collectors.toList())
// 3. Re-expand elements back into output list
.forEach(entry -> {
for (int index = 0; index < entry.getValue(); ++index) {
output.add(entry.getKey());
}
});
System.out.println("Input: " + list);
System.out.println("Output: " + output);
// Output: [5, 5, 5, 5, 2, 2, 2, 3, 3, 6, 7, 1]
}
}
Conclusion & Takeaways
Frequency-based sorting is a classic pattern in data processing. By leveraging Java Streams' mapping utilities, you can replace complex nested loops with a declarative stream pipeline that is easy to read, optimize, and maintain across enterprise collections.