geisterfurz007 geisterfurz007 - 1 year ago 62
Java Question

Split List or array on sign change using streams

Recently I stumbled on the need to split a list of values to lists of positive and negative numbers. However NOT one list positive and one for negative but basically beginning a new list once the sign changes (ignoring 0 values);

Example:

valuesInList = [-1, -3, -5, -120, 0, 15, 24, 42, 13, -15, -24, -42, 1, 2, 3]
splitList = [[-1, -3, -5, -120], [15, 24, 42, 13], [-15, -24, -42], [1, 2, 3]]


I wrote code that works, but it is something I am not really happy with:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Main {

public static void main(String[] args) {
Byte[] values = new Byte[]{-1, -3, -5, -120, 0, 15, 24, 42, 13, -15, -24, -42, 1, 2, 3}; //0 -> ignore
List<Byte> valuesInList = Arrays.asList(values);
System.out.println("valuesInList = " + valuesInList);
int firstNotZero = valuesInList.stream().filter(b -> b != 0).collect(Collectors.toList()).get(0);
boolean currentlyLookingForPositive = firstNotZero > 0;

List<List<Byte>> splitList = new ArrayList<>();

int index = 0;

while (index < valuesInList.size()) {
List<Byte> collection = new ArrayList<>();
while (currentlyLookingForPositive && index < valuesInList.size()) {
byte current = valuesInList.get(index);
if (current > 0) {
collection.add(current);
index++;
}
else
currentlyLookingForPositive = false;
if (current == 0)
index++;
}
if (!collection.isEmpty())
splitList.add(collection);

collection = new ArrayList<>();
while (!currentlyLookingForPositive && index < valuesInList.size()) {
byte current = valuesInList.get(index);
if (current < 0) {
collection.add(current);
index++;
}
else
currentlyLookingForPositive = true;
}
if (!collection.isEmpty())
splitList.add(collection);
}

System.out.println("splitList = " + splitList);
}

}


I think the reason for that is rather obvious: repeating major parts of the code. However I have no clue how I could export this way to a method to make the code a lot clearer.

Further, now that I saw the massive potential of streams I wondered whether there was a convenient way of writing this bulk using Java 8 Streams or using other Java 8 features to at least run parts of the code in a method.

Edit: This question was flagged to be a possible duplicate of Group sequences of values . Eventhough the title might tell differently the questions (at least from my perspective) are not duplicates. The linked one asks for groups of ascending values whereas mine asks for grouping by sign (positive/negative).

Answer Source

A quick and dirty solution:

List<List<Integer>> result = Arrays.asList(-1, -3, -5, -120, 0, 15, 24, 42, 13, -15, -24, -42, 1, 2, 3)
            .stream()
            .collect(Collector.of(
                    () -> {
                        List<List<Integer>> list = new ArrayList<>();
                        list.add(new ArrayList<>());
                        return list;
                    },
                    (list, x) -> {

                        if (x == 0) {
                            return;
                        }

                        if (list.size() == 0) {
                            list.get(0).add(x);
                        } else {
                            List<Integer> lastInner = list.get(list.size() - 1);
                            if (lastInner.size() > 0) {
                                int elem = lastInner.get(0);
                                if (elem >>> 31 == x >>> 31) {
                                    lastInner.add(x);
                                } else {
                                    List<Integer> oneMore = new ArrayList<>();
                                    oneMore.add(x);
                                    list.add(oneMore);
                                }
                            } else {
                                lastInner.add(x);
                            }

                        }

                    }, (left, right) -> {

                        throw new RuntimeException("Not for aprallel");
                    }));

    System.out.println(result);

I am still thinking if its possible to do for parallel processing (the combiner for the custom collector). Also it would probably be better to extract that to a method that returns this collector for further re-usage. Will update with that

EDIT

Or if there is no StreamEx:

List<Integer> input = Arrays.asList(-1, -3, -5, -120, 0, 15, 24, 42, 13, -15, -24, -42, 1, 2, 3);
    List<Integer> filtered = input.stream().filter(x -> x != 0).collect(Collectors.toList());

    int[] indexes = IntStream.range(0, filtered.size() - 2)
            .filter(x -> x == 0 || x == filtered.size() || filtered.get(x) >>> 31 != filtered.get(x + 1) >>> 31)
            .map(x -> x + 1)
            .toArray();

    List<List<Integer>> result = IntStream.range(0, indexes.length - 1)
            .mapToObj(x -> filtered.subList(indexes[x], indexes[x + 1]))
            .collect(Collectors.toList());

    System.out.println(result);
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download