Home > Blockchain >  Comparing elements within Stream
Comparing elements within Stream

Time:01-15

Suppose I have a list of item and I transform it into list of another type using Stream. Suppose also that the original itemList is retrieved from the database and is ordered by last updated time stamp and I would want to preserve the ordering. (so that cannot sort the Stream). I want to find out the maximum price among those elements and if it is, then set the isMax indicator in the ItemResponse. How to do it if I want to do this using the same Stream ?

One way I think of is to create another Stream to compare it, however, it appears to be duplicate effort (looping over the same elements twice).

Or is it better to not Stream in this case and transform the original Stream back to for loop ?

List<Item> itemList = Arrays.asList(
                    new Item(BigDecimal.valueOf(10), 1),
                    new Item(BigDecimal.valueOf(20), 2));

List<ItemResponse> itemResponseList = itemList.stream()
                        .map(item -> {
                            ItemResponse itemResponse = new ItemResponse();
                            itemResponse.setId(item.getId());
                            itemResponse.setPrice(item.getAmount());
//adding logic to find the max and set the max indicator, but how ?
        
                            return itemResponse;
                        })
                        .collect(Collectors.toList());
    

//attempt 1 - it works, but it loops the same elements again
Optional<ItemResponse> max = itemResponseList.stream()
                            .collect(Collectors.maxBy(Comparator.comparingDouble(itemResponse -> itemResponse.getPrice().doubleValue())));
                    max.ifPresent(e -> e.setMax(true));


public class Item {

    private BigDecimal amount;
    private int id;

    Item(BigDecimal amount, int id) {
        this.amount = amount;
        this.id = id;
    }

    public BigDecimal getAmount() {
        return amount;
    }

    public void setAmount(BigDecimal amount) {
        this.amount = amount;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

public class ItemResponse {

    private BigDecimal price;
    private Boolean isMax;
    private int id;

    ItemResponse() {};

    public Boolean getMax() {
        return isMax;
    }

    public void setMax(Boolean max) {
        isMax = max;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "id: "   id   ", price: "   price   ", isMax: "   isMax;
    }
}

CodePudding user response:

Assuming that in general case multiple items may have maximum and/or minimum price, it appears that the stream approach does not help to avoid duplicate iteration. Instead, a loop should be used to convert the items into item responses and build two intermediate lists of items with maximum / minimum prices, and after that iterate the smaller lists and set appropriate indicators.

List<ItemResponse> itemResponseList = new ArrayList<>();
List<ItemResponse> maxPriced = new ArrayList<>();
List<ItemResponse> minPriced = new ArrayList<>();

BigDecimal minPrice = null;
BigDecimal maxPrice = null;

for (Item item : itemList) {
    ItemResponse ir = new ItemResponse();
    ir.setId(item.getId());
    ir.setPrice(item.getAmount());
    if (null == minPrice || minPrice.compareTo(ir.getPrice()) >= 0) {
        if (null == minPrice || minPrice.compareTo(ir.getPrice()) > 0) {
            minPrice = ir.getPrice();
            minPriced.clear();
        }
        minPriced.add(ir);
    }
    if (null == maxPrice || maxPrice.compareTo(ir.getPrice()) <= 0) {
        if (null == maxPrice || maxPrice.compareTo(ir.getPrice()) < 0) {
            maxPrice = ir.getPrice();
            maxPriced.clear();
        }
        maxPriced.add(ir);
    }
    itemResponseList.add(ir);
}
minPriced.forEach(i -> i.setMin(true));
maxPriced.forEach(i -> i.setMax(true));

Or, it may be better just to prepare and run a query on the database to get max/min prices and then use these values to set indicators in one stream.

BigDecimal[] minMax = getMinMaxPrices(); // {min. max}

List<ItemResponse> itemResponseList = itemList
    .stream()
    .map(item -> {
        ItemResponse itemResponse = new ItemResponse();
        itemResponse.setId(item.getId());
        itemResponse.setPrice(item.getAmount());
        itemResponse.setMin(item.getAmount().equals(minMax[0]));
        itemResponse.setMax(item.getAmount().equals(minMax[1]));
        return itemResponse;
    })
    .collect(Collectors.toList());

CodePudding user response:

You can make a decreasing order sorted list and update just the head of the list.

List<Item> itemList = Arrays.asList(
                    new Item(BigDecimal.valueOf(10), 1),
                    new Item(BigDecimal.valueOf(20), 2));

List<ItemResponse> itemResponseList = itemList.stream()
                        .map(item -> {
                            ItemResponse itemResponse = new ItemResponse();
                            itemResponse.setId(item.getId());
                            itemResponse.setPrice(item.getAmount());
                            return itemResponse;
                        })
                        // reverse sorting
                        .sorted(Comaparator.comparing(ItemResponse::getPrice).reversed())
                        .collect(Collectors.toList());

// only first will be updated, if the list is non-empty
itemResponseList.stream().findFirst().ifPresent(e -> e.setMax(true));
  •  Tags:  
  • Related