Home > Blockchain >  Java List, Partition by and Get Final Item in Sort
Java List, Partition by and Get Final Item in Sort

Time:01-03

I have a List of ProductTransactions. I want to find the Final (largest) productTransactionId sale for each product in List<ProductTransaction> . So I partition this by ProductId, and order by ProductTransactionId. Final List in example below List<Integer> (2, 5, 9) How can this be done? I am trying to use stream and filter.

@Data
public class ProductTransaction {
    private int productTransactionId;
    private int productId;
    private Date saleDate;
    private BigDecimal amount;
}
ProductTransactionId ProductId SaleDate Amount
1 1 3/2/2019 5
2 1 4/1/2019 9
3 2 4/1/2019 2
4 2 8/21/2019 3
5 2 8/21/2019 4
6 3 10/1/2019 2
7 3 10/3/2019 5
8 3 10/3/2019 7
9 3 10/3/2019 8

(please ignore the SaleDate, only sort by ProductTransactionId; The table input data, may not be necessarily sorted

currently using Java 8

Attempt:

current Long Solution (want to make cleaner short hand, or perhaps faster performance)

Set<Long> finalProductTransactionIds = new HashSet<>();
    
Set<Long> distinctProductIds =  productTransactions.stream()
        .map(ProductTransaction::getProductid)
        .collect(Collectors.toSet());

for (Long productId: distinctProductIds) {
    Long productTransactionId = productTransactions.stream()
            .filter(x -> x.getProductId() == productId])
            .sorted(Comparator.comparing(ProductTransaction::getProductTransactionId)
            .reversed())
            .collect(Collectors.toList()).get(0).getProductTransactionId();
    finalProductTransactionIds.add(productTransactionId);
}

CodePudding user response:

If you don't mind unwrapping Optionals, you can group by your product id and then use a mapping maxBy downstream collector. This avoids having to collect to a temporary list, as only the last item will be kept (but adds minimal overhead for the optional instances).

final Map<Integer, Optional<Integer>> map = transactions.stream()
        .collect(
                Collectors.groupingBy(
                        ProductTransaction::getProductId,
                        Collectors.mapping(
                                ProductTransaction::getProductTransactionId,
                                Collectors.maxBy(Comparator.naturalOrder()))));

final Collection<Optional<Integer>> optionalMax = map.values();
final List<Optional<Integer>> max = optionalMax.stream()
        .filter(Optional::isPresent)
        .collect(Collectors.toList());

It is also possible to use the special overload of the toMap collector to avoid the Optional type:

final Collection<Integer> maxTransactionIds = transactions.stream()
        .collect(
                Collectors.toMap(
                        ProductTransaction::getProductId,
                        ProductTransaction::getProductTransactionId,
                        BinaryOperator.maxBy(Comparator.naturalOrder())))
        .values();

Thanks to Eritrean for pointing out that getProductId returns an int, so we can replace the generally applicable BinaryOperator.maxBy(Comparator.naturalOrder) with the shorter Math::max (Math#max(int,int)) method reference, which will return the larger value of two integers:

final Collection<Integer> maxTransactionIds = transactions.stream()
        .collect(
                Collectors.toMap(
                        ProductTransaction::getProductId,
                        ProductTransaction::getProductTransactionId,
                        Math::max))
        .values();

And maybe you don't like the Stream API. You can use a regular loop and the Map#merge function to achieve the same end result. If you squint, the merge call even looks like the toMap collector (why that is, is left as an exercise to the reader :)).

final Map<Integer, Integer> maxTxPerProduct = new HashMap<>();
for (final ProductTransaction transaction : transactions) {
    maxTxPerProduct.merge(
            transaction.getProductId(),
            transaction.getProductTransactionId(),
            Math::max);
}
final Collection<Integer> max = maxTxPerProduct.values();

It definitely avoids creating stream and collector objects (which is rarely a problem anyway).

CodePudding user response:

Stream over your list and collect to map using productId as key and productTransactionId as value. If one or more objects share the same productId, take the one with the highest productTransactionId using Math::max and get the values of the map:

List<Integer> result =  new ArrayList<>(
        productTransactions.stream()
                           .collect(Collectors.toMap(ProductTransaction::getProductId, 
                                                     ProductTransaction::getProductTransactionId,
                                                     Math::max))
                           .values());

CodePudding user response:

You can achieve it with a little bit of collectors and grouping by. You can follow this helpful article for reference

    Map<Integer, List<Integer>> productTransactionIdsByProductId = transactionList.stream()
            .collect(Collectors.groupingBy(
                    ProductTransaction::getProductId,
                    Collectors.mapping(ProductTransaction::getProductTransactionId, Collectors.toList())));

    final List<Integer> latestTransactionIds = new ArrayList<>();

    productTransactionIdsByProductId.forEach( (k,v)-> {
        if(!v.isEmpty())
            latestTransactionIds.add(v.get(v.size()-1));
    });
    System.out.println(latestTransactionIds);

CodePudding user response:

Using stream

record A(int tId, int pId, double amount) {

}

List<A> list = List.of(
        new A(6, 3, 2),
        new A(7, 3, 5),

        new A(3, 2, 2),
        new A(4, 2, 3),
        new A(5, 2, 4),

        new A(1, 1, 5),
        new A(2, 1, 9),

        new A(8, 3, 7),
        new A(9, 3, 8)
);

Map<Integer, List<A>> grouped = list.stream()
        .collect(Collectors.groupingBy(A::pId));

grouped.forEach((integer, as) -> as.sort(Comparator.comparing(A::tId).reversed()));
List<Integer> integers = grouped.values().stream()
        .map(as -> as.stream().map(A::tId).findFirst().orElse(0))
        .collect(Collectors.toList());

System.out.println(grouped);
System.out.println(integers);

[2, 5, 9]

CodePudding user response:

BE SIMPLE !!!

Remember, that support of the code is much more complicated that implemnting. It is better to write smth. with a bit more lines, but much more clear.

E.g. Streams are quitre efficient, but sometime much more complicated to realise how it does it's work. In case you can write smth without it, do think about. Probably it can be more clear than streams.

public static List<Integer> getLargest(List<ProductTransaction> transactions) {
    Map<Integer, Integer> map = new HashMap<>();

    for (ProductTransaction transaction : transactions) {
        int productId = transaction.getProductId();
        map.put(productId, Math.max(map.getOrDefault(productId, 0),
                                    transaction.getProductTransactionId()));
    }

    return new ArrayList<>(new TreeMap<>(map).values());
}
  • Related