【问题标题】:Filling a Multidimensional Array using a Stream使用流填充多维数组
【发布时间】:2014-11-20 22:21:51
【问题描述】:

我是 Java 8 的新手,目前无法完全掌握 Streams,是否可以使用 Stream 函数操作填充数组?这是我将如何使用标准 for 循环执行此操作的示例代码:

public static void testForLoop(){
    String[][] array = new String[3][3];
    for (int x = 0; x < array.length; x++){
        for (int y = 0; y < array[x].length; y++){
            array[x][y] = String.format("%c%c", letter(x), letter(y));
        }
    }               
}

public static char letter(int i){
    return letters.charAt(i);
} 

如果可能的话,我将如何使用 Stream 来做到这一点?如果可能,是否方便(性能和可读性)?

【问题讨论】:

  • 不是很相关,但我认为您的意思是 array[x].length 用于内部循环。
  • 是的,我确实做到了
  • 只需使用标准的 for 循环。您的代码简单明了,对读者来说很明显。尽管流解决方案看起来很优雅,但我没有看到它在这里添加任何东西。
  • @SaintHill 因为实际代码使用具有数万到数十万个不同对象的 3d 数组,我想测试 Stream 方法是否比标准 for-loop 更快:)
  • 阵列的总大小比普通流的 200(可能取决于处理器)要快一些。总大小超过 1000 个并行流,速度要快得多。 (我使用的是 QuadCore i5-3570k,64 位

标签: java arrays functional-programming java-8 java-stream


【解决方案1】:

有几种方法可以做到这一点。

一种方法是在行和列索引上嵌套一对IntStreams

String[][] testStream() {
    String[][] array = new String[3][3];
    IntStream.range(0, array.length).forEach(x -> 
        IntStream.range(0, array[x].length).forEach(y -> 
            array[x][y] = String.format("%c%c", letter(x), letter(y))));
    return array;
}

另一种看起来很有希望的方法是使用Array.setAll 而不是流。这对于为一维数组生成值非常有用:您提供了一个函数,该函数将数组索引映射到要在数组中分配的值。例如,您可以这样做:

String[] sa = new String[17];
Arrays.setAll(sa, i -> letter(i));

不幸的是,多维数组不太方便。 setAll 方法采用 lambda,该 lambda 返回分配给该索引处的数组位置的值。如果您创建了一个多维数组,则较高维已经用较低维数组初始化。您不想分配给它们,但您确实想要 setAll 的隐式循环行为。

考虑到这一点,您可以使用setAll 来初始化多维数组,如下所示:

static String[][] testArraySetAll() {
    String[][] array = new String[3][3];
    Arrays.setAll(array, x -> {
        Arrays.setAll(array[x], y -> String.format("%c%c", letter(x), letter(y)));
        return array[x];
    });
    return array;
}

内部setAll 相当不错,但外部必须有一个语句 lambda,它调用内部setAll 然后返回当前数组。不太漂亮。

我不清楚这两种方法是否比典型的嵌套 for 循环更好。

【讨论】:

    【解决方案2】:

    最好的方法是结合Stuart Marks’ answer这两种方法。

    IntStream.range(0, array.length).forEach(x -> Arrays.setAll(
        array[x], y -> String.format("%c%c", letter(x), letter(y))));
    

    导致解决方案的原因是,“填充多维数组”在 Java 中意味着“迭代外部数组”,然后“填充一维数组”,如 String[][] 只是一个Java 中的 String[] 元素数组。为了设置它们的元素,您必须遍历所有 String[] 元素,并且由于需要索引来计算最终值,因此不能使用 Arrays.stream(array).forEach(…)。所以对于外部数组迭代索引是合适的。

    对于内部数组,搜索的是修改(一维)数组的最佳解决方案。这里,Arrays.setAll(…,…) 是合适的。

    【讨论】:

      【解决方案3】:

      这里有一个解决方案,可以生成数组而不是修改先前定义的变量:

      String[][] array = 
          IntStream.range(0, 3)
                   .mapToObj(x -> IntStream.range(0, 3)
                                           .mapToObj(y -> String.format("%c%c", letter(x), letter(y)))
                                           .toArray(String[]::new))
                   .toArray(String[][]::new);
      

      如果您想使用并行流,那么避免诸如修改变量(数组或对象)之类的副作用非常重要。它可能会导致竞争条件或其他并发问题。您可以在java.util.stream package documentation 中了解更多相关信息 - 请参阅非干扰无状态行为副作用部分。

      【讨论】:

      • 实际上这似乎比我自己找到的最终解决方案更令人费解,不过我应该测试哪个更快。
      • 对 - 它比其他解决方案可读性差,但它的优点是它不会修改外部变量,这在您想要使用并行流时通常非常重要 - 想想竞争条件。跨度>
      • 您可以在java.util.stream 包文档中阅读更多相关信息(无干扰无状态行为副作用 部分)。
      • 我试图了解我在这里缺少什么。 Arrays.setAll 不只是 .mapToObj(...).toArray(...).toArray(...) 的快捷方式吗?
      • @AngeloAlvisi 不,Arrays.setAll 正在遍历传递的数组并设置给定函数产生的值。看源码:hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/…
      【解决方案4】:

      经过工作和测试,这是我的最佳选择:

      IntStream.range(0, array.length).forEach(x -> Arrays.setAll(array[x], y -> builder.build2Dobject(x, y)));
      

      (在我建议的具体情况下:

      IntStream.range(0, array.length).forEach(x -> Arrays.setAll(array[x], y -> String.format("%c%c", letter(x), letter(y)));
      

      对于 3d 数组,它很简单:

      IntStream.range(0, array.length).forEach(x -> IntStream.range(0, array[x].length).forEach(y -> Arrays.setAll(array[x][y], z -> builder.build3Dobject(x, y, z))));
      

      这是让程序选择最快选项的代码:

      public static void fill2DArray(Object[][] array, Object2DBuilderReturn builder){
          int totalLength = array.length * array[0].length;
          if (totalLength < 200){
              for(int x = 0; x < array.length; x++){
                  for (int y = 0; y < array[x].length; y++){
                      array[x][y] = builder.build2Dobject(x, y);
                  }
              }
          } else if (totalLength >= 200 && totalLength < 1000){
              IntStream.range(0, array.length).forEach(x -> Arrays.setAll(array[x], y -> builder.build2Dobject(x, y))); 
          } else {
              IntStream.range(0, array.length).forEach(x -> Arrays.setAll(array[x], y -> builder.build2Dobject(x, y))); 
          }
      }
      

      功能界面:

      @FunctionalInterface
      public interface Object2DBuilderReturn<T> {
          public T build2Dobject(int a, int b);
      }
      

      【讨论】:

        猜你喜欢
        • 2023-02-11
        • 2011-01-11
        • 1970-01-01
        • 2019-03-15
        • 2023-03-25
        • 2013-02-20
        • 1970-01-01
        相关资源
        最近更新 更多