Filling a multidimensional array using a stream

I am new to Java 8 and currently do not fully understand streams, is it possible to populate an array using Stream functional operations? This is sample code, as I would do with the standard for the loop:

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

If possible, how can I do this with Stream? If possible, is it convenient (performance and readability)?

+7
java arrays java-8 functional-programming java-stream
source share
4 answers

Here you have a solution that creates an array instead of changing 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, it is very important to avoid side effects such as modifying a variable (array or object). This can lead to race conditions or other concurrency problems. You can learn more about this in the documentation of the java.util.stream package - see Sections "Non-intervention", "Stateless behavior" and "Side effects".

+12
source share

The best way is a combination of the two approaches of Stuart Marx's answer .

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

The motivation for the solution is that “populating a multidimensional array” in Java means “iterating over an external array”, followed by “populating a one-dimensional array” because String[][] is just an array of String[] elements in Java To set their elements, you need to iterate over all the elements of String[] , and since you need an index to calculate the final value, you cannot use Arrays.stream(array).forEach(…) . Thus, for an external array, iterating over indices is appropriate.

For internal arrays, searching is the best solution for modifying a (one-dimensional) array. Here Arrays.setAll(…,…) comes up.

+4
source share

There are several ways to do this.

One way is to have a pair of nested IntStreams by row and column indices:

 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 that 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 displays from the array index the value that you want to assign to the array. For example, you can do this:

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

Unfortunately, this is less convenient for multidimensional arrays. The setAll method, which takes a lambda that returns the value assigned to the location of the array at this index. If you created a multidimensional array, higher sizes are already initialized with lower dimensional arrays. You do not want to assign them, but you want the implicit behavior of the setAll loop.

With that in mind, you can use setAll to initialize a multidimensional array as follows:

 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 good enough, but the outer should have a lambda statement that calls the inner setAll and then returns the current array. Not too pretty.

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

+2
source share

After working and testing around this best option, I came up with:

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

(In a specific case, I assumed that 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 three-dimensional array, 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 allows the program to 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))); } } 

functional interface:

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

All Articles