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

o8x7eapl  于 2023-01-07  发布在  Java
关注(0)|答案(7)|浏览(125)

bounty将在4天后过期。回答此问题可获得+50的声誉奖励。mattsmith5正在寻找来自声誉良好来源的答案

我有一个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);
}
ovfsdjhp

ovfsdjhp1#

如果你不介意打开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();

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

czfnxgou

czfnxgou2#

在列表中进行流式传输,并使用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());
q1qsirdb

q1qsirdb3#

您可以通过一些收集器和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);
jhdbpxl9

jhdbpxl94#

使用流

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

[二、五、九]

cwdobuhd

cwdobuhd5#

简单点!

记住,代码的支持比实现要复杂得多。最好用多一点的行来写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());
}
x6492ojm

x6492ojm6#

如果您对第三方库持开放态度,StreamEx提供了一些很好的助手来进行更高级的转换:

List<Integer> result = StreamEx.of(productTransactions)
        .mapToEntry(
                ProductTransaction::getProductId,
                ProductTransaction::getProductTransactionId)
        .collapseKeys(Math::max)
        .values()
        .toList();
shstlldc

shstlldc7#

流到一个Map中,在所需的键(在您的例子中是productId)上累积,但当您遇到同一个键的多个值时,在Map合并上按最大数量解析-下面的BinaryOperator.maxBy。

List<ProductTransaction> list = List.of(
new ProductTransaction(1,   1,  "3/2/2019", 5),
new ProductTransaction(2,   1,  "4/1/2019", 9),
new ProductTransaction(3,   2,  "4/1/2019", 2),
new ProductTransaction(4,   2,  "8/21/2019",    3),
new ProductTransaction(5,   2,  "8/21/2019",    4),
new ProductTransaction(6,   3,  "10/1/2019",    2),
new ProductTransaction(7,   3,  "10/3/2019",    5),
new ProductTransaction(8,   3,  "10/3/2019",    7),
new ProductTransaction(9,   3,  "10/3/2019",    8));

Map<Integer, ProductTransaction> result = list.stream()
        .collect(Collectors.toMap(tx -> tx.productId, Function.identity(),
            BinaryOperator.maxBy(Comparator.comparingDouble(tx -> tx.amount.doubleValue()))));

System.out.println(result.values().stream().map(tx -> tx.productTransactionId).collect(Collectors.toList()));

打印:[2、5、9]

相关问题