Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to remove duplicate in List<T> JAVA 8

Practically I know ways to reduce duplicate trought distinct(), or assign List to Set, but I have a little different issue. How to solve smart way below problem in JAVA 8 using stream or may be StreamEx ?

Let's say we have a objects in List

A, A, A, B, B, A, A, A, C, C, C, A, A, B, B, A

Now I need

A, B, A, C, A, B, A

So duplicated was removed but only if appear as next, but should stay if next to then is different object. I tried a few solutions but ware ugly, and not readable.

like image 657
Mbded Avatar asked Mar 10 '18 19:03

Mbded


People also ask

How do I remove duplicates from a list in Java 8?

Using Java 8 Stream. You can use the distinct() method from the Stream API. The distinct() method return a new Stream without duplicates elements based on the result returned by equals() method, which can be used for further processing.

How do I remove duplicates from a list?

To remove the duplicates from a list, you can make use of the built-in function set(). The specialty of the set() method is that it returns distinct elements.


1 Answers

Option 1: Filter

You could write a stateful filter, but you should never do that, because it violates the contract of filter(Predicate<? super T> predicate):

predicate - a non-interfering, stateless predicate to apply to each element to determine if it should be included

public class NoRepeatFilter<T> implements Predicate<T> {
    private T prevValue;
    @Override
    public boolean test(T value) {
        if (value.equals(this.prevValue))
            return false;
        this.prevValue = value;
        return true;
    }
}

Test

List<String> result = Stream
        .of("A", "A", "A", "B", "B", "A", "A", "A", "C", "C", "C", "A", "A", "B", "B", "A")
//      .parallel()
        .filter(new NoRepeatFilter<>())
        .collect(Collectors.toList());
System.out.println(result);

Output

[A, B, A, C, A, B, A]

The reason it must be stateless is that it'll fail if the stream is parallel, e.g. running test again with .parallel() uncommented:

[A, A, B, B, A, C, C, C, A, B, B, A]


Option 2: Collector

A valid solution is to create your own Collector using of(...):

public class NoRepeatCollector {
    public static <E> Collector<E, ?, List<E>> get() {
        return Collector.of(ArrayList::new,
                            NoRepeatCollector::addNoRepeat,
                            NoRepeatCollector::combineNoRepeat);
    }
    private static <E> void addNoRepeat(List<E> list, E value) {
        if (list.isEmpty() || ! list.get(list.size() - 1).equals(value))
            list.add(value);
    }
    private static <E> List<E> combineNoRepeat(List<E> left, List<E> right) {
        if (left.isEmpty())
            return right;
        if (! right.isEmpty())
            left.addAll(left.get(left.size() - 1).equals(right.get(0))
                        ? right.subList(1, right.size()) : right);
        return left;
    }
}

Test

List<String> result = Stream
        .of("A", "A", "A", "B", "B", "A", "A", "A", "C", "C", "C", "A", "A", "B", "B", "A")
//      .parallel()
        .collect(NoRepeatCollector.get());
System.out.println(result);

Output (with and without .parallel())

[A, B, A, C, A, B, A]


Option 3: Loop

If your input is a List (or other Iterable), you could remove repeating values using a simple loop:

public static <E> void removeRepeats(Iterable<E> iterable) {
    E prevValue = null;
    for (Iterator<E> iter = iterable.iterator(); iter.hasNext(); ) {
        E value = iter.next();
        if (value.equals(prevValue))
            iter.remove();
        else
            prevValue = value;
    }
}

Test

List<String> list = new ArrayList<>(Arrays.asList(
        "A", "A", "A", "B", "B", "A", "A", "A", "C", "C", "C", "A", "A", "B", "B", "A"));
removeRepeats(list);
System.out.println(list);

Output

[A, B, A, C, A, B, A]

like image 69
Andreas Avatar answered Oct 02 '22 15:10

Andreas