如何基于Invoice属性获取消费最高的10个Taxpayer?
嘿,这个问题我之前做类似需求的时候也碰到过!核心卡点在于你得先把每个纳税人的总消费金额算出来,再基于这个金额排序筛选前10——直接嵌套遍历Taxpayer和Invoice不仅效率低,TreeSet的比较逻辑也没办法直接依赖外部的Invoice数据。我给你梳理下可行的实现思路和代码:
第一步:先预计算每个Taxpayer的总消费金额
首先咱们得把所有发票按TIN归类,算出每个纳税人的总消费,存在一个Map里。这样后面排序的时候不用反复遍历发票集合,效率能提升一大截:
// 计算每个TIN对应的累计消费金额 Map<String, BigDecimal> totalSpendingMap = new HashMap<>(); for (Invoice invoice : this.invoices.values()) { String tin = invoice.getTIN(); BigDecimal amount = invoice.getAmount(); // 假设Invoice有getAmount()返回消费金额 // 用merge方法自动累加同一TIN的发票金额 totalSpendingMap.merge(tin, amount, BigDecimal::add); }
如果某个Taxpayer没有对应的发票,后面可以用getOrDefault把总消费设为0,避免空指针问题。
第二步:按总消费排序,筛选前10个Taxpayer
这里给你两种方案,按需选择:
方案一:用带自定义Comparator的TreeSet(高效维护前10)
如果你坚持要返回TreeSet,可以自定义比较器,结合刚才算的总消费排序。注意TreeSet会根据比较器结果去重,所以总消费相同时,要靠TIN这种唯一标识来区分,避免误删:
// 定义比较器:先按总消费降序,总消费相同则按TIN升序(保证唯一性) Comparator<Taxpayer> spendingComparator = (t1, t2) -> { BigDecimal s1 = totalSpendingMap.getOrDefault(t1.getTIN(), BigDecimal.ZERO); BigDecimal s2 = totalSpendingMap.getOrDefault(t2.getTIN(), BigDecimal.ZERO); // 降序排列,所以用s2.compareTo(s1) int spendingCompare = s2.compareTo(s1); if (spendingCompare != 0) { return spendingCompare; } // 总消费相同,用TIN区分,防止TreeSet把不同纳税人当成重复元素 return t1.getTIN().compareTo(t2.getTIN()); }; TreeSet<Taxpayer> topTen = new TreeSet<>(spendingComparator); // 遍历所有纳税人,加入TreeSet自动排序,超过10个就移除最小的 for (Taxpayer taxpayer : this.taxpayers.values()) { topTen.add(taxpayer); if (topTen.size() > 10) { topTen.pollLast(); // 移除当前集合中消费最低的元素 } }
这种方式不用把所有纳税人都排序,TreeSet会始终维护当前前10的元素,效率更高。
方案二:用List排序后截取前10(逻辑更直观)
如果觉得TreeSet的去重逻辑麻烦,也可以把所有纳税人放到List里排序,再截取前10:
List<Taxpayer> taxpayerList = new ArrayList<>(this.taxpayers.values()); // 按总消费降序排序 taxpayerList.sort((t1, t2) -> { BigDecimal s1 = totalSpendingMap.getOrDefault(t1.getTIN(), BigDecimal.ZERO); BigDecimal s2 = totalSpendingMap.getOrDefault(t2.getTIN(), BigDecimal.ZERO); return s2.compareTo(s1); }); // 取前10个,注意处理纳税人不足10个的情况 List<Taxpayer> topTenList = taxpayerList.subList(0, Math.min(10, taxpayerList.size())); // 如果必须返回TreeSet,转成TreeSet即可 TreeSet<Taxpayer> topTen = new TreeSet<>(spendingComparator); topTen.addAll(topTenList);
整合后的完整方法
把上面的逻辑整合到你的getTenTaxpayers方法里,最终代码是这样的:
public TreeSet<Taxpayer> getTenTaxpayers() { // 1. 预计算每个TIN的总消费金额 Map<String, BigDecimal> totalSpendingMap = new HashMap<>(); for (Invoice invoice : this.invoices.values()) { String tin = invoice.getTIN(); BigDecimal amount = invoice.getAmount(); totalSpendingMap.merge(tin, amount, BigDecimal::add); } // 2. 定义排序比较器 Comparator<Taxpayer> spendingComparator = (t1, t2) -> { BigDecimal s1 = totalSpendingMap.getOrDefault(t1.getTIN(), BigDecimal.ZERO); BigDecimal s2 = totalSpendingMap.getOrDefault(t2.getTIN(), BigDecimal.ZERO); int spendingCompare = s2.compareTo(s1); if (spendingCompare != 0) { return spendingCompare; } return t1.getTIN().compareTo(t2.getTIN()); }; // 3. 筛选并维护前10个消费最高的纳税人 TreeSet<Taxpayer> topTen = new TreeSet<>(spendingComparator); for (Taxpayer taxpayer : this.taxpayers.values()) { topTen.add(taxpayer); if (topTen.size() > 10) { topTen.pollLast(); } } return topTen; }
几个关键注意点
- 效率优化:原来的嵌套遍历是O(N*M)的时间复杂度,预计算总消费的方式是O(N+M),当发票数量很大时差异特别明显。
- 空值处理:用
getOrDefault给无发票的纳税人设置0消费,避免空指针。 - TreeSet去重:必须加入TIN的比较逻辑,否则总消费相同的纳税人会被TreeSet误判为重复元素而丢弃。
内容的提问来源于stack exchange,提问作者John Mulaney
相关产品推荐
相关产品推荐

