如何为TreeMap编写equalsTo/compareTo/hasCode以获取前N个值。

huangapple go评论70阅读模式
英文:

How to write customer equalsTo/compareTo/hasCode for TreeMap to get Top N values

问题

问题是:客户想要查看到目前为止销售最多的物品。我们希望有效地向客户提供这些数据。

样本输入:
已售物品清单:
F|400
B|1000
A|500
A|600
C|1000
D|700
E|300

样本输出:
A|1100
C|1000
B|1000
D|700

我的想法是在第一个 API processInput() 中,使用一个映射将物品和价值关联起来,以跟踪到目前为止已经销售的所有物品,并将更新后的物品添加到最大优先级队列中。在第二个 API processQuery() 中,只需从队列中选择前 K 个物品。一个问题是在 processQuery 中,时间复杂度是 O(NlogN)

是否可能通过单个 TreeMap 来解决这个问题,通过覆盖 equalsTocompareTohasCode,使 TreeMap 按值排序。这样我们只需迭代 TreeMap 并返回前 N 个物品?

英文:

The question is:<br/>Clients want to see which items are most sold so far. We want to provide this data to our clients efficiently.<br/>Sample input:<br/>List of sold items:<br/>F|400 <br/> B|1000<br/>A|500<br/>A|600<br/>C|1000<br/>D|700<br/>E|300<br/>Sample output<br/>A|1100<br/> C|1000<br/>B|1000<br/>D|700

My thought is in first API processInput(), use a map of item and value to track all items sold so far and then add the updated item into a max priority queue. And in second API processQuery(), just select top K from queue. One issue is in processQuery, time complexity is O(NlogN).<br/><br/> Is it possible we solve this problem with a single TreeMap by overriding equalsTo, 'compareTo, and hasCode so that that treemap is sorted by value. So we just iterate over the TreeMap and return topN items?

答案1

得分: 0

假设输入是以提到的格式为例的字符串列表,第一步 processInput 是将输入列表转换为一些对象{name, num}的列表:

public static List<Obj> processInput(List<String> data) {
    return Arrays.stream(data)
                 .map(s -> s.split("\\|"))
                 .map(arr -> new Obj(arr[0], Integer.parseInt(arr[1])))
                 .collect(Collectors.toList());
}

然后第二个方法按名称分组,累加总值,并返回类似的列表作为结果:

public static List<Obj> getTopTotals(List<Obj> data, int topCount) {
    return data.stream()
               .collect(
                       Collectors.groupingBy(Obj::getName, TreeMap::new,
                       Collectors.summingInt(Obj::getNum)
                )) // 获得总值的 TreeMap<String, Integer>
                .entrySet().stream()
                .sorted((a, b) -> b.getValue().compareTo(a.getValue())) // 按值降序排序
                .limit(topCount)
                .map(e -> new Obj(e.getKey(), e.getValue()))
                .collect(Collectors.toList());
}
英文:

Assuming that the input is a list of strings in the mentioned format, the first step processInput is to convert the input list into list of some objects {name, num}:

public static List&lt;Obj&gt; processInput(List&lt;String&gt; data) {
    return Arrays.stream(data)
                 .map(s -&gt; s.split(&quot;\\|&quot;))
                 .map(arr -&gt; new Obj(arr[0], Integer.parseInt(arr[1])))
                 .collect(Collectors.toList());
}

Then the second method groups by the name, sums up the total value, and returns similar list as a result:

public static List&lt;Obj&gt; getTopTotals(List&lt;Obj&gt; data, int topCount) {
    return data.stream()
               .collect(
                       Collectors.groupingBy(Obj::getName, TreeMap::new,
                       Collectors.summingInt(Obj::getNum)
                )) // get TreeMap&lt;String, Integer&gt; of totals
                .entrySet().stream()
                .sorted((a, b) -&gt; b.getValue().compareTo(a.getValue())) // sort by value desc
                .limit(topCount)
                .map(e -&gt; new Obj(e.getKey(), e.getValue()))
                .collect(Collectors.toList());
}

答案2

得分: 0

你需要在两个方向上都实现高效的访问:当项目到达时,您需要根据它们的名称(A、B、C)获取聚合金额,但是为了对它们进行排序,将使用聚合金额。我认为您可以使用Map来按名称查找项目,并使用SortedSet来进行排序。由于金额本身不是唯一的(例如在示例中,B和C都为1000),所以排序也应该利用名称(因为名称是唯一的),这就是为什么对于排序部分而言,只使用一个集合就足够了:

