Search code examples
javajava-streammax

Obtain the Maximum Value which can be produced by a Function from a Stream Element and the Element itself


I have a function which computes a value from an object.

And 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 for which complexFunction produces the highest value, but I want to get this 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?


Solution

  • 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.