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());
}