Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

java 8 how to get distinct list on more than one property

How can one get the distinct (distinct based on two property) list from a list of objects. for example let there are list of objects with property name and price. Now how can I get a list with distinct name or price.
suppose

list<xyz> l1 = getlist(); // getlist will return the list.

Now let l1 has the following properties(name, price) :-
n1, p1
n1, p2
n2, p1
n2, p3

Now after the filter the list should be-
n1, p1
n2, p3

I tried solving like this -

public List<xyz> getFilteredList(List<xyz> l1) {

        return l1
                .stream()
                .filter(distinctByKey(xyz::getName))
                .filter(distinctByKey(xyz::getPrice))
                .collect(Collectors.toList());
    }

    private static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
        Map<Object,Boolean> seen = new ConcurrentHashMap<>();
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }

Now the problem is when i did filter on name the list return would be -
n1, p1
n2, p1

and then it would have run filter on price which return -
n1, p1

which is not the expected result.

like image 988
rcipher222 Avatar asked Mar 15 '17 18:03

rcipher222


People also ask

How do I get unique values from a collection stream?

distinct() returns a stream consisting of distinct elements in a stream. distinct() is the method of Stream interface. This method uses hashCode() and equals() methods to get distinct elements.

What is the use of distinct () in Java 8?

Java Stream distinct() method returns a new stream of distinct elements. It's useful in removing duplicate elements from the collection before processing them.

What is streams in Java?

A stream is a sequence of objects that supports various methods which can be pipelined to produce the desired result. The features of Java stream are – A stream is not a data structure instead it takes input from the Collections, Arrays or I/O channels.


2 Answers

I'd go for something like this, which is fairly simple and flexible, and builds on your example:

public static <T> List<T> distinctList(List<T> list, Function<? super T, ?>... keyExtractors) {

    return list
        .stream()
        .filter(distinctByKeys(keyExtractors))
        .collect(Collectors.toList());
}

private static <T> Predicate<T> distinctByKeys(Function<? super T, ?>... keyExtractors) {

    final Map<List<?>, Boolean> seen = new ConcurrentHashMap<>();

    return t -> {

        final List<?> keys = Arrays.stream(keyExtractors)
            .map(ke -> ke.apply(t))
            .collect(Collectors.toList());

        return seen.putIfAbsent(keys, Boolean.TRUE) == null;

    };

}

This can then be called in the following manner:

final List<Xyz> distinct = distinctList(list, Xyz::getName, Xyz::getPrice)
like image 109
lukens Avatar answered Oct 15 '22 15:10

lukens


Almost verbatim from Stuart Marks' answer:

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

class Class {

  public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
    Map<Object, Boolean> seen = new ConcurrentHashMap<>();
    return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
  }

  private static List<Pojo> getList() {
    return Arrays.asList(
      new Pojo("123", 100),
      new Pojo("123", 100),
      new Pojo("123", 100),
      new Pojo("456", 200)
    );
  }

  public static void main(String[] args) {

    System.out.println(getList().stream()
      // extract a key for each Pojo in here. 
      // concatenating name and price together works as an example
      .filter(distinctByKey(p -> p.getName() + p.getPrice()))
      .collect(Collectors.toList()));
  }

}

class Pojo {
  private final String name;
  private final Integer price;

  public Pojo(final String name, final Integer price) {
    this.name = name;
    this.price = price;
  }

  public String getName() {
    return name;
  }

  public Integer getPrice() {
    return price;
  }

  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder("Pojo{");
    sb.append("name='").append(name).append('\'');
    sb.append(", price=").append(price);
    sb.append('}');
    return sb.toString();
  }
}

This main method yields:

[Pojo{name='123', price=100}, Pojo{name='456', price=200}]

Edit

Made price an int per Eugene's prompting.

Note: that you could use something more interesting as a key if you wanted to flesh it out:

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

class Class {

  public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
    Map<Object, Boolean> seen = new ConcurrentHashMap<>();
    return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
  }

  private static List<Pojo> getList() {
    return Arrays.asList(
      new Pojo("123", 100),
      new Pojo("123", 100),
      new Pojo("123", 100),
      new Pojo("456", 200)
    );
  }

  private static class NameAndPricePojoKey {
    final String name;
    final int price;

    public NameAndPricePojoKey(final Pojo pojo) {
      this.name = pojo.getName();
      this.price = pojo.getPrice();
    }

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

      final NameAndPricePojoKey that = (NameAndPricePojoKey) o;

      if (price != that.price) return false;
      return name != null ? name.equals(that.name) : that.name == null;

    }

    @Override
    public int hashCode() {
      int result = name != null ? name.hashCode() : 0;
      result = 31 * result + price;
      return result;
    }
  }

  public static void main(String[] args) {

    System.out.println(getList().stream()
      // extract a key for each Pojo in here. 
      .filter(distinctByKey(NameAndPricePojoKey::new))
      .collect(Collectors.toList()));
  }

}

class Pojo {
  private String name;
  private Integer price;
  private Object otherField1;
  private Object otherField2;

  public Pojo(final String name, final Integer price) {
    this.name = name;
    this.price = price;
  }

  public String getName() {
    return name;
  }

  public void setName(final String name) {
    this.name = name;
  }

  public Integer getPrice() {
    return price;
  }

  public void setPrice(final Integer price) {
    this.price = price;
  }

  public Object getOtherField1() {
    return otherField1;
  }

  public void setOtherField1(final Object otherField1) {
    this.otherField1 = otherField1;
  }

  public Object getOtherField2() {
    return otherField2;
  }

  public void setOtherField2(final Object otherField2) {
    this.otherField2 = otherField2;
  }

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

    final Pojo pojo = (Pojo) o;

    if (name != null ? !name.equals(pojo.name) : pojo.name != null) return false;
    if (price != null ? !price.equals(pojo.price) : pojo.price != null) return false;
    if (otherField1 != null ? !otherField1.equals(pojo.otherField1) : pojo.otherField1 != null) return false;
    return otherField2 != null ? otherField2.equals(pojo.otherField2) : pojo.otherField2 == null;

  }

  @Override
  public int hashCode() {
    int result = name != null ? name.hashCode() : 0;
    result = 31 * result + (price != null ? price.hashCode() : 0);
    result = 31 * result + (otherField1 != null ? otherField1.hashCode() : 0);
    result = 31 * result + (otherField2 != null ? otherField2.hashCode() : 0);
    return result;
  }

  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder("Pojo{");
    sb.append("name='").append(name).append('\'');
    sb.append(", price=").append(price);
    sb.append(", otherField1=").append(otherField1);
    sb.append(", otherField2=").append(otherField2);
    sb.append('}');
    return sb.toString();
  }
}
like image 24
Andreas Avatar answered Oct 15 '22 15:10

Andreas