3

Let's start with the following lists :

List<Double> firstList = new ArrayList<>();
firstList.add(2.0);
firstList.add(3.0);
List<Double> secondList = new ArrayList<>();
secondList .add(2.0000000001);
secondList .add(2.99999999994);

I know I can compare each element one by one using brute force. Of course, I already checked that both lists have the same number of elements.

boolean isEqual = true;
for (int i = 0; i < firstList.size(); i++) {
  isEqual &= Math.abs(firstList.get(i) - secondList.get(i)) < 1.0e-6;
}
return isEqual;

My question : is there a way to compare these two lists of double values using lambda expressions? It seems to be easy with any other type of objects, but not with doubles. I need to check if the two lists are numerically equal.

Thanks in advance.

Francois
  • 586
  • 2
  • 6
  • 19

3 Answers3

3

Given the statement:

Of course, I already checked that both lists have the same number of elements.

Then you can accomplish the same result with IntStream.range along with allMatch like so:

boolean isEqual =  firstList.isEmpty() || 
         IntStream.range(0, firstList.size())
           .allMatch(i -> Math.abs(firstList.get(i) - secondList.get(i)) < 1.0e-6);

Another solution using reduce:

BiPredicate<Double, Double> biPredicate = (e, a) -> Math.abs(e - a) < 1.0e-6;
boolean isEqual = 
         IntStream.range(0, firstList.size())
                  .boxed()
                  .reduce(Boolean.TRUE,
                    (accumulator, i) -> Boolean.logicalAnd(accumulator, biPredicate.test(firstList.get(i), secondList.get(i))),
                    (a, b) -> {
                        throw new RuntimeException("not implemented");
                    });

I've intentionally left the third parameter (the combiner) to reduce unimplemented as it will not be called at all. Reason being that this specific overload was designed to be used with a parallel stream, so in order for a combiner to work, a stream must be parallel.

Yet, it's still necessary for us to use this overload in order to accomplish the task at hand.

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
  • I don't understand why you have to use combiner field. Can't you use just `T reduce(T identity, BinaryOperator accumulator);`? – Michał Zabielski Mar 29 '18 at 19:54
  • 1
    @MichałZabielski that overload states that the `identity`, as well as the parameters of the `accumulator` _must all, be the same type_. if I was to proceed with that approach the code wouldn't compile. Hence the need for this overload which allows having the `identity` be a different type than the elements in the stream. in this case, `accumulator` is of type `Boolean` and `i` of type `Integer`. This enables me to fold into a boolean result as well as having access to the index. – Ousmane D. Mar 29 '18 at 20:01
  • @MichałZabielski see the signature of the overload [you've suggested](https://docs.oracle.com/javase/9/docs/api/java/util/stream/Stream.html#reduce-T-java.util.function.BinaryOperator-) and the overload [I am using](https://docs.oracle.com/javase/9/docs/api/java/util/stream/Stream.html#reduce-U-java.util.function.BiFunction-java.util.function.BinaryOperator-). – Ousmane D. Mar 29 '18 at 20:02
  • 1
    Ok, now i see it. In fact you are performing two steps in reduce: comparing `double` from two lists, and then reducing it to one boolean. Nice to know this technique with `combiner` field:) – Michał Zabielski Mar 29 '18 at 20:04
  • I would never use the 2nd approach. It's not only longer, but also *non short-circuiting*. If one pair of elements don't satisfy the condition, why keep on checking until the last one? – fps Mar 30 '18 at 14:56
2

In functional languages you can use zip function to zip two lists (streams) and then use map or reduce to manipulate merged list using lambdas. Unfortunately Java doesn't have such function out-of-the-box. However, you can use Google Guava Streams to zip two lists and obtain result with lambda (functional style):

    BiFunction<Double, Double, Boolean> zippingFunction = (a, b) -> Math.abs(a - b) < 1.0e-6;

    boolean result = Streams.zip(firstList.stream(), secondList.stream(), zippingFunction)
            .reduce(true, Boolean::logicalAnd);
  • nice!. you can achieve the same thing without an external library, although it may not be as neat. I've provided it in my post :) – Ousmane D. Mar 29 '18 at 19:53
0

Sure. Create an interface with a compare method that takes two List and returns boolean. Wrap your existing code in a lambda expression.

Steve11235
  • 2,849
  • 1
  • 17
  • 18