This is a simple task from some website.
If a[i] > b[i], then Alice is awarded 1 point. If a[i] < b[i], then Bob is awarded 1 point. If a[i] = b[i], then neither person receives a point. Return: Alice's score is in the first position, and Bob's score is in the second.
Can I write this code using stream?
public static List<Integer> compareTriplets(List<Integer> a, List<Integer> b) {
int alice = 0;
int bob = 0;
for (int i = 0; i < a.size(); i ) {
if (a.get(i) > b.get(i))
alice ;
else if (a.get(i) < b.get(i))
bob ;
}
return Arrays.asList(alice, bob);
}
...
After a little thought, I wrote this code:
public static List<Integer> compareTriplets(List<Integer> a, List<Integer> b) {
Integer[] result = {0, 0};
IntStream.range(0, Math.min(a.size(), b.size()))
.map(i -> a.get(i).compareTo(b.get(i)))
.forEach(i -> {
if (i > 0) result[0] ;
else if (i < 0) result[1] ;
});
return Arrays.asList(result);
}
But this code is not much more concise. Are there ways to make the code more beautiful?
CodePudding user response:
You can make the code shorter with filter
and count
, but using loops is cleaner here. Stream
s are not always the solution.
return Arrays.asList(
IntStream.range(0, Math.min(a.size(), b.size())).filter(i -> a.get(i) > b.get(i)).count(),
IntStream.range(0, Math.min(a.size(), b.size())).filter(i -> a.get(i) < b.get(i)).count()
);
CodePudding user response:
Java 8 - partitioningBy()
& counting()
One of the way to solve this problem with streams performing only one iteration over the given set of data is to make use of the built Collectors partitioningBy()
and counting()
:
public static List<Integer> compareTriplets(List<Integer> a, List<Integer> b) {
return IntStream.range(0, a.size())
.map(i -> a.get(i) - b.get(i))
.filter(i -> i != 0)
.boxed()
.collect(Collectors.collectingAndThen(
Collectors.partitioningBy(i -> i > 0, Collectors.counting()),
map -> Arrays.asList(map.get(true).intValue(), map.get(false).intValue())
));
}
Java 8 - custom Collector
Another option would be to define a custom Collector using static factory method Collector.of()
. As well as previous approach, it would allow to process the data using a single stream:
public static List<Integer> compareTriplets(List<Integer> a, List<Integer> b) {
return IntStream.range(0, a.size())
.boxed()
.collect(Collector.of(
() -> new int[]{0, 0},
(int[] score, Integer i) -> {
if (a.get(i) > b.get(i)) score[0] ;
if (b.get(i) > a.get(i)) score[1] ;
},
(int[] left, int[] right) -> {
Arrays.setAll(left, i -> left[i] right[i]);
return left;
},
arr -> Arrays.asList(arr[0], arr[1])
));
}
Java 12 - Collector teeing()
Another option that would allow to produce the result using a single stream is Java 12 Collector teeing()
, which expects two downstream Collectors and a Function which performs a final transformation by merging the results they produced.
public static List<Integer> compareTriplets(List<Integer> a, List<Integer> b) {
return IntStream.range(0, a.size())
.boxed()
.collect(Collectors.teeing(
Collectors.filtering(i -> a.get(i) > b.get(i), Collectors.counting()),
Collectors.filtering(i -> b.get(i) > a.get(i), Collectors.counting()),
(alice, bob) -> List.of(alice.intValue(), bob.intValue())
));
}
CodePudding user response:
This could well be overkill for your case but you could handle this as a stream reduction:
class Counter {
private int bob = 0;
private int alice = 0;
public void accept(int index) {
if (a[index] > b[index])
alice ;
else if (a[index] < b[index])
bob ;
}
}
Counter counter = IntStream.range(0, a.size()).boxed().reduce(new Counter(), Counter::accept);
Another alternative is to take advantage of Integer.compare
:
List<Integer> comparisons = IntStream.range(0, a.size()).map(i -> Integer.compare(a[i], b[i]).toList();
long bob = comparisons.stream().filter(n -> n < 0).count();
int alice = comparisons.stream().filter(n -> n > 0).count();