Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect duplicated groups in stream

I want to ensure that all numbers in the list are grouped together. Let me explain this on examples:

{1, 1, 1, 2, 2}    // OK, two distinct groups
{1, 1, 2, 2, 1, 1} // Bad, two groups with "1"
{1, 2, 3, 4}       // OK, 4 distinct groups of size 1
{1, 1, 1, 1}       // OK, 1 group
{3, 4, 3}          // Bad, two groups with "3"
{99, -99, 99}      // Bad, two groups with "99"
{}                 // OK, no groups

Here's how I obtain the stream:

IntStream.of(numbers)
    ...

Now I need to pass or return true for "OK" examples and throw AssertionError or return false on "Bad" examples. How can I do that using Stream API?

Here's my current solution with additional Set created:

Set<Integer> previousNumbers = new HashSet<>();
IntStream.of(numbers)
        .reduce(null, (previousNumber, currentNumber) -> {
                    if (currentNumber == previousNumber) {
                        assertThat(previousNumbers).doesNotContain(currentNumber);
                        previousNumbers.add(currentNumber);
                    }
                    return currentNumber;
                }
        );
like image 960
Michal Kordas Avatar asked Feb 05 '16 09:02

Michal Kordas


2 Answers

Using my free StreamEx library:

IntStreamEx.of(numbers).boxed().runLengths().toMap();

This code will throw IllegalStateException if there are repeating groups.

Here runLengths() method is used. It collapses equal adjacent elements replacing them with Map.Entry where key is the input element and value is the number of repeats. Finally toMap() is used which is a shortcut for .collect(Collectors.toMap(Entry::getKey, Entry::getValue)). We are using the fact that .toMap() throws IllegalStateException when keys repeat (unless custom mergeFunction is supplied).

As a free bonus on successful execution you will have a map where keys are input elements and values are lengths of series.

like image 147
Tagir Valeev Avatar answered Oct 11 '22 15:10

Tagir Valeev


In my opinion this problem doesn't fit the Stream API at all but I was curious how this could be implemenented (however in a performant way).

The problem is that you have to keep track of seen elements and the whole test should have a short-circuit behaviour. So I came up with this solution (without Streams):

public static boolean hasUniqueGroups(int[] arr) {
    Objects.requireNonNull(arr);
    Set<Integer> seen = new HashSet<>();
    for (int i = 0; i < arr.length; i++) {
        if (i == 0 || arr[i] != arr[i - 1]) {
            if (!seen.add(arr[i])) {
                return false;
            }
        }
    }
    return true;
}

The next step is to introduce the Stream API and the solution looks like this:

public static boolean hasUniqueGroups(int[] arr) {
    Objects.requireNonNull(arr);
    Set<Integer> seen = new HashSet<>();
    return IntStream.range(0, arr.length)
            .filter(i -> i == 0 || arr[i] != arr[i - 1])
            .mapToObj(i -> arr[i])
            .allMatch(seen::add);
}

Note: In order to parallelize this Stream you should use a thread-safe Set.

like image 30
Flown Avatar answered Oct 11 '22 15:10

Flown