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 ( ) );
}
}
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.