Reputation: 787
I have a specific question. There are some similar questions but these are either with Python, not with Java, or the requirements are different even if the question sounds similar.
I have a list of values.
List1 = {10, -2, 23, 5, -11, 287, 5, -99}
At the end of the day, I would like to split lists based on their values. I mean if the value is bigger than zero, it will be stay in the original list and the corresponding index in the negative values list will be set zero. If the value is smaller than zero, it will go to the negative values list and the negative values in the original list will be replaced with zero.
The resulting lists should be like that;
List1 = {10, 0, 23, 5, 0, 287, 5, 0}
List2 = {0, -2, 0, 0, -11, 0, 0, -99}
Is there any way to solve this with Stream api in Java?
Upvotes: 40
Views: 64834
Reputation: 851
Map<Boolean, List<Integer>> results =
list1.stream().collect(Collectors.partitioningBy(n -> n < 0));
I think that this one is prettier and easy to read. (You can then get the negative and non-negative list from the map.)
Upvotes: 85
Reputation: 4769
I have found two solutions for splitting the stream based on condition
Your List is List1 = {10, -2, 23, 5, -11, 287, 5, -99}
List1.stream().collect(Collectors.partitioningBy(num -> num <0))
This would give a Map with below key values
"true" : 10,24,5,287,5
"false": -2,-11,-99
Another solution is on the same line, but instead of using partitioningBy
, use groupingBy
List2.stream().collect(Collectors.groupingBy(num -> num <0))
Upvotes: 0
Reputation: 3134
Since Java 12 it can be done very simple by using Collectors::teeing
:
var divided = List.of(10, -2, 23, 5, -11, 287, 5, -99)
.stream()
.collect(Collectors.teeing(
Collectors.mapping(i -> Math.max(0, i), Collectors.toList()),
Collectors.mapping(i -> Math.min(0, i), Collectors.toList()),
List::of
));
Upvotes: 14
Reputation: 1155
There are pros and cons in each solution.
for
loop is the obvious answer, but your question explicitly
mentions Streams API. Collector
is difficult to implement, and gives the impression of redundant work while the problem seems so straightforward, even naïve. I haven't seen anyone else mentioning this, but you can collect your numbers in a Map<Boolean,List<Integer>>
map, where key corresponds to your grouping criterion, and List
is the selection of items matching the criterion, for example:
List<Integer> numbers = List.of(10, -2, 23, 5, -11, 287, 5, -99);
Map<Boolean, List<Integer>> numbersByIsPositive = numbers.stream()
.collect(Collectors.groupingBy(number -> number >= 0));
List<Integer> positiveNumbers = numbersByIsPositive.get(true);
List<Integer> negativeNumbers = numbersByIsPositive.get(false);
Think of auto-boxing and -unboxing when applying this approach.
Output:
Positive numbers: [10, 23, 5, 287, 5]
Negative numbers: [-2, -11, -99]
Upvotes: 6
Reputation: 34460
A generic solution without streams might consist of choosing between two possible consumers, based on a condition:
private static <T> Consumer<T> splitBy(
Predicate<T> condition,
Consumer<T> action1,
Consumer<T> action2,
T zero) {
return n -> {
if (condition.test(n)) {
action1.accept(n);
action2.accept(zero);
} else {
action1.accept(zero);
action2.accept(n);
}
};
}
For your specific problem, you could use the splitBy
method as follows:
List<Integer> list = Arrays.asList(10, -2, 23, 5, -11, 287, 5, -99);
List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
list.forEach(splitBy(n -> n > 0, list1::add, list2::add, 0));
System.out.println(list1); // [10, 0, 23, 5, 0, 287, 5, 0]
System.out.println(list2); // [0, -2, 0, 0, -11, 0, 0, -99]
Upvotes: 5
Reputation: 120858
Well you could do that in place:
List<Integer> left = Arrays.asList(10, -2, 23, 5, -11, 287, 5, -99);
int[] right = new int[left.size()];
IntStream.range(0, left.size())
.filter(i -> left.get(i) < 0)
.forEach(x -> {
right[x] = left.get(x);
left.set(x, 0);
});
System.out.println(left);
System.out.println(Arrays.toString(right));
That is a side-effect, but as far as I can tell, it is a safe side-effect.
Upvotes: 4
Reputation: 2344
As shmosel already pointed out in the comments, you'll need two iterations using streams:
List<Integer> list = Arrays.asList(10, -2, 23, 5, -11, 287, 5, -99);
List<Integer> positives = list.stream().map(i -> i < 0 ? 0 : i).collect(Collectors.toList());
List<Integer> negatives = list.stream().map(i -> i < 0 ? i : 0).collect(Collectors.toList());
All in one stream is possible if your list is modifiable. This is not better than a for-loop
List<Integer> list = Arrays.asList(10, -2, 23, 5, -11, 287, 5, -99);
List<Integer> list2 = new ArrayList<>();
IntStream.range(0, list.size()).forEach(i -> {
int j;
if ((j = list.get(i)) < 0) {
list2.add(j);
list.set(i, 0);
} else {
list2.add(0);
}});
Upvotes: 8
Reputation: 15622
Java-Streams are a functional programming feature.
The essential pattern of functional programming is that you convert one collection to one other collection. This means your requirement is does not suit to a functional approach and hence java streams are the second best solution (after legacy for(each) loop).
But
Of cause you can split the problem into two separate FP friendly operations.
The downside it that this requires an additional loop over the input collection.
For small collections (up to roughly 100000 items) this may not be a problem but for bigger collections you may raise a performance issue.
Disclaimer: do not choose or deny an approach for performance reasons unless you have justified your decision by measurement with a profiling tool!
I'd consider the "legacy loop" the better approach since it may be more readable in the sense that it better expresses your intent (to split up the collection).
Upvotes: 6
Reputation: 298213
If you want to do it in a single Stream operation, you need a custom collector:
List<Integer> list = Arrays.asList(10, -2, 23, 5, -11, 287, 5, -99);
List<List<Integer>> result = list.stream().collect(
() -> Arrays.asList(new ArrayList<>(), new ArrayList<>()),
(l,i) -> { l.get(0).add(Math.max(0, i)); l.get(1).add(Math.min(0, i)); },
(a,b) -> { a.get(0).addAll(b.get(0)); a.get(1).addAll(b.get(1)); });
System.out.println(result.get(0));
System.out.println(result.get(1));
Upvotes: 19