使用Java 8流的方法来获取最后的最大值

问题描述 投票:16回答:7

鉴于项目与属性的列表,我试图让出现与上述财产的最大值的最后一个项目。

例如,对于对象名单如下:

t  i
A: 3
D: 7 *
F: 4
C: 5
X: 7 *
M: 6

我可以得到最高i的事情之一:

Thing t = items.stream()
        .max(Comparator.comparingLong(Thing::getI))
        .orElse(null);

然而,这将让我Thing t = D。有越来越在这种情况下的最后一个项目,即X的干净和优雅的方式?

一个可能的解决方案是使用reduce功能。但是,属性计算的飞行,它看起来更像是:

Thing t = items.stream()
        .reduce((left, right) -> {
            long leftValue = valueFunction.apply(left);
            long rightValue = valueFunction.apply(right);
            return leftValue > rightValue ? left : right;
        })
        .orElse(null);

valueFunction现在需要将近一倍,而调用。

其他明显的迂回的解决方案是:

  1. 存储对象在其索引的元组
  2. 存放在一个元组对象,其计算值
  3. 反向名单提前
  4. 不要使用流
java java-8 java-stream
7个回答
6
投票

删除等号选项(不返回0,如果比较的数字是相等的,返回-1代替)从比较(即写自己的比较,不包括等于选项):

Thing t = items.stream()
        .max((a, b) -> a.getI() > b.getI() ? 1 : -1)
        .orElse(null);

4
投票

从概念上讲,你似乎是可能寻找类似使用列表中的元素的thenComparing index

Thing t = items.stream()
        .max(Comparator.comparingLong(Thing::getI).thenComparing(items::indexOf))
        .orElse(null);

3
投票

为了避免valueFunction的多个应用程序在你的缩短解决方案,只需明确计算结果,并把它放在一个元组:

Item lastMax = items.stream()
        .map(item -> new AbstractMap.SimpleEntry<Item, Long>(item, valueFunction.apply(item)))
        .reduce((l, r) -> l.getValue() > r.getValue() ? l : r )
        .map(Map.Entry::getKey)
        .orElse(null);

1
投票

如果你在两个步骤做的事情流是没有必要的坏:

1)发现在i有多个实例Iterable值(像你一样) 2)通过从物品的端部开始搜索此i值的最后一个元素:

Thing t =  
  items.stream()
        .max(Comparator.comparingLong(Thing::getI))
        .mapping(firstMaxThing ->  
                   return
                   IntStream.rangeClosed(1, items.size())
                            .mapToObj(i -> items.get(items.size()-i))
                            .filter(item -> item.getI() == firstMaxThing.getI())
                            .findFirst().get(); 
                            // here get() cannot fail as *max()* returned something.
         )
       .orElse(null)

1
投票

该valueFunction现在需要将近一倍,而调用。

请注意,使用max即使,在getI方法将被一次又一次地呼吁每一个比较,而不仅仅是每个元素一次。在您的例子,它被称为11次,其中6次为d,而对于较长的列表,也似乎在每个单元的平均两次被调用。

怎么样,你只需直接缓存在Thing情况下的计算值?如果这是不可能的,你可以使用一个外部Map和使用calculateIfAbsent计算值仅一次为每个Thing,然后使用reduce用你的方法。

Map<Thing, Long> cache = new HashMap<>();
Thing x = items.stream()
        .reduce((left, right) -> {
            long leftValue = cache.computeIfAbsent(left, Thing::getI);
            long rightValue = cache.computeIfAbsent(right, Thing::getI);
            return leftValue > rightValue ? left : right;
        })
        .orElse(null);

还是有点清洁,事先计算所有的值:

Map<Thing, Long> cache = items.stream()
        .collect(Collectors.toMap(x -> x, Thing::getI));
Thing x = items.stream()
        .reduce((left, right) -> cache.get(left) > cache.get(right) ? left : right)
        .orElse(null);

0
投票

你仍然可以使用的减少让这件事完成。如果T1较大,那么只有它将保持T1。在所有其他情况下,将保持T2。如果任T2大于或T1和T2是相同的,那么它最终会回归T2秉承您的要求。

Thing t = items.stream().
    reduce((t1, t2) -> t1.getI() > t2.getI() ? t1 : t2)
    .orElse(null);

0
投票

您使用reduce当前的实现看起来不错,除非你的价值提取功能是昂贵的。

后来考虑到可能要重复使用不同的对象类型和领域的逻辑,我想提取逻辑本身在不同的泛型方法/方法:

public static <T, K, V> Function<T, Map.Entry<K, V>> toEntry(Function<T, K> keyFunc, Function<T, V> valueFunc){
    return t -> new AbstractMap.SimpleEntry<>(keyFunc.apply(t), valueFunc.apply(t));
}

public static <ITEM, FIELD extends Comparable<FIELD>> Optional<ITEM> maxBy(Function<ITEM, FIELD> extractor, Collection<ITEM> items) {
    return items.stream()
                .map(toEntry(identity(), extractor))
                .max(comparing(Map.Entry::getValue))
                .map(Map.Entry::getKey);
}

代码段可使用上述这样的:

Thing maxThing =  maxBy(Thing::getField, things).orElse(null);

AnotherThing maxAnotherThing = maxBy(AnotherThing::getAnotherField, anotherThings).orElse(null);
© www.soinside.com 2019 - 2024. All rights reserved.