seba21007
seba21007

Reputation: 71

Java 8 stream unique Integers

Can i reduce the following code to one/two line?

DTO dto;
List<DTO> dtos;
List<Integer> list1 = dtos.stream().map(DTO::getFirstId).distinct().collect(Collectors.toList());
List<Integer> list2 = dtos.stream().map(DTO::getSecondId).distinct().collect(Collectors.toList());

List<Integer> reducedId = list1.stream().filter(list2::contains).collect(Collectors.toList());

Upvotes: 7

Views: 9452

Answers (5)

user_3380739
user_3380739

Reputation: 1254

With StreamEx:

Set<Integer> set = StreamEx.of(dtos).map(DTO::getSecondId).toSet();
List<Integer> result = StreamEx.of(dtos).map(DTO::getFirstId)
                                        .filter(set::contains).distinct().toList();

Or by abacus-common

Set<Integer> set = Stream.of(dtos).map(DTO::getSecondId).toSet();
List<Integer> result = Stream.of(dtos).map(DTO::getFirstId)
                                        .filter(set::contains).distinct().toList();

// OR:

List<Integer> result = Stream.of(dtos).map(DTO::getFirstId)
       .intersection(Stream.of(dtos).map(DTO::getSecondId).toSet()).toList();

Upvotes: 2

Donald Raab
Donald Raab

Reputation: 6706

If you're open to using a third-party library, you have a few options using Eclipse Collections. If you want to use Stream, the following should work using Collectors2.

MutableList<Integer> result =
    dtos.stream().map(DTO::getFirstId).collect(Collectors2.toSet())
        .intersect(dtos.stream().map(DTO::getSecondId).collect(Collectors2.toSet()))
        .toList();

Eclipse Collections also has its only LazyIterable type that can be used instead of Stream.

LazyIterable<DTO> iterable = LazyIterate.adapt(dtos);
MutableList<Integer> result =
    iterable.collect(DTO::getFirstId).toSet()
        .intersect(iterable.collect(DTO::getSecondId).toSet())
        .toList();

Finally, if you have large numbers of ids, you might want to use primitive sets to avoid boxing Integer objects. You can either work with Stream and Collectors2 again as follows:

IntSet second = dtos.stream().collect(
    Collectors2.collectInt(DTO::getSecondId, IntSets.mutable::empty));
IntList result = dtos.stream().collect(
    Collectors2.collectInt(DTO::getFirstId, IntSets.mutable::empty))
        .select(second::contains).toList();

Or you can use LazyIterable as follows:

LazyIterable<DTO> iterable = LazyIterate.adapt(dtos);
IntList result =
    iterable.collectInt(DTO::getFirstId).toSet()
        .select(iterable.collectInt(DTO::getSecondId).toSet()::contains)
        .toList();

Note: I am a committer for Eclipse Collections.

Upvotes: 3

Holger
Holger

Reputation: 298389

You may force it into one stream operation, but the performance would be even worse than what you have now, i.e. an operation with quadratic time complexity.

A better approach would be:

Set<Integer> set = dtos.stream().map(DTO::getSecondId).collect(Collectors.toSet());
List<Integer> result = dtos.stream().map(DTO::getFirstId)
    .distinct().filter(set::contains).collect(Collectors.toList());
// result now contains the common IDs

By collecting the second IDs into a Set instead of a List, you don’t need to use distinct() in the first stream operation and avoid the linear search applied to every element in the second stream operation when contains is invoked.

You may generally consider using a Set for remembering unique IDs. When using a Set as result type, you may avoid the distinct() of the second stream operation too:

Set<Integer> set = dtos.stream().map(DTO::getSecondId).collect(Collectors.toSet());
Set<Integer> result = dtos.stream().map(DTO::getFirstId)
    .filter(set::contains).collect(Collectors.toSet());

If you suspect lots of duplicate IDs and want to keep the behavior of sorting out duplicates before checking the other Set, you may use:

Set<Integer> set = dtos.stream().map(DTO::getSecondId).collect(Collectors.toSet());
Set<Integer> result = dtos.stream().map(DTO::getFirstId)
    .collect(Collectors.toCollection(HashSet::new));
result.retainAll(set);

Note that if you prefer long, hard too read “one liner”, you can inline the set variable in all variants.

Upvotes: 4

Patrick Parker
Patrick Parker

Reputation: 4957

Using a single Java 8 stream is not a great choice here. Instead you should first create a Set so that you can perform an efficient contains test.

Set<Integer> secondIds = dtos.stream().map(DTO::getSecondId).collect(Collectors.toSet());
List<Integer> reducedIds = dtos.stream().map(DTO::getFirstId).distinct()
        .filter(secondIds::contains).collect(Collectors.toList());

Upvotes: 11

dylaniato
dylaniato

Reputation: 516

I think you could do something like

List<Integer> reducedId = dtos.stream().map(DTO::getFirstId).distinct().filter(
    (dtos.stream().map(DTO::getSecondId).distinct().collect(Collectors.toList()))::contains
).collect(Collectors.toList());

Not tested in my local, but it seems reasonable to me :)

Upvotes: 1

Related Questions