Java列表、分区和获取排序中的最终项

kqhtkvqz  于 2023-01-04  发布在  Java
关注(0)|答案(5)|浏览(76)

我有一个ProductTransactions列表。我想找到List<ProductTransaction>中每个产品的最终(最大)productTransactionId销售。因此,我按ProductId对其进行分区,并按ProductTransactionId排序。下面示例中的最终列表List<Integer> (2, 5, 9)如何完成?我正在尝试使用流和过滤器。

@Data
public class ProductTransaction {
    private int productTransactionId;
    private int productId;
    private Date saleDate;
    private BigDecimal amount;
}

| 产品交易ID|产品ID|销售日期|金额|
| - ------|- ------|- ------|- ------|
| 1个|1个|2019年3月2日|五个|
| 第二章|1个|2019年4月1日|九|
| 三个|第二章|2019年4月1日|第二章|
| 四个|第二章|2019年8月21日|三个|
| 五个|第二章|2019年8月21日|四个|
| 六个|三个|2019年10月1日|第二章|
| 七|三个|2019年10月3日|五个|
| 八个|三个|2019年10月3日|七|
| 九|三个|2019年10月3日|八个|
(请忽略销售日期,只按产品交易Id排序;表输入数据可能不需要排序
当前正在使用Java 8

尝试:

当前的长解决方案(希望使短手更干净,或者性能更快)

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

k97glaaz1#

如果你不介意打开Optionals,你可以根据你的产品ID分组,然后使用mapping + maxBy下游收集器,这样就避免了收集到临时列表,因为只会保留最后一个项目(但是为可选示例增加了最小的开销)。

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

还可以使用toMap收集器的特殊重载来避免Optional类型:

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

感谢Eritrean指出getProductId返回一个int,所以我们可以用较短的Math::maxMath#max(int,int))方法引用替换通常适用的BinaryOperator.maxBy(Comparator.naturalOrder),它将返回两个整数中较大的值:

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

也许你不喜欢Stream API,你可以使用一个常规循环和Map#merge函数来实现同样的最终结果,如果你眯着眼睛看,merge调用甚至看起来像toMap收集器(为什么这样,留给读者作为练习:)。

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

它绝对避免了创建流和收集器对象(无论如何,这很少是个问题)。

piah890a

piah890a2#

在列表中进行流式传输,并使用productId作为键,productTransactionId作为值收集到map。如果一个或多个对象共享同一个productId,则使用Math::max获取productTransactionId最大的对象,并获得map的值:

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

mlnl4t2r3#

您可以通过一些收集器和grouping by来实现它。您可以参考以下有用的article

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);
chhqkbe1

chhqkbe14#

使用流

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

[二、五、九]

lyfkaqu1

lyfkaqu15#

简单点!

记住,代码的支持比实现要复杂得多。最好用多一点的行来写smth.,但是要清楚得多。
例如Streams是相当高效的,但是有时候要实现它是如何工作的要复杂得多。如果你可以不使用它来写smth,请考虑一下。也许它会比流更清晰。

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

相关问题