3 个嵌套的 for-each 循环作为 Java 流

3 nested for-each loops as Java Stream

提问人:Ancient Darth 提问时间:10/31/2023 最后编辑:Ancient Darth 更新时间:11/13/2023 访问量:76

问:

在我目前正在处理的项目中,我们有三个嵌套的 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

我做错了什么?

Java 嵌套循环

评论

0赞 Henning Luther 10/31/2023
z -> currentPos.offset(x, y, z))) 是一个向量,右边
0赞 Dan Getz 10/31/2023
什么类型?blockFinderZone
3赞 rzwitserloot 10/31/2023
我想用流来做这一切,让它更快如果有的话,它会更慢。如果你在“流总是更好”的假设下运作 - 不。它们是工具箱中的工具。目前,一个被过度炒作和过度使用的工具。
0赞 Henning Luther 10/31/2023
@rzwitserloot这是真的。这是一个错误的假设,认为它很快:这取决于!stackoverflow.com/questions/22658322/......
0赞 Ancient Darth 10/31/2023
@DanGetz它只是 list<Vector3i>

答:

0赞 rzwitserloot 10/31/2023 #1

我想用流来做这一切,让它更快

好。我将把你的问题解释为“我如何让它更快”。

对于初学者;不是通过使用 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 个条件都必须成立。因此,这归结为 ,这意味着有一个子范围 。我们确实必须循环 - 我们可以减少该循环的大小。ynewPos.getZ() > 20&&newPos.getY() < -1[startPos.y, endPos.y]

这使得您的 3 深度循环成为 1 深度循环,效率大大提高。这就是你如何让它更快。用另一种代码结构替换一个代码结构不会产生任何算法复杂性差异,甚至不会给你带来恒定的(即大部分无关紧要,但仍然)速度提升——如果有的话,它会减慢速度。

是的,那么我该如何让它更快呢?

  1. 计算使 newPos 的 x 分量为 0 的范围内的奇异 x 值。这是一个简单的加法/减法,然后是一个范围检查。不涉及任何循环

  2. 对 z 执行相同的操作。

  3. 现在循环,仅适用于 .一旦 y “超出范围”(为 0 或向上),就突破 - 无论如何它都不会回落到范围。ynewPos.getY()

使用基本循环(没有 lambda),这样事情就简单得多。

0赞 Davidberserker 10/31/2023 #2

首先,您似乎使用了错误的变量 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);  
                }  
            }  
        }
0赞 Reilas 11/13/2023 #3

请尝试以下操作。

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);
                                                           })));