简体   繁体   English

如何使用比较器按值对 TreeMap 进行排序

[英]How to sort TreeMap by values using comparator

I want to build a Map containing elements that are sorted by their value.我想构建一个包含按值排序的元素的 Map。 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.我收到一个包含 {customerId, purchaseAmount} 的购买列表,并且想要构建一个将客户映射到他们的总购买金额的表格地图。 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.我最初的解决方案是构建一个 Map(使用 HashMap),将这个 Map 转换为一个 List(LinkedList),按降序对这个 List 进行排序,然后处理这个 List。 This is an O(n log n) solution, and I believe it is the best possible time complexity.这是一个 O(n log n) 解决方案,我相信这是最好的时间复杂度。 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.但是,我想知道是否有某种方法可以利用具有固有排序属性的 TreeMap 等数据结构。 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.当前代码完成了我想做的事情,但是我想知道是否有一种方法可以避免将 Map 转换为 List,然后使用我的自定义 Comparator 对 List 进行排序。 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这是我在 StackOverflow 上的第一篇文章

First of all a TreeMap does not work for you, because it is sorted by keys, not by values.首先TreeMap不适合你,因为它是按键排序的,而不是按值排序。 Another alternative would be a LinkedHashMap .另一种选择是LinkedHashMap It is sorted by insertion order.它按插入顺序排序。

You also can use Java Streams to process your List:您还可以使用 Java Streams 来处理您的列表:

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.这将创建一个以customerId为键和所有购买总和的映射。 Next you can sort that, by using another stream and migrating it to a LinkedHashMap :接下来,您可以通过使用另一个流并将其迁移到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.如果你想了解更多关于 java Streams 的基本信息,这里是一个官方教程。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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