Chad
Chad

Reputation: 168

Refactoring into java 8 lambda/stream

when I solve the Subset Sum problem or "P = NP" it takes 5 minutes with the following code. I am really curious to know how much faster it would be if I were using .parallelStream. however I don't understand how to convert the code.

public class MainActivity {
final static Integer[] POPS = {8897109, 12828837, 9461105, 6371773, 5965343, 5946800, 5582170, 5564635, 5268860, 4552402, 4335391, 4296250, 4224851, 4192887, 3439809, 3279833, 3095313, 2812896, 2783243, 2710489, 2543482, 2356285, 2226009, 2149127, 2142508, 2134411};
final static int TOTAL = 100000000;
/**
 * @param args
 */
public static void main(String[] args) {
    Combinations c = new Combinations(POPS, TOTAL);
    c.chooser();
}

}

import java.util.ArrayList;
import org.paukov.combinatorics.Factory;
import org.paukov.combinatorics.Generator;
import org.paukov.combinatorics.ICombinatoricsVector;


public class Combinations {
private Integer[] POPS;
private int TOTAL;

public combinations(Integer[] pops, int total){
    this.POPS = pops;
    this.TOTAL = total;
}

public void chooser(){
    for(int i = 1; i<=POPS.length; i++){
        System.out.println(i);
        ICombinatoricsVector<Integer> initialVector = Factory.createVector(POPS);
        Generator<Integer> gen = Factory.createSimpleCombinationGenerator(initialVector, i);
        for (ICombinatoricsVector<Integer> combination : gen) {
            String temp = combination.toString();
            int size = temp.indexOf("size");
            temp = temp.substring(22, size-3);
            int sum = Adder(temp);
            if (sum == TOTAL){
                System.out.println(temp);
            }
        }
    }
}

public int adder(String combos){
    int total = 0;
    String[] parts = combos.split(", ");
    ArrayList<Integer> nums = new ArrayList<>();
    for(int i = 0; i<parts.length; i++){
        nums.add(Integer.parseInt(parts[i]));
    }
    for(int temp : nums){
        total += temp;
    }

    return total;
}
}

Here is the code with the string stuff taken out. It only takes about 15 seconds now. I realize that .parallelStream() will not reduce its time much, but could someone still at least give me some hints on how to do it.

import java.util.ArrayList;
import java.util.List;

import org.paukov.combinatorics.Factory;
import org.paukov.combinatorics.Generator;
import org.paukov.combinatorics.ICombinatoricsVector;


public class Combinations {
private Integer[] POPS;
private int TOTAL;

public Combinations(Integer[] pops, int total){
    this.POPS = pops;
    this.TOTAL = total;
}

public void chooser(){
    for(int i = 1; i<=POPS.length; i++){
        System.out.println(i);
        ICombinatoricsVector<Integer> initialVector = Factory.createVector(POPS);
        Generator<Integer> gen = Factory.createSimpleCombinationGenerator(initialVector, i);
        for (ICombinatoricsVector<Integer> combination : gen) {
            List<Integer> temp = combination.getVector();
            int sum = adder(temp);
            if (sum == TOTAL){
                System.out.println(temp);
            }
        }
    }
}

public int adder(List<Integer> combos){
    int total = 0;
    for(Integer temp : combos){
        total+=temp;
    }
    return total;
}
}

Upvotes: 0

Views: 714

Answers (1)

Sean Van Gorder
Sean Van Gorder

Reputation: 3453

This runs about three times as fast on my box (i7-2600 with hyper-threading, 8 virtual cores):

public class Combinations {
    private Integer[] POPS;
    private int TOTAL;

    public Combinations(Integer[] pops, int total) {
        this.POPS = pops;
        this.TOTAL = total;
    }

    public void chooser() {
        ICombinatoricsVector<Integer> initialVector = Factory.createVector(POPS);

        IntStream.range(1, POPS.length + 1).parallel()
                .peek(System.out::println)
                .mapToObj(i -> Factory.createSimpleCombinationGenerator(initialVector, i))
                .flatMap(gen -> genToStream(gen, false)
                        .map(ICombinatoricsVector::getVector)
                        .filter(v -> adder(v) == TOTAL))
                .forEach(System.out::println);
    }

    public static int adder(Iterable<Integer> combos) {
        int total = 0;
        for (Integer temp : combos) {
            total += temp;
        }
        return total;
    }

    public static <E> Stream<ICombinatoricsVector<E>> genToStream(Generator<E> gen, boolean parallel) {
        return StreamSupport.stream(Spliterators.spliterator(gen.iterator(),
                gen.getNumberOfGeneratedObjects(), Spliterator.ORDERED), parallel);
    }
}

This uses a parallel stream for the outer loop, a regular stream for the inner loop, and avoids using a stream to sum the list (for speed). You can try a parallel inner stream with genToStream(gen, true), but I didn't see any difference in speed.

Also, if you want a List<List<Integer>> of matches, just change the forEach line to .collect(Collectors.toList());.

Upvotes: 2

Related Questions