代码之家  ›  专栏  ›  技术社区  ›  Druckles

使用Java8流方法获取最后一个最大值

  •  16
  • Druckles  · 技术社区  · 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. 事先把清单倒过来
    7 回复  |  直到 7 年前
        1
  •  9
  •   jvdmr    7 年前

    从比较器中删除equals选项(如果比较的数字相等,则不返回0,而是返回-1)(即,编写不包含equals选项的比较器):

    Thing t = items.stream()
            .max((a, b) -> a.getI() > b.getI() ? 1 : -1)
            .orElse(null);
    
        2
  •  5
  •   Naman    7 年前

    从概念上讲,您似乎在寻找类似 thenComparing 使用 index

    Thing t = items.stream()
            .max(Comparator.comparingLong(Thing::getI).thenComparing(items::indexOf))
            .orElse(null);
    
        3
  •  3
  •   MikeFHay    7 年前

    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);
    
        4
  •  1
  •   davidxxx    7 年前

    如果您按两个步骤进行操作,则流不是必需的:

    1) 找到 i 值,该值在 Iterable (和你一样)
    2) 在最后一个元素中搜索此元素 项目

    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)
    
        5
  •  1
  •   tobias_k    7 年前

    valueFunction现在需要调用近两倍的频率。

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

    直接将计算出的值缓存在 Thing 例子如果这是不可能的,您可以使用外部 Map 和使用 calculateIfAbsent 然后使用您的方法使用 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);
    
        6
  •  0
  •   Ravindra Ranwala    7 年前

    你仍然可以使用减少来完成这件事。如果t1更大,那么只有它才能保持t1。在所有其他情况下,它将保持t2。如果t2较大或t1和t2相同,则最终将返回符合您要求的t2。

    Thing t = items.stream().
        reduce((t1, t2) -> t1.getI() > t2.getI() ? t1 : t2)
        .orElse(null);
    
        7
  •  0
  •   ETO    7 年前

    您当前的实现使用 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);