import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

public class Test {
    static class Item implements Comparable<Item> {
        final String name;
        int amount;
        Item(String name, int amount) {
            this.name = name;
            this.amount = amount;
        }
        public int compareTo(Item o) {
            if (o.amount < amount)
                return -1;
            if (o.amount > amount)
                return 1;
            return o.name.compareTo(name);
        }
        public String toString() {
            return name + "|" + amount;
        }
    }
    
    Map<String, Item> itemmap = new HashMap<>();
    SortedSet<Item> sorted = new TreeSet<>();
    
    void add(String name, int amount) {
        Item i = itemmap.get(name);
        if (i != null) {
            sorted.remove(i);
            i.amount += amount;
        } else {
            i = new Item(name, amount);
        }
        itemmap.put(name, i);
        sorted.add(i);
    }
    public static void main(String[] args) {
        Test t = new Test();
        t.add("F", 400);
        t.add("B", 1000);
        t.add("xA", 500);
        t.add("xA", 600);
        t.add("C", 1000);
        t.add("D", 700);
        t.add("E", 300);
        System.out.println("itemmap: " + t.itemmap);
        System.out.println("sorted: " + t.sorted);
    }
}

它会产生如下输出(在 Ideone 上测试:https://ideone.com/2d79aC)

itemmap: {B=B|1000, C=C|1000, D=D|700, E=E|300, F=F|400, xA=xA|1100}
sorted: [xA|1100, C|1000, B|1000, D|700, F|400, E|300]

我故意将A重命名为xA,否则itemmap将会是A|1100, B|1000, C|1000,但这只是字母顺序的巧合(对几个连续的单个字母字符串进行哈希处理实际上并不能很好地混合顺序)。C和B以相反的顺序出现是因为这是您要求的。如果您希望它们是B,然后是C,请交换compareTo()方法的返回值,或者只需将当前返回值添加一个负号。

英文:

You need efficient access in both directions: when items come, you need to get the aggregated amount based on their name (A,B,C), but for sorting them, the aggregated amount is going to be used. I think you could use a Map for finding items by name, and a SortedSet for sorting. As amounts themselves are not unique (like B and C have both 1000 in the example), sorting should also make use of the name (as those are unique), and that's why a set is enough for that part:

import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
public class Test {
static class Item implements Comparable&lt;Item&gt; {
final String name;
int amount;
Item(String name,int amount){
this.name=name;
this.amount=amount;
}
public int compareTo(Item o) {
if(o.amount&lt;amount)
return -1;
if(o.amount&gt;amount)
return 1;
return o.name.compareTo(name);
}
public String toString() {
return name+&quot;|&quot;+amount;
}
}
Map&lt;String,Item&gt; itemmap=new HashMap&lt;&gt;();
SortedSet&lt;Item&gt; sorted=new TreeSet&lt;&gt;();
void add(String name,int amount) {
Item i=itemmap.get(name);
if(i!=null) {
sorted.remove(i);
i.amount+=amount;
} else {
i=new Item(name,amount);
}
itemmap.put(name,i);
sorted.add(i);
}
public static void main(String[] args) {
Test t=new Test();
t.add(&quot;F&quot;,400);
t.add(&quot;B&quot;,1000);
t.add(&quot;xA&quot;,500);
t.add(&quot;xA&quot;,600);
t.add(&quot;C&quot;,1000);
t.add(&quot;D&quot;,700);
t.add(&quot;E&quot;,300);
System.out.println(&quot;itemmap: &quot;+t.itemmap);
System.out.println(&quot;sorted: &quot;+t.sorted);
}
}

It produces the output (test on Ideone: https://ideone.com/2d79aC)

> itemmap: {B=B|1000, C=C|1000, D=D|700, E=E|300, F=F|400, xA=xA|1100}
> sorted: [xA|1100, C|1000, B|1000, D|700, F|400, E|300]

I deliberately renamed A to xA, otherwise the map would be A|1100, B|1000, C|1000, but that's just a coincidence of alphabetical order (hashing a few consecutive, single-letter strings does not really mix the order). C and B come in this reverse order because that's what you have asked for. If you want them as B, then C, swap the compareTo(), or just return the current one with a negative sign.

huangapple
  • 本文由 发表于 2020年10月3日 05:24:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/64178414.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定