Reputation: 662
I have 2 lists like:
List<Double> margins = Arrays.asList(1.0,2.0,3.0,4.0,5.0);
List<Integer> quantity = Arrays.asList(1,2,3,4,5);
I want to do element wise multiplication between them, I know I can do it with standard for loop, but I was wondering if we can achieve the same via the Stream API by making the process faster and less resource heavy?
Something like what Python ML does with NumPy arrays; instead of making a for
loop they vectorize the thing which makes it faster.
Upvotes: 3
Views: 585
Reputation: 28978
Something like what Python ML does with numpy arrays; instead of making a
for
loop they vectorize the thing which makes it faster.
If you're interested in Vectorization, then Stream API is not what you're looking for.
Since Java 16 we have Vector API as an incubating feature (meaning that it's not the final state of the API, it's there for testing and collecting feedback, and you shouldn't use it in production).
In order to use incubating features in Java, you need to do some extra work to get files from an incubating module imported.
One of the ways to make sure that classes from the package jdk.incubator.vector
would be imported is to create a module to explicitly specify that it requires this package.
Consider a simple test project having the following folder-structure:
- [src]
- [main]
- [java]
- module-info.java
- [vectorization.test]
- Main.java
- resources
// other things
module-info.java
- here we're requesting Vector API's files:
module vectorization.test {
requires jdk.incubator.vector;
}
Main.java
package vectorization.test;
import jdk.incubator.vector.DoubleVector;
import jdk.incubator.vector.VectorSpecies;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
double[] margins = {1.0, 2.0, 3.0, 4.0, 5.0};
double[] quantity = {1, 2, 3, 4, 5};
double[] result = new double[margins.length];
multiply(margins, quantity, result);
System.out.println(Arrays.toString(result));
}
public static final VectorSpecies<Double> SPECIES = DoubleVector.SPECIES_PREFERRED;
public static void multiply(double[] margins, double[] quantity, double[] result) {
int i = 0;
for (; i < SPECIES.loopBound(margins.length); i += SPECIES.length()) {
DoubleVector marginsVector = DoubleVector.fromArray(SPECIES, margins, i);
DoubleVector quantityVector = DoubleVector.fromArray(SPECIES, quantity, i);
DoubleVector resultVector = marginsVector.mul(quantityVector);
resultVector.intoArray(result, i);
}
for (; i < margins.length; i++) {
result[i] = margins[i] * quantity[i];
}
}
}
Output:
[1.0, 4.0, 9.0, 16.0, 25.0]
For more information related to the Vector API, have a look at the JEP 426.
Speaking of Streams they are not more performant than plain loop (if case of sequential streams), in fact they are slower because streams require creation of additional objects to perform iteration, to make transformation, and to accumulate the result. No magic here.
Parallel streams might be faster, but it's a tool which should be used with care because you also might get the opposite effect. You need your task to be parallelizable, there should free CPU cores for your threads to do the job, and amount of data should be massive to make the usage of parallel streams justifiable (and still have to measure the performance to find out whether it makes difference in your case).
One of the main goals of introducing Functional programming features in Java was to provide a concise and well readable way of structuring the code.
For instance, that how you can populate your resulting array of products by using Arrays.setAll()
:
Arrays.setAll(result, i -> margins[i] * quantity[i]);
Upvotes: 3
Reputation: 6840
You can multiply the elements of your arrays with each other via stream, but it won't be more efficient than a traditional loop. However, for such small sets of data the overhead is so small it won't make any difference.
Here is an implementation via stream. Basically, you could use the IntStream
class to iterate both of your lists from 0 to the size of the smallest list (excluded). Then, map each int index to a Double object representing the product of the i-th element of the margins
list with the i-th element of the quantity
list and finally collect the results into a List<Double>
.
List<Double> margins = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0);
List<Integer> quantity = Arrays.asList(1, 2, 3, 4, 5);
List<Double> res = IntStream.range(0, Math.min(margins.size(), quantity.size()))
.mapToObj(i -> margins.get(i) * quantity.get(i))
.collect(Collectors.toList());
Upvotes: 2