So here's a simple algorithmic problem,
Given a list of integers, check if there are two numbers in this list that when added together give eight (8).
Here's my solution,
import java.util.List;
public class Main {
static List<Integer> arrayOne = List.of(1,3,6,9);
static List<Integer> arrayTwo = List.of(1,6,2,10);
static boolean validateArray(int result, List<Integer> array){
for (int i = 0; i<array.size() - 1; i ){
for (int j = i 1; j < array.size(); j ){
int value1 = array.get(i);
int value2 = array.get(j);
if(value1 value2 == result){
return true;
}
}
}
return false;
}
public static void main(String[] args) {
System.out.println(validateArray(8, arrayTwo));
}
}
This works fine. What I'm trying to learn is how to rewrite this code in Java 8. As in what the different options with the loops in Java 8.
CodePudding user response:
Regardless of the implementation (streams or loops) performing brute-force iterations over the whole list for each element of the list isn't the best way to solve this problem.
We can index the elements by generating a Map of frequencies of elements in the given list. Then we can iterate over the Keys of the Map, checking for each key if the corresponding key, which is equal to result - key
is present in the Map.
There's one edge case, though, that we need to address:
- if
result
is even and there's a single element in the list, which is equal toresult / 2
checking ifresult - key
is present in the map is not sufficient and in this case we need to change the value associated with the key.
If you want to use Stream API firstly to generate a Map you can use combination of Collectors groupingBy()
and counting()
. Then create a stream over the Keys of the Map and apply anyMatch()
operation to obtain the boolean
result:
static boolean validateArray(int result, List<Integer> array) {
Map<Integer, Long> countByNum = array.stream()
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()
));
return countByNum.keySet().stream()
.anyMatch(key -> key * 2 == result ?
countByNum.get(key) >= 2 : countByNum.containsKey(result - key)
);
}
CodePudding user response:
If you use a bit different solution using Set<Integer>
to find an addition complement into the result, then you can easily convert the solution into Stream API.
Iterative approach
Set<Integer> set = Set.copyOf(array);
for (Integer integer : array) {
if (set.contains(result - integer) && (result != 2 * integer)) {
System.out.printf("Found %s %s = %s", integer, result - integer, result);
return true;
}
}
System.out.printf("Found found no two numbers their addition would result in in %s%n", result);
return false;
Stream API approach (with logging)
Set<Integer> set = Set.copyOf(array);
return array.stream()
.filter(integer -> set.contains(result - integer) && (result != 2 * integer))
.findFirst()
.map(integer -> {
System.out.printf("Found %s %s = %s%n", integer, result - integer, result);
return true;
})
.orElseGet(() -> {
System.out.printf("Found found no two numbers their addition would result in in %s%n", result);
return false;
});
And if you don't need to log the results and you care only about the result, the whole stream can be simplified and shortened.
Stream API approach (result only)
Set<Integer> set = Set.copyOf(array);
return array.stream()
.anyMatch(integer -> set.contains(result - integer) && (result != 2 * integer));
Remark:
The algorithm used in all snippets above is simple. You iterate each number in the array and check whether its difference from the result
would be a number found in the Set<Integer>
of the array (constant look-up: O(1)
). To eliminate the currently iterated number (in case the requested result
would be 2 * integer
), such a check is added. This solution assumes there are no duplicated numbers in the input array
. In such a case, the Set<Integer>
shall be used instead and there is no need of a conversion.