1

I was trying to implement a program from Coursera. The program statement is:

Given a sequence of non-negative integers a0,…,an−1, find the maximum pairwise product, that is, the largest integer that can be obtained by multiplying two different elements from the sequence.

I was trying out various ways to implement the solution and checking which works best.

I see that streams are still fast.

Why is it like that?

I was under the impression, then finding the largest 2 elements without sorting and multiplying them would be the best. Stream sorting (sequential stream is faster than parallel stream) is giving better result than this approach?

My program

    import java.util.ArrayList;
    import java.util.Comparator;
    import java.util.List;
    import java.util.Random;
    import java.util.stream.Collectors;

    public class MaxProduct {
        public static void main(String[] args) {
            List<Long> testList = createRandomArrayOfSize(30000000);

            warmup(testList);
            System.out.println("Warmup done");

            Measure.meaureTime("No sorting, finding largest 2 elements and multiplying",
                    () -> findMaxProductByFindingLargest2Elements(new ArrayList<>(testList)));
            Measure.meaureTime("Sorting, Multiplying last 2 elements",
                    () -> findMaxProductUsingSorting(new ArrayList<>(testList)));
            Measure.meaureTime("Sorting using streams, Multiplying last 2 elements",
                    () -> findMaxProductUsingStreamSorting(new ArrayList<>(testList)));
            Measure.meaureTime("Parallel sorting using streams, Multiplying last 2 elements by reduction",
                    () -> findMaxProductReducingParallelStream(new ArrayList<>(testList)));
        }

        public static long findMaxProductByFindingLargest2Elements(List<Long> list) {
            long largest = 0;
            long secondlargest = 0;
            for (Long no : list) {
                if (no > largest) {
                    secondlargest = largest;
                    largest = no;
                }
            }
            return largest * secondlargest;
        }

        public static long findMaxProductUsingSorting(List<Long> list) {
            list.sort(Comparator.naturalOrder());
            return list.get(list.size() - 1) * list.get(list.size() - 2);
        }

        public static long findMaxProductUsingStreamSorting(List<Long> list) {
            List<Long> collect = list.stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList());
            return collect.get(list.size() - 1) * collect.get(list.size() - 2);
        }

        public static long findMaxProductReducingParallelStream(List<Long> list) {
            return list.parallelStream().sorted(Comparator.reverseOrder()).limit(2).reduce((x, y) -> x * y).get();
        }

        private static class Measure {
            private static final int NO_OF_ITERATIONS = 3;

            private static void meaureTime(String description, Runnable runnable) {
                long startTime = System.nanoTime();
                for (int i = 0; i < NO_OF_ITERATIONS; i++) {
                    runnable.run();
                }
                long timeTakenInNanos = System.nanoTime() - startTime;
                System.out.println(description + " : " + (timeTakenInNanos / 3));
            }
        }

        private static void warmup(List<Long> testList) {
            findMaxProductByFindingLargest2Elements(new ArrayList<>(testList));
            findMaxProductUsingSorting(new ArrayList<>(testList));
            findMaxProductUsingStreamSorting(new ArrayList<>(testList));
            findMaxProductReducingParallelStream(new ArrayList<>(testList));
        }

        private static List<Long> createRandomArrayOfSize(int size) {
            return new Random().longs(size, 1, 10000000).boxed().collect(Collectors.toList());
        }
    }

Updated Results

No sorting, finding largest 2 elements and multiplying : 778547847
Sorting, Multiplying last 2 elements : 13423659115
Sorting using streams, Multiplying last 2 elements : 15518997158
Parallel sorting using streams, Multiplying last 2 elements by reduction : 5405983848

Updated the code

There is a bug in the first approach. If the largest no is at the zeroth index, then product will come as zero. Need to fix that.

Streams are not fast
Was a mistake in my test code. Fixed the mistake. And now working as expected.

Holger
  • 285,553
  • 42
  • 434
  • 765
Paul Nibin
  • 726
  • 3
  • 10
  • 18
  • https://www.coursera.org/learn/algorithmic-toolbox – Paul Nibin Aug 11 '16 at 15:37
  • 1
    I still see a few issues. 1) Your list gets sorted after calling `warmup` 2) The functions you compare do not give the same result. 3) See http://stackoverflow.com/q/504103/2568885 on how to write microbenchmarks. – binoternary Aug 11 '16 at 16:06
  • @binoternary Thank you. Fixed. Now results look fine. Will use JMH or caliper, when I do these things. – Paul Nibin Aug 11 '16 at 16:18
  • Also `findMaxProductReducingParallelStream` is not used at all here, but `findMaxProductUsingStreamSorting` is used twice. – binoternary Aug 11 '16 at 16:20

2 Answers2

2

Unfortunately stream is not necessarily fast.

findMaxProductUsingStreamSorting specifies a Stream, but does not realize it with collect or findFirst. It does not alter the list. Hence only the product of two list gets will be done. The result will be wrong too.

With Stream it is that the iteration is done late, so sort, filter and such may make for clever execution.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • Thank you. Fixed that mistake also. Now streams are not fast anymore. Need to work on my code review skills. – Paul Nibin Aug 11 '16 at 15:51
0

Your test is flawed. The time you run the stream-test the list is already sorted, that's why it is that fast.

macsniper
  • 330
  • 1
  • 11