4

I have a list of Thingy pojos, such that:

public class Thingy {
    private DifferentThingy nestedThingy;
    public DifferentThingy getNestedThingy() {
        return this.nestedThingy;
    }
}

...

public class DifferentThingy {
    private String attr;
    public String getAttr() {
        return this.attr;
    }
}

I want to filter a

List<Thingy>

to be unique based on the

attr

of the Thingy's

DifferentThingy

Here is what I have tried so far:

private List<Thingy> getUniqueBasedOnDifferentThingyAttr(List<Thingy> originalList) {
    List<Thingy> uniqueItems = new ArrayList<Thingy>();
    Set<String> encounteredNestedDiffThingyAttrs= new HashSet<String>();
    for (Thingy t: originalList) {
        String nestedDiffThingyAttr = t.getNestedThingy().getAttr();
        if(!encounteredNestedDiffThingyAttrs.contains(nestedDiffThingyAttr)) {
            encounteredNestedDiffThingyAttrs.add(nestedDiffThingyAttr);
            uniqueItems.add(t);
        }
    }
    return uniqueItems;
}

I want to do this using a Java 8 stream and lambdas for the two getters that end up retrieving the attribute used for determining uniqueness, but am unsure how. I know how to do it when the attribute for comparison is on the top level of the pojo, but not when the attribute is nested in another object.

1

5 Answers 5

4

You can do it that way:

originalList.stream()
    .collect(Collectors.toMap(thing -> thing.getNestedThingy().getAttr(), p -> p, (p, q) -> p))
    .values();

Not sure if the most optimal way though, I'm on Android, so don't use streams in daily job.

UPD

For someone can not compile it, there is full code of my test file.

Sign up to request clarification or add additional context in comments.

8 Comments

Anyone tested the above? I have got a java.lang.ClassCastException: java.util.HashMap$Values cannot be cast to java.util.List.
My solution is about the same: public static Collection<Thingy> getUniqueBasedOnDifferentThingyAttr(List<Thingy> originalList) { return originalList.stream() .collect(Collectors.toMap(t -> t.getNestedThingy().getAttr(), t -> t)) .values(); }
@Andreas, I think it depends on which one they want when a collision happens.
@GrzegorzGórkiewicz yes, I wrote it in idea, and it's 100% working code
@JellyRaptor I've added link to full code of my test class.
|
4

What about making it far more explicit using javaslang

javaslang.collection.List.ofAll(thingies)
  .distinctBy(t -> t.getNestedThingy().getAttr());

Javaslang core is a functional library for Java 8+. It helps to reduce the amount of code and to increase the robustness. A first step towards functional programming is to start thinking in immutable values. Javaslang provides immutable collections and the necessary functions and control structures to operate on these values. The results are beautiful and just work.

Comments

2

I keep a set of unique nested thingies' attributes... then I try to add an element to it. When the set "grows", it means that an unique element has been encountered. When the set size does not change, it means that this Thingy has a NestedThingy with an already encountered attribute. Together with a simple test that would be:

public class Thingies {

    public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        final Set<Object> seen = Collections.synchronizedSet(new HashSet<>());
        return t -> seen.add(keyExtractor.apply(t));
    }

    private static List<Thingy> getUniqueBasedOnDifferentThingyAttr(List<Thingy> originalList) {
        return originalList.stream()
                .filter(distinctByKey(thingy -> thingy.getNestedThingy().getAttr()))
                .collect(toList());
    }

    public static void main(String[] args) {
        List<Thingy> originalList = new ArrayList<>();
        originalList.add(new Thingy(new DifferentThingy("first")));
        originalList.add(new Thingy(new DifferentThingy("first")));
        originalList.add(new Thingy(new DifferentThingy("second")));
        System.out.println(getUniqueBasedOnDifferentThingyAttr(originalList));
    }
}

Output:

[first, second]

The original answer, which helped the OP, (a bit lengthy and not good for parallel streams) contained such a method instead:

private static List<Thingy> getUniqueBasedOnDifferentThingyAttr(List<Thingy> originalList) {
    final Set<String> uniqueAttributes = new HashSet<>(originalList.size());
    return originalList.stream()
            .filter(thingy -> {
                int initialSize = uniqueAttributes.size();
                uniqueAttributes.add(thingy.getNestedThingy().getAttr());
                return initialSize != uniqueAttributes.size();
            }).collect(toList());
}

10 Comments

This works if I change toList() to Collectors.toList()
Then go for it ;) I can try to make it a bit shorter with an distinctBy method implementation.
Change outer value from inside stream chain looks duty for me.
@Divers, it is contained in one method. Reassigning a variable is a bad practice (here impossible because of final), making use of its mutability (here: add method call) is a common practice, especially in void methods. Having said this, I still like your code... it returns an instance of Collection<Thingy> instead of List<Thingy>, which is its only drawback (very easy to fix).
Thank you all for your constructive criticism, I will edit my answer.
|
2

You might use a TreeSet whose comparator reaches into the subobject's attribute:

private static List<Thingy> getUniqueBasedOnDifferentThingyAttr(List<Thingy> originalList) {

    Set<Thingy> uniqueSet = new TreeSet<Thingy>(Comparator.comparing(thingy -> thingy.getNestedThingy().getAttr()));

    return originalList.stream()
            .filter(uniqueSet::add)
            .collect(Collectors.toList());
}

uniqueSet::add makes an adequate uniqueness filter because it returns true only if the item is not already in the set.

2 Comments

Using an original list of length 5 with 3 "unique" items, this solution was, on average, 6 times faster (11.8ms to 66ms) than the originally accepted answer by @GrzegorzGórkiewicz. I like the use of Comparator as well.
Imho Hank D's answer should be the accepted one. It's simple and somehow beautiful.
0

With StreamEx:

StreamEx.of(originalList).distinct(e -> e.getNestedThingy().getAttr()).toList();

Or by abacus-common

Stream.of(originalList).distinct(e -> e.getNestedThingy().getAttr()).toList();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.