Soumalya Bhattacharya
Soumalya Bhattacharya

Reputation: 662

How to use Java Streams API to do element wise multiplication between 2 lists of same dimension

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

Answers (2)

Alexander Ivanchenko
Alexander Ivanchenko

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

dani-vta
dani-vta

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

Related Questions