user2555212
user2555212

Reputation: 171

Filter objects via streams

I have a list of objects like below

public class Price{

    private long id;

    private String productid;

    private long sequence;

    private BigDecimal price;
    
}

i will have list of objects like below

Price1 -> {1, "p1", 1, 1.0}
Price2 -> {2, "p1", 2, 2.0}
Price3 -> {3, "p1", 3, 3.0}
Price4 -> {4, "p2", 1, 1.0}

each product id can have multiple entries of prices(ordered by sequence). The latest price comes with latest sequence. in the above data, product p1 price is 3, p2's price is 1.

I need to filter the above list of java objects to get only 2 entries(id 3 and 4). the output should come with below list

Price3 -> {3, "p1", 3, 3.0}
Price4 -> {4, "p2", 1, 1.0}

Any help with Java8 streams?

Upvotes: 0

Views: 142

Answers (5)

gkatiforis
gkatiforis

Reputation: 1652

Use Collectors.groupingBy to group the prices per product and then Collectors.maxBy to get the latest price from the list. The result is a map with latest price per products.

Map<String, Price> lastPricePerProduct = data.stream().collect(
Collectors.groupingBy(Price::getProductid, Collectors.collectingAndThen(                
Collectors.maxBy(Comparator.comparing(Price::getSequence)), Optional::get));

More elegant solution with Collectors.toMap and BinaryOperator.maxBy:

Map<String, Price> lastPricePerProduct = data.stream().collect(
Collectors.toMap(Price::getProductid,Function.identity(),
    BinaryOperator.maxBy(Comparator.comparing(Price::getSequence))));

Upvotes: 2

Hunny Chawla
Hunny Chawla

Reputation: 3

You can try this:

 List<Price> list = new ArrayList<>();
    Price p1 = new Price(1,"p1",1,new BigDecimal(1.0));
    Price p2 = new Price(2,"p1",2,new BigDecimal(2.0));
    Price p3 = new Price(3,"p1",3,new BigDecimal(3.0));
    Price p4 = new Price(4,"p3",1,new BigDecimal(1.0));
    list.add(p1);
    list.add(p2);
    list.add(p3);
    list.add(p4);
    List<Price> priceList = list.stream().sorted(Comparator.comparing(Price::getSequence).reversed())
            .filter(distinctByKey(Price::getProductid))
            .collect(Collectors.toList());
    System.out.print(priceList);

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

Output: [Price{id=3, productid='p1', sequence=3, price=3}, Price{id=4, productid='p3', sequence=1, price=1}]

Upvotes: 0

semicolon
semicolon

Reputation: 545

You can do something like this:

prices.stream().collect(Collectors.groupingBy(Price::getProductid)).entrySet().forEach(e -> e.getValue().stream().max(Comparator.comparing(Price::getSequence)).get());

NOTE: I have not tested above code so it may have some incorrect semantics.

Explanation:

  1. Collect respective price objects in map according to product. Collectors.groupingBy(Price::getProductid)
  2. Stream through each keys' list of prices. entrySet().forEach()
  3. Find max sequence for each key and return object. max(Comparator.comparing(Price::getSequence)).get()

Let me know if you require any further assistance.

Upvotes: 1

pcsutar
pcsutar

Reputation: 1821

Here you need to use groupingBy rather than filter to get desired output. Please refer below example:

public class Main {
    public static void main(String[] args) {
        List<Price> data = Arrays.asList(
                new Price(1, "p1", 1, new BigDecimal(1.0)),
                new Price(2, "p1", 2, new BigDecimal(2.0)),
                new Price(3, "p1", 3, new BigDecimal(3.0)),
                new Price(4, "p2", 1, new BigDecimal(1.0)));

        Map<String, Object> result = data.stream().collect(Collectors.groupingBy(Price::getProductid,
                Collectors.collectingAndThen(
                        Collectors.reducing((Price p1, Price p2) -> p1.getSequence() > p2.getSequence() ? p1 : p2),
                        Optional::get)));

        System.out.println(result.values());
    }
}

Output:

[Price(id=3, productid=p1, sequence=3, price=3), Price(id=4, productid=p2, sequence=1, price=1)]

Price class:

@AllArgsConstructor
@Data
public class Price {
    private long id;
    private String productid;
    private long sequence;
    private BigDecimal price;
}

Upvotes: 1

hu-zza
hu-zza

Reputation: 81

As java.util.stream.Stream#predicate accepts "a non-interfering, stateless predicate",
I think you should use a Collection for that.

I would use java.util.stream.Stream#collect with a Collector.
java.util.stream.Collectors makes it convenient.

Upvotes: 0

Related Questions