Home > Software design >  How to get maximum value from stream after computation
How to get maximum value from stream after computation

Time:09-10

I have a function which computes a value from an object. I have code like this:

List.stream().max(t1, t2)->Float.compare(complexFunction(x, t1), complexFunction(x,t2)).get();

This successfully gets me the element that computes that highest value, but I want to get the value as well. What's the best way to do this? I'm thinking that I need to create a map first with all the elements and the values, and then do the max operation after that. Is there anything better?

CodePudding user response:

You could use a Map, but using a class might be easier.

Define a record to hold each input float, and its resulting computation. Note that a record can be defined locally within a method, or defined separately.

In the old days, some people used AbstractMap.SimpleEntry, AbstractMap.SimpleImmutableEntry, or their interface Map.Entry, or in Java 9 the convenient Map.entry(…) method, as a tuple for holding multiple values together. In Java 16 , the need for that vanishes. The new Records feature makes the code more explicit about your invention of a type. Having named fields makes the meaning clear. All the inherited Object methods being implicitly created by the compiler makes defining a record utterly simple. And the fact that a record can be defined locally keeps the code minimal and tidy.

Core example code:

record Comp( Float f , float computed ) { }  // Locally defines an entire class to communicate data transparently and immutably.
Optional < Comp > compOptional =
        list
                .stream()
                .map(
                        f -> new Comp( f , complexFunction( f ) )
                )
                .max(
                        Comparator.comparingDouble( Comp :: computed )
                );

We could optimize a bit if we do not care about collecting all the Comp records. We could avoid instantiation of all but the winning Comp record by simply flipping the order of .map and .max.

record Comp( Float f , float computed ) { }  // Locally defines an entire class to communicate data transparently and immutably.
Optional < Comp > compOptional =
        list
                .stream()
                .max(
                        Comparator.comparingDouble( App :: complexFunction )
                )
                .map(
                        f -> new Comp( f , complexFunction( f ) )
                );

Full example code:

package work.basil.example.modmax;

import java.util.Comparator;
import java.util.List;
import java.util.Optional;

public class App
{
    public static void main ( String[] args )
    {
        App app = new App();
        app.demo();
    }

    private void demo ( )
    {
        List < Float > list = List.of( 7F , 99F , 42F , 1F );

        record Comp( Float f , float computed ) { }  // Locally defines an entire class to communicate data transparently and immutably.
        Optional < Comp > compOptional =
                list
                        .stream()
                        .max(
                                Comparator.comparingDouble( App :: complexFunction )
                        )
                        .map(
                                f -> new Comp( f , complexFunction( f ) )
                        );

        System.out.println( "compOptional = "   compOptional );
    }

    static float complexFunction ( Float f )
    {
        return f * 2;
    }
}

When run:

Optional[Comp[f=99.0, computed=198.0]]

You could play around with Float versus float to minimize auto-boxing. But for small amounts of data infrequently processed, I would not bother.

CodePudding user response:

If I understood the problem correctly, you need to obtain only a single object for which your function produces maximum and its corresponding value.

Judging by the usage of get(), I assume that you're OK with throwing an exception in case if the optional is empty. If you're using Java 10 orElseThrow() is a preferred option because it explicitly conveys your intention to the reader of the code.

Here's how you can generate an auxiliary Map and associating each object with a value returned by the function using Collectors.toMap(), and then pick the entry containing the highest value.

Map.Entry<MyType, Float> maxEntry = list.stream()
    .collect(Collectors.toMap(
        Function.identity(),        // key
        t -> complexFunction(x, t), // value
        (left, right) -> left       // resolving duplicates
    ))
    .max(Map.Entry.comparingByValue())
    .orElseThrow();
  • Related