Filling a Multidimensional Array using a Stream

两盒软妹~` 提交于 2019-12-01 16:31:25

Here you have a solution that produces the array instead of modifying a previously defined variable:

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

If you want to use parallel streams then it's very important to avoid side effects like modifications of a variable (array or object). It might lead to race conditions or other concurrency issues. You can read more about that in java.util.stream package documentation - see Non-interference, Stateless behaviors and Side-effects sections.

Holger

The best way is a combination of the two approaches of Stuart Marks’ answer.

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

The reasoning leading to the solution is that “filling a multi-dimensional array” in Java means, “iterating over the outer array(s)” followed by “filling a one-dimensional array” as String[][] is just a an array of String[] elements in Java. In order to set their elements you have to iterate over all String[] elements and since you need the index to calculate the final value, you can’t use Arrays.stream(array).forEach(…). So for the outer array iterating over the indices is appropriate.

For the inner arrays the search is for the best solution for modifying an (one-dimensional) array. Here, Arrays.setAll(…,…) is appropriate.

There are a couple ways to do this.

One way is with a couple nested IntStreams over the row and column indexes:

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

Another way which seems promising is to use Array.setAll instead of streams. This is great for generating values for a one-dimensional array: you provide a function that maps from the array index to the value you want assigned in the array. For example, you could do this:

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

Unfortunately it's less convenient for multidimensional arrays. The setAll method that takes a lambda that returns a value that's assigned to the array location at that index. If you've created a multidimensional array, the higher dimensions are already initialized with lower dimensional arrays. You don't want to assign to them, but you do want the implicit looping behavior of setAll.

With this in mind, you can use setAll to initialize the multidimensional array like this:

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

The inner setAll is reasonably nice, but the outer one has to have a statement lambda that calls the inner setAll and then returns the current array. Not too pretty.

It's not clear to me that either of these approaches is any better than the typical nested for-loops.

After working and testing around this is the best option I came with:

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

(In the specific case I proposed it would be:

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

for a 3d array it's simply:

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

this is the code that lets the program choose the fastest option:

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

the functional interface:

@FunctionalInterface
public interface Object2DBuilderReturn<T> {
    public T build2Dobject(int a, int b);
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!