Diem
Diem

Reputation: 101

How to exclude non-single items from two lists?

How can I use a stream from two lists to get a list of unique entities? Match only by username

public class Entity {   
    private String username;
    private String password;
}

    var first = Arrays.asList(
            new Entity("user1", ""),
            new Entity("user2", "")
            new Entity("user3", "pass3"),
            new Entity("user5", "pass5")
            
    );

    var second = Arrays.asList(
            new Entity("user1", "pass1"),
            new Entity("user2", "pass2"),
 
    );

    public static void foo(List<Entity> first, List<Entity> second) {
      List<Entity>result = Stream.of(first, second)
            .flatMap(List::stream)
            ? 
            ?
            .collect(Collectors.toList());
      }

result must be list with Entity("user3", "pass3") and Entity("user5", "pass5")

Upvotes: 3

Views: 371

Answers (4)

Kaplan
Kaplan

Reputation: 3758

lambda as a straight forward solution
concat the streams of first-list-entities not contained in second-list and vice versa

List<Entity> unique = Stream.concat(
    first.stream().filter(e -> ! second.contains( e )),
    second.stream().filter(e -> ! first.contains( e )) ).collect( toList() );

Upvotes: 0

Sergey Afinogenov
Sergey Afinogenov

Reputation: 2202

Along with using groupingBy you can also use Collectors.toMap with merging (val1, val2) -> null to exclude elements getting to merge thus leaving only single elements:

List<Entity> result = Stream.concat(first.stream(), second.stream())
                            .collect(Collectors.toMap(Entity::getUsername, 
                                                      val -> val, (val1, val2) -> null))
                            .values().stream()
                            .collect(Collectors.toList());

Upvotes: 3

Guilherme Alencar
Guilherme Alencar

Reputation: 1403

Probably it's not the best way

 public static List<Entity> foo(List<Entity> first, List<Entity> second) {
    List<Entity> arr = new ArrayList<>();
    arr.addAll(first);
    arr.addAll(second);
    return arr
            .stream()
            .filter(entity -> (first.stream().map(Entity::getUsername).anyMatch(username -> username.equals(entity.getUsername())) &&
                    second.stream().map(Entity::getUsername).noneMatch(username -> username.equals(entity.getUsername()))) ||
                    (second.stream().map(Entity::getUsername).anyMatch(username -> username.equals(entity.getUsername())) &&
                            first.stream().map(Entity::getUsername).noneMatch(username -> username.equals(entity.getUsername()))))
            .collect(Collectors.toList());
}

The logic in the filter is "Exclusive OR", as we don't have a straight way of doing that, we need to make the logic of (Condition1 and not Condition2) or (Condition2 and not Condition1).

Upvotes: 0

Dmitrii B
Dmitrii B

Reputation: 2860

you can make grouping by username:

var groupedData = Stream.concat(list1.stream(), list2.stream())
                .collect(Collectors.groupingBy(Entity::getUsername));

and then filtered entity which size > 1:

groupedData.values().stream()
            .filter(s -> s.size() == 1)
            .flatMap(Collection::stream)
            .collect(Collectors.toList());

or only one a big stream:

Stream.concat(list1.stream(), list2.stream())
                .collect(Collectors.groupingBy(Entity::getUsername)).values().stream()
                .filter(s -> s.size() == 1)
                .flatMap(Collection::stream)
                .collect(Collectors.toList()); 

Upvotes: 6

Related Questions