Apply stream to filter out all elements satisfying the condition except one

  • A+

I have the following class:

public class Offer {      private final OfferType type;     private final BigDecimal price;      // constructor, getters and setters } 

and enum type:

public enum OfferType {     STANDARD, BONUS; } 

My use case is that having a list of offers as an input, I want to filter out all the standard ones except the cheapest one. So for the following input data

List<Offer> offers = Arrays.asList(new Offer(OfferType.STANDARD, BigDecimal.valueOf(10.0)),             new Offer(OfferType.STANDARD, BigDecimal.valueOf(20.0)),             new Offer(OfferType.STANDARD, BigDecimal.valueOf(30.0)),             new Offer(OfferType.BONUS, BigDecimal.valueOf(5.0)),             new Offer(OfferType.BONUS, BigDecimal.valueOf(5.0))); 

I expect the following result

[Offer [type=STANDARD, price=10.0], Offer [type=BONUS, price=5.0], Offer [type=BONUS, price=5.0]] 

Is there a single-line statement (using streams or any third-party library) that allows for doing that?


Not with a single stream operation though:

List<Offer> some =                          .filter(x -> x.getType() != OfferType.STANDARD)                          .collect(Collectors.toCollection(ArrayList::new));       .filter(x -> x.getType() == OfferType.STANDARD)       .min(Comparator.comparing(Offer::getPrice))       .ifPresent(some::add); 

If you find yourself doing this a lot, may be spin a custom collector:

 public static Collector<Offer, ?, List<Offer>> minCollector() {     class Acc {          Offer min = null;         List<Offer> result = new ArrayList<>();          void add(Offer offer) {             if (offer.getType() == OfferType.STANDARD) {                 if (min == null) {                     min = offer;                 } else {                     min = offer.getPrice()                                .compareTo(min.getPrice()) > 0 ? min : offer;                 }             } else {                 result.add(offer);             }         }          Acc combine(Acc another) {             this.min = reduceMin(this.min, another.min);             result.addAll(another.result);             return this;         }          List<Offer> finisher() {             result.add(min);             return result;         }          private Offer reduceMin(Offer left, Offer right) {             return Collections.min(Arrays.asList(left, right),                                    Comparator.nullsLast(Comparator.comparing(Offer::getPrice)));         }     }      return Collector.of(Acc::new, Acc::add, Acc::combine, Acc::finisher); } 

And usage would be:

List<Offer> result =                            .collect(minCollector()); 


:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: