Split a List into Sublists Based on a Condition With Stream API

Split a list into sublists based on a condition with Stream api

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));

How to separate a List by a condition using Java 8 streams

Here is an example of how you could separate elements (numbers) of this list in even and odd numbers:

List<Integer> myList = Arrays.asList(1,2,3,4,5,6,7,8,9,10);

Map<Boolean, List<Integer>> evenAndOdds = myList.stream()
.collect(partitioningBy(i -> i % 2 == 0));

You would get lists of even/odd numbers like this (either list may be empty):

List<Integer> even = evenAndOdds.get(true);
List<Integer> odd = evenAndOdds.get(false);

You could pass any lambda with required logic in partitioningBy.

Split a list into multiple sublist based on element properties in Java

If you just want to group elements by collectionId you could try something like

List<AnswerCollection> collections = answerRows.stream()
.collect(Collectors.groupingBy(x -> x.collectionId))
.entrySet().stream()
.map(e -> { AnswerCollection c = new AnswerCollection(); c.addAll(e.getValue()); return c; })
.collect(Collectors.toList());

Above code will produce one AnswerCollection per collectionId.


With Java 6 and Apache Commons Collections, the following code produce the same results as the above code using Java 8 streams:

ListValuedMap<Long, AnswerRow> groups = new ArrayListValuedHashMap<Long, AnswerRow>();
for (AnswerRow row : answerRows)
groups.put(row.collectionId, row);
List<AnswerCollection> collections = new ArrayList<AnswerCollection>(groups.size());
for (Long collectionId : groups.keySet()) {
AnswerCollection c = new AnswerCollection();
c.addAll(groups.get(collectionId));
collections.add(c);
}

Splitting List into sublists based on unique values

With Java 8 you can use the groupingBy collector:

Map<String, List<List<String>>> grouped = D.stream()
.collect(Collectors.groupingBy(list -> list.get(1)));
Collection<List<List<String>>> sublists = grouped.values();

or as suggested by @AlexisC:

import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.groupingBy;

Collection<List<List<String>>> sublists = D.stream()
.collect(collectingAndThen(groupingBy(list -> list.get(1)), Map::values));

split a list based on a condition in java using streams

You can use Java 9 takeWhile​(Predicate<? super T> predicate):

List<String> b = a.stream().takeWhile​(item -> !item.equals("something")).collect(Collectors.toList());

Splitting List into sublists along elements

The only solution I come up with for the moment is by implementing your own custom collector.

Before reading the solution, I want to add a few notes about this. I took this question more as a programming exercise, I'm not sure if it can be done with a parallel stream.

So you have to be aware that it'll silently break if the pipeline is run in parallel.

This is not a desirable behavior and should be avoided. This is why I throw an exception in the combiner part (instead of (l1, l2) -> {l1.addAll(l2); return l1;}), as it's used in parallel when combining the two lists, so that you have an exception instead of a wrong result.

Also this is not very efficient due to list copying (although it uses a native method to copy the underlying array).

So here's the collector implementation:

