0

my people object looks like this

class People {
   this.name,
   this.height,
   this.age
}

I have a list from a database query like so

List<People> people = DAO.queryAllPeople();

which returns 100's of people

Then I want just people with unique height

    Set<People> uniquePeople = list
                    .stream()
                    .map(people -> people)
                    .filter(Objects::nonNull)
                    .collect( Collectors.toSet() );

But this is returning all objects, is there a way to get people distinct by height?

Edit this is what I want but I want the Person object so I can call get methods when I loop over it

  Set<String> people =      people
                                .stream()
                                .map(People::getHeight)
                                .filter(Objects::nonNull)
                                .collect( Collectors.toSet() );
Garuuk
  • 2,153
  • 6
  • 30
  • 59
  • That class, is it even compiling? The members are missing the types and `this` is not possible at that position. – Zabuzard Apr 17 '18 at 20:50
  • 1
    `.map(people -> people)` What did you expect this to do? – takendarkk Apr 17 '18 at 20:51
  • Can you show some examples? Give some example inputs, desired output and current output. In the current state your question is too **unclear** in my opinion. Useful read: [mcve]. – Zabuzard Apr 17 '18 at 20:52
  • updated my original for an example of what i'd want – Garuuk Apr 17 '18 at 21:00
  • This solution should do the trick for you, my friend. https://stackoverflow.com/questions/27870136/java-lambda-stream-distinct-on-arbitrary-key/27872086 – ygorazevedo Apr 17 '18 at 21:03

2 Answers2

1

First, naming a class People is not natural, a better name would be Person.

As, for solving your problem, you can override equals and hashcode for height only like this:

@Override
public boolean equals(Object o) {
     if (this == o) return true;
     if (o == null || getClass() != o.getClass()) return false;

     Person person = (Person) o;

     return height == person.height;
}

@Override
public int hashCode() {
     return height;
}

The above assumes height is an int field. if instead, it's Integer, then you'll need to implement it like so:

@Override
public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      Person person = (Person) o;

      return height != null ? height.equals(person.height) : person1.height == null;
}

@Override
public int hashCode() {
     return height != null ? height.hashCode() : 0;
}

Now, you can do:

 Set<People> uniquePeople = 
              myList.stream()
                    .filter(Objects::nonNull)
                    .collect(Collectors.toSet());

or for what ever reason you don't want to override equals and hashcode you can do it with the toMap collector.

Set<Person> values = new HashSet<>(myList.stream()
                .collect(Collectors.toMap(Person::getHeight, Function.identity(),
                        (left, right) -> left))
                .values());

deciphering the above code snippet:

myList.stream()
      .collect(Collectors.toMap(Person::getHeight, Function.identity(),
              (left, right) -> left))
      .values()

This creates a stream from myList collecting it to a map implementation, where Person::getHeight is a function extracting the person height for the map keys, Function.identity() is a function extracting a person object for the map values, (left, right) -> left) is known as the merge function meaning if two given people have the same key (height) we return the first person (left). Conversely, (left, right) -> right will return the last person in the case of key conflict.

Lastly, we pass the result of this processing to the HashSet constructor to create a Set<Person>.

Community
  • 1
  • 1
Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
  • This gives unique heights with random people assigned, @Garuuk asked for people with unique height, meaning there is no other person with the same height. – hoaz Apr 17 '18 at 21:09
  • @hoaz _"This gives unique heights with random people assigned"_ , what do you mean? _@Garuuk asked for people with unique height, meaning there is no other person with the same height._ that's exactly what this code does... – Ousmane D. Apr 17 '18 at 21:12
  • This does give unique heights, it works. Also Aomine, could you explain what the left, right -> left means? – Garuuk Apr 17 '18 at 21:16
  • Thanks Aomine, I appreciate it. Also, lastly, if I wanted to filter out nulls any Height that is null do I do that before the .collect method? – Garuuk Apr 17 '18 at 21:21
  • 1
    @Garuuk you can filter out null elements from `myList` and person objects that have null `Height` like this `Set values = new HashSet<>(myList.stream() .filter(Objects::nonNull) .filter(p -> p.getHeight() != null) .collect(Collectors.toMap(Person::getHeight, Function.identity(), (left, right) -> left)) .values());` – Ousmane D. Apr 17 '18 at 21:38
  • 1
    if you only want to filter out person objects that have null Height `Set values = new HashSet<>(myList.stream() .filter(p -> p.getHeight() != null) .collect(Collectors.toMap(Person::getHeight, Function.identity(), (left, right) -> left)) .values());` – Ousmane D. Apr 17 '18 at 21:42
  • Given John is 5 feet and Mike is 5 feet as well. There are no persons with unique height in this list. But if he really wants unique heights and somone with this height, then this solution is non-deterministic – hoaz Apr 17 '18 at 21:43
  • @hoaz I believe the OP wants _distinct by height_. i.e. in the example, you've provided John should be in the result set but not Mike. – Ousmane D. Apr 17 '18 at 21:48
  • Correct, I should have been clear with my verbage. Distinct height is what I wanted - I'll update my OP. Thank you very much for the explanation – Garuuk Apr 17 '18 at 23:27
1

Split this task into two subtasks.

First group people by height:

Map<Integer, List<People>> groups = list.stream()
        .collect(Collectors.groupingBy(People::getHeight);

Then find which groups have only one person:

groups.entrySet().stream()
        .filter(e -> e.getValue().size() == 1) // use only groups with one person
        .map(e -> e.getValue().get(0))
        .collect(Collectors.toList());
hoaz
  • 9,883
  • 4
  • 42
  • 53