-2

I have List<List<String>>. How can I filter the list so that only unique lists remain in it using java streams api?

For example: from originalList to resultList:

List<List<String>> originalList = List.of(
    List.of("abc", "cba"),
    List.of("cba", "abc"),
    List.of("123", "321")
);

List<List<String>> resultList = List.of(
    List.of("abc", "cba"),
    List.of("123", "321")
);
seenukarthi
  • 8,241
  • 10
  • 47
  • 68
NeverSleeps
  • 1,439
  • 1
  • 11
  • 39

2 Answers2

0

As List.equals() is does ensure equal content,stream.distinct would be enough if der different order of elements is considered different, but your example shows that you want to ignore order.

I would recommend to use a non-stream approach in this case, as streams work best when elements can be processed independently.

You need a stateful intermediate filter here, as it needs to remember the elements already checked. One approch would be adding elements to a TreeSet with custom comparator, and allow only elements for which the add-method returns true.

This related question shows how to implent such a filter.


Alternatively, convert the inner lists to LinkedHashSets for the purpose of filtering - this is the simplest approach in terms of code size:

public static void main(String[] args) {
            
    List<List<String>> originalList = List.of(
            List.of("abc", "cba"),
            List.of("cba", "abc"),
            List.of("123", "321")
        );
                
        List<List<String>> result2 = originalList.stream()
                .map(LinkedHashSet::new) // Note: does not support null values
                .distinct()
                .map(List::copyOf) // Note: does not support null values
                .toList();
        
        result2.forEach(System.out::println);           
}

Prints:

[abc, cba]
[123, 321]
Hulk
  • 6,399
  • 1
  • 30
  • 52
0

so that only unique lists remain

Judging by your example, the following lists ["abc", "cba"] and ["cba", "abc"] are considered to be duplicates (which obviously isn't aligned with how equality is defined by the List interface).

I infer your definition of equality as: two lists are equal if every element from the first list appear the same number of times in the second list, regardless of the order.

We can eliminate duplicates in a linear time O(n) by making use of standard hash-based mechanisms like method Stream.distinct().

For that, we need to create a wrapper-class which would implement equals/hashCode contract as described above.

The following class exposes a static method of() which internally generates a map of frequencies for every element in the given list (which would is needed for equality checks) and hash code of the map of frequencies (which would be reused instead of being recalculated) and return an instance of wrapper class.

class ListWrapper<T> {
    private List<T> list;
    private Map<T, Integer> frequencies;
    private final int hash; // hash code of the Map of frequencies generated from the List would be computed only once and reused
    
    private ListWrapper(List<T> list, Map<T, Integer> frequencies, int hash) { // should not be invoked from outside
        this.list = list;
        this.frequencies = frequencies;
        this.hash = hash;
    }
    
    public static <T> ListWrapper<T> of(List<T> list) {
        Map<T, Integer> freq = getFrequencies(list);
        
        return new ListWrapper<>(list, freq, freq.hashCode());
    }
    
    public static <T> Map<T, Integer> getFrequencies(List<T> list) {
        
        return list.stream()
            .collect(Collectors.toMap(
                Function.identity(),
                item -> 1,
                Integer::sum
            ));
    }
    
    public List<T> getList() {
        return list;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        
        if (o instanceof ListWrapper<?> other) {
            return Objects.equals(this.frequencies, other.frequencies);
        }
        return false;
    }
    
    @Override
    public int hashCode() {
        return hash;
    }
}

And that's how we can use it.

public static void main(String args[]) {
    List<List<String>> originalList = List.of(
        List.of("abc", "cba"),
        List.of("cba", "abc"),
        List.of("123", "321")
    );
    
    List<List<String>> resultList = originalList.stream()
        .map(ListWrapper::of)      // wrapping a list
        .distinct()                // eliminating duplicates
        .map(ListWrapper::getList) // unwrapping a list
        .toList();                 // or .collect(Collectors.toList()) for Java 15 and earlier

    resultList.forEach(System.out::println);
}

Output:

[abc, cba]
[123, 321]

A link to Online Demo

Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46