user13123978
user13123978

Reputation:

Get the highest value, distinct elements from a list using Stream

I have an ArrayList with Bottle objects. Each Bottle object has a brand:String, and a price:double field. I am trying to write an expression using Stream API, so not only I end up with a list of unique bottles, but with a list of unique bottles with the highest price - e.g. if the ArrayList contains three Bottle objects of the same name, I would like the one with the highest price to make it into the final list.

Upvotes: 0

Views: 687

Answers (3)

waleedbinnaveed
waleedbinnaveed

Reputation: 1

If it contains more than two properties Collectors.toMap will not work. Better solution would be

  • Sort the list of bottle with the highest price
  • Filter (use distinct by key)

      List<Bottle> processedBottles = bottles.stream()
                    .sorted(Comparator.comparing(Bottle::getPrice).reversed())
                    .filter(distinctByKey(Bottle::getName))
                    .collect(Collectors.toList());
            //print 
            processedBottles.forEach(System.out::println);

Distinct by key:


public 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;
}

Upvotes: 0

Boris Chistov
Boris Chistov

Reputation: 950

Well here is an example of how you can find unique with highest prices

import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.stream.Collectors;

public class ArrayTest {

    public static class Bottle {
        private final String name;
        private final double price;

        public Bottle(final String name, final double price) {
            this.name = name;
            this.price = price;
        }

        public String getName() {
            return name;
        }

        public double getPrice() {
            return price;
        }

        @Override
        public String toString() {
            return "Bottle{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
        }
    }

    @Test
    public void uniqueBottles() {
        List<Bottle> bottles = List.of(
            new Bottle("bottle1", 1.),
            new Bottle("bottle1", 2.),
            new Bottle("bottle1", 3.),
            new Bottle("bottle2", 3.5),
            new Bottle("bottle2", 1.5)
        );

        var processedBottleList = bottles
            .stream()
            .collect(Collectors.toMap(Bottle::getName, Bottle::getPrice, (p, p2) -> p > p2 ? p : p2))
            .entrySet()
            .stream()
            .map(e -> new Bottle(e.getKey(), e.getValue()))
            .collect(Collectors.toList());

        System.out.println(processedBottleList);
    }
}

Upvotes: 1

Ravindra Ranwala
Ravindra Ranwala

Reputation: 21124

You can create a map, with brand name as the key and bottle as the value. And then use a merge function in which, if another bottle is found with the same name, it keeps the bottle with the highest price. Then, get hold of the map values at the end. Here's hot it looks in practice.

Collection<Bottle> highestPriceBottles = bottles.stream()
    .collect(Collectors.toMap(Bottle::getBrand, b -> b,
        BinaryOperator.maxBy(Comparator.comparingDouble(Bottle::getPrice))))
    .values();

Upvotes: 3

Related Questions