javacollectionsjava-streaminfinite-looprandom-access

No result when filtering on a random IntStream, in Java


I am representing a collection of turtles, each with a moment when hatched. I want to randomly choose any mature turtle.

I pretend that minutes are years, in this aquarium simulation.

record Turtle(
        String name ,
        Instant hatched ,
        Duration lifespan
)
{
    static final Duration MATURITY = Duration.ofMinutes ( 4 );

    Duration age ( ) { return Duration.between ( this.hatched , Instant.now ( ) ); }
}

I use ThreadLocalRandom to generate an IntStream of random integers. I use those integers as an index into my List of Turtle objects. I check the age of that turtle, to see if it exceeds the predefined MATURITY duration. If not mature, let the stream continue.

Of course, no turtles may yet be mature. So I defined the return type as Optional < Turtle >, to be empty if no turtle found.

The problem is that my stream seems to fail, with no result ever returned. Why?

public class Aquarium
{
    public static void main ( String[] args )
    {
        System.out.println ( "INFO Demo start. " + Instant.now ( ) );

        // Sample data.
        List < Turtle > turtles =
                List.of (
                        new Turtle ( "Alice" , Instant.now ( ).truncatedTo ( ChronoUnit.MINUTES ).minus ( Duration.ofMinutes ( 3 ) ) , Duration.ofMinutes ( 17 ) ) ,
                        new Turtle ( "Bob" , Instant.now ( ).truncatedTo ( ChronoUnit.MINUTES ).minus ( Duration.ofMinutes ( 2 ) ) , Duration.ofMinutes ( 16 ) ) ,
                        new Turtle ( "Carol" , Instant.now ( ).truncatedTo ( ChronoUnit.MINUTES ).minus ( Duration.ofMinutes ( 1 ) ) , Duration.ofMinutes ( 18 ) ) ,
                        new Turtle ( "Davis" , Instant.now ( ).truncatedTo ( ChronoUnit.MINUTES ).minus ( Duration.ofMinutes ( 2 ) ) , Duration.ofMinutes ( 22 ) )
                );
        System.out.println ( "turtles = " + turtles );

        // Logic
        Optional < Turtle > anArbitraryMatureTurtle =
                ThreadLocalRandom
                        .current ( )
                        .ints ( 0 , turtles.size ( ) )
                        .filter (
                                ( int randomIndex ) -> turtles.get ( randomIndex ).age ( ).compareTo ( Turtle.MATURITY ) > 0
                        )
                        .mapToObj ( turtles :: get )
                        .findAny ( );
        System.out.println ( "anArbitraryMatureTurtle = " + anArbitraryMatureTurtle );

        // Wrap-up
        try { Thread.sleep ( Duration.ofMinutes ( 30 ) ); } catch ( InterruptedException e ) { throw new RuntimeException ( e ); } // Let the aquarium run a while.
        System.out.println ( "INFO Demo end. " + Instant.now ( ) );
    }
}

Solution

  • Try this for the Logic section in your main() method, adding calls to Stream#distinct and Stream#limit. This way we cover all possible index values once, in a random order.

    …
    // Logic
    Optional<Turtle> anArbitraryMatureTurtle = ThreadLocalRandom.current()
      .ints( 0, turtles.size() )
      .distinct()
      .limit( turtles.size() )
      .filter( (int randomIndex) -> turtles.get( randomIndex ).age().compareTo( Turtle.MATURITY ) > 0 )
      .mapToObj( turtles::get )
      .findAny();
    System.out.println( "anArbitraryMatureTurtle = " + anArbitraryMatureTurtle );
    …
    

    If no Turtle passes the test, we get an empty Optional.

    The code sequence:

    ThreadLocalRandom.current()
      .ints( 0, 5 )
      .distinct()
      .limit( 5 )
      .forEach( System.out::println );
    

    … prints the five values 0 to 4 in a random order – always five, no duplicates. Of course you have to use turtles.size() for your use case instead of the constant 5.

    I have some doubts that this is the most efficient way to get a random mature turtle from your list (given that there is one), but it works.