提问人:Ancient Darth 提问时间:10/31/2023 最后编辑:Ancient Darth 更新时间:11/13/2023 访问量:76
3 个嵌套的 for-each 循环作为 Java 流
3 nested for-each loops as Java Stream
问:
在我目前正在处理的项目中,我们有三个嵌套的 for 循环的构造:
final Vector3i startPos = new Vector3i(-3, -2, -3);
final Vector3i finishPos = new Vector3i(3, 4, 3);
final Vector3i currentPos = entity.getPos();
final List<Vector3i> entityFinderZone = new ArrayList<>();
for (int x = startPos.getX(); x <= finishPos.getX(); x++)
{
for (int y = startPos.getY(); y <= finishPos.getY(); y++)
{
for (int z = startPos.getX(); z <= finishPos.getZ(); z++)
{
Vector3i newPos = currentPos.offset(x ,y ,z);
if (newPos.getX() == 0 && newPos.getZ() == 0 && (newPos.getY() < -1 || newPos.getY() > 2))
{
entityFinderZone.add(newPos);
}
}
}
}
我想用流来做这一切以使其更快,但我遇到错误
final IntStream xCoords = IntStream.iterate(startPos.getX(), i -> i <= finishPos.getX(), i -> i + 1);
final IntStream yCoords = IntStream.iterate(startPos.getY(), i -> i <= finishPos.getY(), i -> i + 1);
final IntStream zCoords = IntStream.iterate(startPos.getZ(), i -> i <= finishPos.getZ(), i -> i + 1);
entityFinderZone = xCoords.
flatMap( x -> yCoords.
flatMap(y -> zCoords.
flatMap(z -> currentPos.offset(x, y, z)))).collect(Collectors.toList());
错误:
Bad return type in lambda expression: Vector3i cannot be converted to int
我做错了什么?
答:
我想用流来做这一切,让它更快
好。我将把你的问题解释为“我如何让它更快”。
对于初学者;不是通过使用 lambda。这并不能使任何事情变得更快。Lambdas,最佳情况下,可以让你更容易地并行化一个任务,但这并不是免费的,而且几乎总是,如果一个任务对性能至关重要,以至于它必须并行化,你希望控制这个过程,而不仅仅是要求流很好地尝试它(这是你所能做的 - 没有保证,也没有关于它如何做的特定结构)。
一般来说,“我会使用 lambda,因为它们更快”是一种不正确的心态。
一般来说,“我会使用 lambda,因为它们更好”是一种不正确的心态。
正确的心态是:“我可以用 lambda 来做到这一点。如果我能预见到代码将更易于阅读和更易于维护(即不使用循环推理,例如“每个人都知道 lambda 更容易阅读,因此使用 lambda 会使它更容易阅读,QED”)——那么我将使用它们。如果没有,我不会”。
if (newPos.getX() == 0 && newPos.getZ() == 0 && (newPos.getY() < -1 || newPos.getZ() > 2))
如果这就是您想要做的全部,三重循环会使效率低得令人难以置信。
如果这没有触发,你的循环什么都不做。仅看 if,我们有几个布尔属性:if
对于范围内的一个或零个数,的 x 偏移量(对于函数来说是常数)是 0。如果没有这样的数字,那么这种方法什么都不做,我们“什么都不做”的最快方法是尽可能快地返回。否则,循环播放绝对没有意义 - 除了那个数字之外,什么都不会发生。
[startPos.x, finishPos.x]
[startPos.x, finishPos.x]
startPos.x-finishPos.x
完全相同的逻辑也适用于范围。
[startPos.z, finishPos.z]
对于组件来说,不可能是真的——毕竟,newPos.getZ() 必须是真的,否则我们甚至不会到达这里。 表示 AND:所有 3 个条件都必须成立。因此,这归结为 ,这意味着有一个子范围 。我们确实必须循环 - 我们可以减少该循环的大小。
y
newPos.getZ() > 2
0
&&
newPos.getY() < -1
[startPos.y, endPos.y]
这使得您的 3 深度循环成为 1 深度循环,效率大大提高。这就是你如何让它更快。用另一种代码结构替换一个代码结构不会产生任何算法复杂性差异,甚至不会给你带来恒定的(即大部分无关紧要,但仍然)速度提升——如果有的话,它会减慢速度。
是的,那么我该如何让它更快呢?
计算使 newPos 的 x 分量为 0 的范围内的奇异 x 值。这是一个简单的加法/减法,然后是一个范围检查。不涉及任何循环。
对 z 执行相同的操作。
现在循环,仅适用于 .一旦 y “超出范围”(为 0 或向上),就突破 - 无论如何它都不会回落到范围。
y
newPos.getY()
使用基本循环(没有 lambda),这样事情就简单得多。
首先,您似乎使用了错误的变量 startPos 而不是 startPose,并且似乎您正在尝试将 newPos(类型为 Vector3i)添加到 entityFinderZone 列表中,该列表需要 int 类型的元素。
要修复此错误,您可以修改 lambda 表达式以从 newPos 中提取所需的值并将其添加到列表中。下面是代码的更新版本:
for (int x = startPose.getX(); x <= finishPos.getX(); x++) {
for (int y = startPose.getY(); y <= finishPos.getY(); y++) {
for (int z = startPose.getZ(); z <= finishPos.getZ(); z++) {
Vector3i newPos = currentPos.offset(x, y, z);
if (newPos.getX() == 0 && newPos.getZ() == 0 && (newPos.getY() < -1 || newPos.getZ() > 2)) {
entityFinderZone.add(newPos);
}
}
}
请尝试以下操作。
final List<Vector3i> entityFinderZone = new ArrayList<>();
IntStream.rangeClosed(startPos.getX(), finishPos.getX())
.forEach(x -> IntStream.rangeClosed(startPos.getY(), finishPos.getY())
.forEach(y -> IntStream.rangeClosed(startPos.getZ(), finishPos.getZ())
.forEach(
z -> {
Vector3i newPos = currentPos.offset(x, y, z);
if (newPos.getX() == 0 && newPos.getZ() == 0 && (newPos.getY() < -1 || newPos.getY() > 2))
entityFinderZone.add(newPos);
})));
评论
blockFinderZone