【发布时间】:2019-12-17 04:09:51
【问题描述】:
我刚刚尝试了一个关于 codility 的测试示例。任务是:“......给定一个包含 N 个整数的数组 A,返回 A 中未出现的最小正整数(大于 0)。”。
加号:
N 是 [1..100,000] 范围内的整数;
数组 A 的每个元素都是 [−1,000,000..1,000,000] 范围内的整数。
我的第一次尝试是典型的 Java 8 解决方案:
public int solution(int[] A) {
Set<Integer> l = Arrays
.stream(A)
.boxed()
.filter(i -> i > 0)
.collect(Collectors.toSet());
return IntStream
.iterate(1, a -> a + 1)
.filter(i -> !l.contains(i))
.findFirst()
.getAsInt();
}
一切正确,但对中等大小的测试数组的测试遇到了超时。
第二次尝试(普通的旧java):
public int solution(int[] A) {
boolean[] B = new boolean[1000001];
for (int a : A) {
if (a > 0) {
B[a] = true;
}
}
for (int i = 1; i <= 1000000; i++) {
if (!B[i]) {
return i;
}
}
return 1;
}
这个版本快得令人难以置信,尤其是对于短数组 A。
问题:
- 我第一次尝试时是否遗漏了什么?
- 是否有调整选项?
- 对代码能力的测试是否为时过早,实际上预计差异会消失,几乎完全消失?
- 有人有更好的 Java 8 解决方案吗?
测试结果第一版:
▶ 示例1 第一个示例测试 ✔OK 1. 0.108秒OK
▶ 例子2 第二个示例测试✔OK 1. 0.104秒OK
▶ 例子3 第三个示例测试✔OK 1. 0.104秒OK
▶extreme_single 单个元素 ✔OK 1. 0.100 秒确定 2. 0.104 秒 3. 0.104 秒 OK 4. 0.100秒OK
▶ 简单 简单测试✔OK 1. 0.100 秒确定 2. 0.104 秒 3. 0.100秒OK
▶extreme_min_max_value 最小值和最大值 ✔OK 1. 0.100 秒确定 2. 0.104秒OK
▶ positive_only 0...100 然后 102...200 的随机序列 ✔OK 1. 0.100 秒确定 2. 0.104秒OK
▶negative_only 随机序列 -100 ... -1 ✔OK 1. 0.100秒OK
▶ 中等 混沌序列长度=10005(带减号)✘TIMEOUT ERROR 运行时间:0.136 秒,时限:0.100 秒。 1. 0.136 s TIMEOUT ERROR,运行时间:0.136 秒,时限:0.100 秒。 2. 0.128 s TIMEOUT ERROR,运行时间:0.128 秒,时限:0.100 秒。 3. 0.144 s TIMEOUT ERROR,运行时间:0.144 秒,时限:0.128 秒。
▶ 大_1 混乱 + 序列 1, 2, ..., 40000(没有减号)✔OK 1. 0.588秒OK
▶大_2 改组后的序列 1, 2, ..., 100000(没有减号) ✔OK 1. 0.748 秒 2. 0.660秒OK
▶ 大_3 混乱+许多-1、1、2、3(带减号)✔OK 1. 0.444秒OK
测试结果第二版:
▶ 示例1 第一个示例测试 ✔OK 1. 0.004秒OK
▶ 例子2 第二个示例测试✔OK 1. 0.004秒OK
▶ 例子3 第三个示例测试✔OK 1. 0.004秒OK
▶extreme_single 单个元素 ✔OK 1. 0.004 秒 2. 0.008 秒 3. 0.004 秒 4. 0.008秒OK
▶ 简单 简单测试✔OK 1. 0.004 秒 2. 0.004 秒 3. 0.008秒OK
▶extreme_min_max_value 最小值和最大值 ✔OK 1. 0.008 秒 2. 0.004秒OK
▶ positive_only 0...100 然后 102...200 的随机序列 ✔OK 1. 0.008 秒 2. 0.004秒OK
▶negative_only 随机序列 -100 ... -1 ✔OK 1. 0.008秒OK
▶ 中等 混沌序列长度=10005(带减号)✔OK 1. 0.024 秒 2. 0.024 秒 3. 0.032秒OK
▶ 大_1 混乱 + 序列 1, 2, ..., 40000(没有减号)✔OK 1. 0.220秒OK
▶大_2 改组后的序列 1, 2, ..., 100000(没有减号) ✔OK 1. 0.244 秒 2. 0.244秒OK
▶ 大_3 混乱+许多-1、1、2、3(带减号)✔OK 1. 0.172秒OK
底线: 尤其是使用小型数组的测试仅使用普通 java 就快了两个数量级。对于大型数组,它“仅”是 3 倍。
编辑:
根据 cmets,我只是试图深入研究问题并尝试:
public int solution(int[] A) {
boolean[] B = new boolean[1000001];
for (int a : A) {
if (a>0){
B[a] = true;
}
}
return IntStream
.iterate(1, a -> a + 1)
.filter(i -> !B[i])
.findFirst()
.getAsInt();
}
结果:
▶ 示例1 第一个示例测试 ✔OK 1. 0.096秒OK
▶ 例子2 第二个示例测试✔OK 1. 0.096秒OK
▶ 例子3 第三个示例测试✔OK 1. 0.096 秒 折叠所有正确性测试
▶extreme_single 单个元素 ✔OK 1. 0.096 秒 2. 0.096 秒 3. 0.096 秒 OK 4. 0.096秒OK
▶ 简单 简单测试✔OK 1. 0.100 秒确定 2. 0.096 秒 3. 0.100秒OK
▶extreme_min_max_value 最小值和最大值 ✔OK 1. 0.096 秒 2. 0.100秒OK
▶ positive_only 0...100 然后 102...200 的随机序列 ✔OK 1. 0.096 秒 2. 0.096秒OK
▶negative_only 随机序列 -100 ... -1 ✔OK 1. 0.096 秒 折叠所有性能测试
▶ 中等 混沌序列长度=10005(带减号)✘TIMEOUT ERROR 运行时间:0.116 秒,时限:0.112 秒。 1. 0.116 s TIMEOUT ERROR,运行时间:0.116 秒,时限:0.112 秒。 2. 0.116 s TIMEOUT ERROR,运行时间:0.116 秒,时限:0.100 秒。 3. 0.124秒OK
▶ 大_1 混乱 + 序列 1, 2, ..., 40000(没有减号)✔OK 1. 0.340秒OK
▶大_2 改组后的序列 1, 2, ..., 100000(没有减号) ✔OK 1. 0.408 秒 2. 0.372秒OK
▶ 大_3 混乱+许多-1、1、2、3(带减号)✔OK 1. 0.272秒OK
结论:
- 对于小型测试阵列,它几乎和第一个版本一样慢,因此这里的 IntStream 似乎是瓶颈。
- 对于大型测试阵列,速度似乎是中等的。因此,我不确定它应该告诉我什么。
编辑 2:
我实际上只是找到了一篇部分描述该问题的文章:https://jaxenter.com/java-performance-tutorial-how-fast-are-the-java-8-streams-118830.html。其中,作者声称流和在数组上运行的 for 循环之间的差异是由于流是相当新的事实。但是这篇文章的日期是 4 年前。
【问题讨论】:
-
你为什么认为流应该胜过普通的旧循环?
-
您的流解决方案中有很多装箱/拆箱。
-
@AndrewTobilko 我没有声称,我希望流更快。但是,我预计可能会有一些百分比的差异,因此对于大多数现实世界的应用程序来说并不有趣。但是两个数量级,甚至是三倍,都超出了我的预期。
-
@LutzHorn 我无权访问支持的 java 进程。它是由 codility 运行的。
-
@Nexevis 是的,但我将其与基元数组进行比较。
标签: java java-8 performance-testing primitive-types