Filter list based on distinct and second predicate

Viewed 84

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 ?

3 Answers
 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.

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());

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);
Related