【发布时间】:2020-07-14 11:39:12
【问题描述】:
它与 How to apply to sort and limiting after groupBy using Java streams 不同,因为我想在一次迭代中解决这个问题。想象一下我有以下实体:
@Getter
@Setter
@AllArgsConstructor
public static class Hospital {
private AREA area;
private int patients;
}
public enum AREA {
AREA1, AREA2, AREA3
}
现在给定一个医院列表,我想找到其中患者最多的区域,这是我到目前为止所做的:
public static void main(String[] args) {
List<Hospital> list = Arrays.asList(
new Hospital(AREA.AREA1, 20),
new Hospital(AREA.AREA2, 10),
new Hospital(AREA.AREA1, 10),
new Hospital(AREA.AREA3, 40),
new Hospital(AREA.AREA2, 10));
Map<AREA, Integer> map = findTopTen(list);
for (AREA area : map.keySet())
System.out.println(area);
}
public static Map<AREA, Integer> findTopTen(Iterable<Hospital> iterable) {
Map<AREA, Integer> iterationOneResult = StreamSupport.stream(iterable.spliterator(), false)
.collect(Collectors.groupingBy(Hospital::getArea,
Collectors.summingInt(Hospital::getPatients)));
return iterationOneResult.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(10)
.collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue, (o, o2) -> o,
LinkedHashMap::new));
}
显然,我已经迭代了两次以找到患者最多的前十个区域(一次用于按区域对医院进行分组并计算该组的总和,再一次用于查找前十个区域)。
现在我想知道的是:
-
有没有更好的方法在一个流中解决这个问题,因此需要一个迭代?
-
在一次迭代中执行此操作是否有任何性能优势,解决此类问题的最佳实践是什么? (一方面我认为当我调用
collect这是一个终端操作,它第一次迭代我的可迭代对象并将中间结果保存在另一个对象中,在我的代码中我将该对象命名为iterationOneResult,因此使用一个流并调用collect one time 将省略中间结果,这是在 java 中使用流的主要好处,另一方面,在一次迭代中解决此问题使其更快)。
【问题讨论】:
-
将
(e1, e2) -> e2.getValue() - e1.getValue()分别替换为Map.Entry.comparingByValue()。Map.Entry.comparingByValue(Comparator.reverseOrder()) -
时间复杂度不是这样工作的。 “O(2n)”与 O(n) 没有什么不同。但无论如何,你的操作不是 O(n),因为排序是 O(n log n)。您可以为第二个操作创建一个自定义的基于
PriorityQueue的收集器,以仅保留前十个条目并避免对整个地图进行排序。但是,这并不意味着生成的操作比简单方法需要更少的时间。对了,你忘记limit(10)了吗? -
我不知道你想说什么“O(n log n) 是搜索而不是排序的复杂性”。首先,我不知道您指的是什么“搜索”,其次,仅仅因为某些操作具有O(n log n),并不意味着不能有任何其他操作具有O(n log n)。 Stream 的
sorted操作没有指定任何算法,但有证据表明,一般情况下不可能有比 O(n log n) 更好的排序算法。 -
总和为 O(n)。而且,顺便说一句,第二个操作流过组,所以虽然最坏的情况仍然是 O(n log n),但实际性能可能要好得多,因为实际上组比原始元素少。同样,实际的排序算法具有更好的平均和最佳情况性能。这就是为什么假设迭代两次是不好的,是没有用的。理论上的时间复杂度不会改变,现实生活中的性能甚至可以比任何尝试在单次迭代中做到这一点更好。在这里不重要,因为单次迭代是不可能的
-
因此,当您真正构建单通道解决方案时,例如使用
PriorityQueue,您必须在遇到时删除并重新添加每个项目,因为排序标准的值会永久更改。因此,时间复杂度仍然是 O(n log n),但这里 n 真正意味着源元素的数量,即使在最好的情况下也是如此。因此,对于大多数实际用例,两次迭代的解决方案将明显更快。
标签: java java-8 java-stream