Reputation: 21
I want to build a Map containing elements that are sorted by their value. I receive a list of purchases containing {customerId, purchaseAmount}, and want to build a map of the form which maps the customer to their total purchase amount. A single customer may have multiple purchases.
Finally, I want to process this information customer-by-customer, in order of decreasing total purchase amount. Meaning that I process the highest spending customer first, and the lowest spending customer last.
My initial solution for this was to build a Map (using HashMap), converting this Map to a List (LinkedList), sorting this List in decreasing order, and then processing this List. This is an O(n log n) solution, and I believe it is the best possible time complexity. However, I want to know if there is some way to leverage a data structure such as TreeMap which has a sorted property inherent to it. By default it will be sorted by its keys, however I want to sort it by the value. My current solution below.
public class MessageProcessor {
public static void main(String[] args) {
List<Purchase> purchases = new ArrayList<>();
purchases.add(new Purchase(1, 10));
purchases.add(new Purchase(2, 20));
purchases.add(new Purchase(3, 10));
purchases.add(new Purchase(1, 22));
purchases.add(new Purchase(2, 100));
processPurchases(purchases);
}
private static void processPurchases(List<Purchase> purchases) {
Map<Integer, Double> map = new HashMap<>();
for(Purchase p: purchases) {
if(!map.containsKey(p.customerId)) {
map.put(p.customerId, p.purchaseAmt);
}else {
double value = map.get(p.customerId);
map.put(p.customerId, value + p.purchaseAmt);
}
}
List<Purchase> list = new LinkedList<>();
for(Map.Entry<Integer, Double> entry : map.entrySet()) {
list.add(new Purchase(entry.getKey(), entry.getValue()));
}
System.out.println(list);
Comparator<Purchase> comparator = Comparator.comparing(p -> p.getPurchaseAmt());
list.sort(comparator.reversed());
//Process list
//...
}
class Purchase {
int customerId;
double purchaseAmt;
public Purchase(int customerId, double purchaseAmt) {
this.customerId = customerId;
this.purchaseAmt = purchaseAmt;
}
public double getPurchaseAmt() {
return this.purchaseAmt;
}
}
The current code accomplishes what I want to do, however I would like to know if there is a way that I can avoid transforming the Map into a List and then sorting the List using my custom Comparator. Perhaps using some kind of sort of sorted Map. Any advice would be appreciated. Also, suggestions on how to make my code more readable or idiomatic would be appreciated. Thanks. This is my first post of StackOverflow
Upvotes: 1
Views: 393
Reputation: 11042
First of all a TreeMap
does not work for you, because it is sorted by keys, not by values. Another alternative would be a LinkedHashMap
. It is sorted by insertion order.
You also can use Java Streams to process your List:
Map<Integer, Double> map = purchases.stream()
.collect(Collectors.toMap(Purchase::getCustomerId, Purchase::getPurchaseAmt, (a, b) -> a + b));
This creates a map for with the customerId
as key and the sum of all purchases. Next you can sort that, by using another stream and migrating it to a LinkedHashMap
:
LinkedHashMap<Integer, Double> sorted = map.entrySet().stream()
.sorted(Comparator.comparing(Map.Entry<Integer, Double>::getValue).reversed())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> {
throw new IllegalStateException("");
}, LinkedHashMap::new));
At the end you can create a new list again if you need it:
List<Purchase> list = sorted.entrySet().stream()
.map(e -> new Purchase(e.getKey(), e.getValue()))
.collect(Collectors.toList());
If you want more basic information to java Streams here is an official tutorial.
Upvotes: 1