private static Collector<String, List<List<String>>, List<List<String>>> splitBySeparator(Predicate<String> sep) {
final List<String> current = new ArrayList<>();
return Collector.of(() -> new ArrayList<List<String>>(),
(l, elem) -> {
if (sep.test(elem)) {
l.add(new ArrayList<>(current));
current.clear();
}
else {
current.add(elem);
}
},
(l1, l2) -> {
throw new RuntimeException("Should not run this in parallel");
},
l -> {
if (current.size() != 0) {
l.add(current);
return l;
}
);
}

and how to use it:

List<List<String>> ll = list.stream().collect(splitBySeparator(Objects::isNull));

Output:

[[a, b], [c], [d, e]]



As the answer of Joop Eggen is out, it appears that it can be done in parallel (give him credit for that!). With that it reduces the custom collector implementation to:

private static Collector<String, List<List<String>>, List<List<String>>> splitBySeparator(Predicate<String> sep) {
return Collector.of(() -> new ArrayList<List<String>>(Arrays.asList(new ArrayList<>())),
(l, elem) -> {if(sep.test(elem)){l.add(new ArrayList<>());} else l.get(l.size()-1).add(elem);},
(l1, l2) -> {l1.get(l1.size() - 1).addAll(l2.remove(0)); l1.addAll(l2); return l1;});
}

which let the paragraph about parallelism a bit obsolete, however I let it as it can be a good reminder.


Note that the Stream API is not always a substitute. There are tasks that are easier and more suitable using the streams and there are tasks that are not. In your case, you could also create a utility method for that:

private static <T> List<List<T>> splitBySeparator(List<T> list, Predicate<? super T> predicate) {
final List<List<T>> finalList = new ArrayList<>();
int fromIndex = 0;
int toIndex = 0;
for(T elem : list) {
if(predicate.test(elem)) {
finalList.add(list.subList(fromIndex, toIndex));
fromIndex = toIndex + 1;
}
toIndex++;
}
if(fromIndex != toIndex) {
finalList.add(list.subList(fromIndex, toIndex));
}
return finalList;
}

and call it like List<List<String>> list = splitBySeparator(originalList, Objects::isNull);.

It can be improved for checking edge-cases.

How to create 2 lists from one list based on predicate using stram().reduce()

You asked about how to partition a list by using reduce. That's like asking how to hammer a nail using a screwdriver. It would be better to use the correct method for the purpose.

If you can use collect() then you could make use of Collectors.partitioningBy():

Map<Boolean, List<LocalRequestDTO>> partition = localRequestDTOList.stream()
.collect(Collectors.partitioningBy(payToVendor()));
List<LocalRequestDTO> a = partition.get(true);
List<LocalRequestDTO> b = partition.get(false);

Java: how can I split an ArrayList in multiple small ArrayLists?

You can use subList(int fromIndex, int toIndex) to get a view of a portion of the original list.

From the API:

Returns a view of the portion of this list between the specified fromIndex, inclusive, and toIndex, exclusive. (If fromIndex and toIndex are equal, the returned list is empty.) The returned list is backed by this list, so non-structural changes in the returned list are reflected in this list, and vice-versa. The returned list supports all of the optional list operations supported by this list.

Example:

List<Integer> numbers = new ArrayList<Integer>(
Arrays.asList(5,3,1,2,9,5,0,7)
);

List<Integer> head = numbers.subList(0, 4);
List<Integer> tail = numbers.subList(4, 8);
System.out.println(head); // prints "[5, 3, 1, 2]"
System.out.println(tail); // prints "[9, 5, 0, 7]"

Collections.sort(head);
System.out.println(numbers); // prints "[1, 2, 3, 5, 9, 5, 0, 7]"

tail.add(-1);
System.out.println(numbers); // prints "[1, 2, 3, 5, 9, 5, 0, 7, -1]"

If you need these chopped lists to be NOT a view, then simply create a new List from the subList. Here's an example of putting a few of these things together:

// chops a list into non-view sublists of length L
static <T> List<List<T>> chopped(List<T> list, final int L) {
List<List<T>> parts = new ArrayList<List<T>>();
final int N = list.size();
for (int i = 0; i < N; i += L) {
parts.add(new ArrayList<T>(
list.subList(i, Math.min(N, i + L)))
);
}
return parts;
}


List<Integer> numbers = Collections.unmodifiableList(
Arrays.asList(5,3,1,2,9,5,0,7)
);
List<List<Integer>> parts = chopped(numbers, 3);
System.out.println(parts); // prints "[[5, 3, 1], [2, 9, 5], [0, 7]]"
parts.get(0).add(-1);
System.out.println(parts); // prints "[[5, 3, 1, -1], [2, 9, 5], [0, 7]]"
System.out.println(numbers); // prints "[5, 3, 1, 2, 9, 5, 0, 7]" (unmodified!)

Split a alphabetically sorted list into sublists based on alphabets in java

The solution of @SilverNak is good with Java-8, you can use this also if you are not using Java-8 :

public static void main(String[] args) {
String list[] = {"calculator", "catch", "doll", "elephant"};
Map<Character, List<String>> map = new HashMap<>();

List<String> lst;
for (String str : list) {
//if the key exit then add str to your list else add a new element
if (map.containsKey(str.charAt(0))) {
map.get(str.charAt(0)).add(str);
} else {
lst = new ArrayList<>();
lst.add(str);
map.put(str.charAt(0), lst);
}
}
}


Related Topics



Leave a reply



Submit