Saurabh Kumar
Saurabh Kumar

Reputation: 16671

Filter list based on distinct and second predicate

My object looks like following

Store {
   String shopId;
   long distance;
}

I got a list of stores.

List<Store> storesList = Arrays.asList(
    new Store (1, 1),
    new Store (1, 5),
    new Store (2, 2),
    new Store (1, 1), // this is duplicate
    new Store (1, 2),
    new Store (1, 1), // this is duplicate
    new Store (3, 7)
    new Store (3, 5)
);

Output

Store {shopId=1, distance=1}  // its fine to have any one among 3 duplicates
Store {shopId=2, distance=2}
Store {shopId=3, distance=5}

i can call my own distint method like following

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

and filter it like this

List<Store> stores= storesList .stream()
        .filter(distinctByKey(pr -> Arrays.asList(pr.getShopId())))
        .collect(toList());

but how to filter it at the same time by smaller distance too ?

Upvotes: 3

Views: 99

Answers (3)

Samuel Philipp
Samuel Philipp

Reputation: 11050

You can try this:

Collection<Store> stores = storesList.stream()
        .collect(Collectors.groupingBy(Store::getShopId, 
                Collectors.collectingAndThen(
                        Collectors.minBy(Comparator.comparingLong(Store::getDistance)), 
                        Optional::get)))
        .values();

At the beginning you are grouping by shopId, then you are using the value with the minimum distance. At the Ende you are just using the values of this map as result.

If you need a List instead oft a Collection you can just use

new ArrayList<>(stores);

Upvotes: 0

Eugene
Eugene

Reputation: 120968

 storesList.stream()
           .collect(Collectors.toMap(
                Store::getShopId,
                Function.identity(),
                BinaryOperator.minBy(Comparator.comparingLong(Store::getDistance))
              ))
           .values()
           .forEach(System.out::println);

You can merge these same Stores (by storeId), where you would say that when merging you would take the smallest distance between two Stores.

Upvotes: 4

youhans
youhans

Reputation: 6859

If you sort the stream by distance before filter, you will get the smallest distances:

List<Store> stores = storesList.stream()
        .sorted(Comparator.comparing(Store::getDistance))
        .filter(distinctByKey(it -> it.shopId))
        .collect(toList());

Upvotes: 2

Related Questions