2

Let's say I have a stream of following object.

class MyObject {
    private Some some; // Comparable
    private Other value
}

Is there any idiom doing following things in a single chain?

  • With given Stream<MyObject>,
  • Finds the maximum value of some.
  • Map values whose some is the maximum.

as if,

final List<MyObject> list = getList();
final Some max = list.stream().max(Comparator.naturalOrder())
final List<Other> list = list.stream()
        .filter(e -> Objects.equals(e.some, max)
        .map(e -> e.getValue()).collect(toList());
ernest_k
  • 44,416
  • 5
  • 53
  • 99
Jin Kwon
  • 20,295
  • 14
  • 115
  • 184
  • 1
    The stream only performs 1 passthrough when you do the terminal operation, so in order to do the filtering you have to already know the maximum. Hence, you need to find the maximum first, as you've already done. – marstran Mar 30 '22 at 07:28
  • 1
    By the way, be aware that a stream can only be collected once. You should start with a `List` or something and make 2 streams out of it. One for getting the maximum and the second for the filter+map. – marstran Mar 30 '22 at 07:29
  • Did you mean `.max(Comparator.naturalOrder())` or `.max(Comparator.comparing(MyObject::getSome))`? Seems the comparable type is `Some`, from your comments, not `MyObject`. – ernest_k Mar 30 '22 at 07:46

2 Answers2

1

If you have to do in one pass, you can write a custom Collector to reduce the stream into a list of max elements. Here's an implementation based on this answer by Stuart Marks.

List<MyObject> maxList = list.stream()
                             .collect(maxList(Comparator.comparing(MyObject::getSome)));

static <T> Collector<T,?,List<T>> maxList(Comparator<? super T> comp) {
    return Collector.of(
        ArrayList::new,
        (list, t) -> {
            int c;
            if (list.isEmpty() || (c = comp.compare(t, list.get(0))) == 0) {
                list.add(t);
            } else if (c > 0) {
                list.clear();
                list.add(t);
            }
        },
        (list1, list2) -> {
            if (list1.isEmpty()) {
                return list2;
            } 
            if (list2.isEmpty()) {
                return list1;
            }
            int r = comp.compare(list1.get(0), list2.get(0));
            if (r < 0) {
                return list2;
            } else if (r > 0) {
                return list1;
            } else {
                list1.addAll(list2);
                return list1;
            }
        });
}

The Collector will maintain an ArrayList to be used for the result, and accumulate each element into it, checking how the element compares to the first element of the current list. The part c = comp.compare(t, list.get(0))) == 0 will check if the element has the same max value, and if so will add it to the list.

M A
  • 71,713
  • 13
  • 134
  • 174
1

If you're on Java 12+, you can use Collectors.teeing:

List<Other> maxObjects = list.stream().collect(
    Collectors.teeing(
        Collectors.maxBy(Comparator.comparing(MyObject::getSome)),
        Collectors.groupingBy(
            MyObject::getSome,
            Collectors.mapping(
                MyObject::getValue, 
                Collectors.toList())),
        (max, groups) -> max.map(MyObject::getSome).map(groups::get).orElse(null)));
ernest_k
  • 44,416
  • 5
  • 53
  • 99