30. Filling a long array with pseudo-random numbers

When we think to fill up a large array with data, we can consider the Arrays.setAll() and Arrays.parallelSetAll(). These methods can fill up an array by applying a generator function for computing each element of the array.Since we have to fill up the array with pseudo-random data, we should consider that the generator function should be a pseudo-random generator. If we think to do this in parallel then we should consider the SplittableRandom (JDK 8+)/ SplittableGenerator (JDK 17+) which is dedicated to generating pseudo-random numbers in isolated parallel computations. In conclusion, the code may look as follows (JDK 17+):

SplittableGenerator splittableRndL64X256
  = RandomGeneratorFactory
     .<SplittableGenerator>of(“L64X256MixRandom”).create();
long[] arr = new long[100_000_000];
Arrays.parallelSetAll(arr,
                      x -> splittableRndL64X256.nextLong());

Or, by using SplittableRandom (this time we cannot specify the algorithm, JDK 8+):

SplittableRandom splittableRandom = new SplittableRandom();               
long[] arr = new long[100_000_000];
Arrays.parallelSetAll(arr, x -> splittableRandom.nextLong());

Next, let’s see how we can create a stream of pseudo-random generators.

31. Creating a stream of pseudo-random generators

Before creating a stream of pseudo-random generators, let’s create a stream of pseudo-random numbers. First thing first, let’s see how to do it with the legacy Random, SecureRandom, and ThreadLocalRandom. Since these three pseudo-random generators contain methods such as ints() returning IntStream, doubles() returning DoubleStream, and so on, we can easily generate an (in)finite stream of pseudo-random numbers as follows:

Random rnd = new Random();
// the ints() flavor returns an infinite stream
int[] arrOfInts = rnd.ints(10).toArray(); // stream of 10 ints
// or, shortly
int[] arrOfInts = new Random().ints(10).toArray();

In our examples, we collect the generated pseudo-random numbers in an array. Of course, you can process them as you want. We can obtain similar results via SecureRandom as follows:

SecureRandom secureRnd = SecureRandom.getInstanceStrong();
int[] arrOfSecInts = secureRnd.ints(10).toArray();
// or, shortly
int[] arrOfSecInts = SecureRandom.getInstanceStrong()
   .ints(10).toArray();

How about ThreadLocalRandom? Here it is:

ThreadLocalRandom tlRnd = ThreadLocalRandom.current();
int[] arrOfTlInts = tlRnd.ints(10).toArray();
// or, shortly
int[] arrOfTlInts = ThreadLocalRandom.current()
   .ints(10).toArray();

If you just need a stream of doubles between 0.0 and 1.0 then rely on Math.random() which internally uses an instance of java.util.Random. The following example collects an array of doubles between 0.0 and 0.5. The stream will stop when the first double larger than 0.5 is generated:

Supplier<Double> doubles = Math::random;
double[] arrOfDoubles = Stream.generate(doubles)                   
   .takeWhile(t -> t < 0.5d)
   .mapToDouble(i -> i)
   .toArray();

How about using the new JDK 17 API? The RandomGenerator contains the well-known methods ints(), doubles(), and so on and they are available in all its subinterfaces. For instance, StreamableGenerator can be used as follows:

StreamableGenerator streamableRnd
   = StreamableGenerator.of(“L128X1024MixRandom”);
int[] arrOfStRndInts = streamableRnd.ints(10).toArray();
// or, shortly
StreamableGenerator.of(“L128X1024MixRandom”)
   .ints(10).toArray();

In the same way, we can use JumpableGenerator, LeapableGenerator, and so on.Ok, now let’s get back to our problem. How to generate a stream of pseudo-random generators? All RandomGenerator subinterfaces contain a method named rngs() that comes in different flavors. Without arguments, this method returned an infinite stream of new pseudo-random generators that implement the RandomGenerator interface. The following code generated 5 StreamableGenerator instances, and each of these instances, generated 10 pseudo-random integers:

StreamableGenerator streamableRnd
   = StreamableGenerator.of(“L128X1024MixRandom”);
List<int[]> listOfArrOfIntsSG
   = streamableRnd.rngs(5) // get 5 pseudo-random generators
     .map(r -> r.ints(10)) // generate 10 ints per generator
     .map(r -> r.toArray())
     .collect(Collectors.toList());

We can accomplish the same thing with JumpableGenerator, but instead of rngs(), we may prefer jumps() which implement the behavior specific to this type of generator:

JumpableGenerator jumpableRnd
   = JumpableGenerator.of(“Xoshiro256PlusPlus”);
List<int[]> listOfArrOfIntsJG = jumpableRnd.jumps(5)                 
   .map(r -> {
        JumpableGenerator jg = (JumpableGenerator) r;
        int[] ints = new int[10];
        for (int i = 0; i < 10; i++) {
           ints[i] = jg.nextInt();
           jg.jump();
        }
        return ints;
   })
   .collect(Collectors.toList());

The same thing can be accomplished via LeapableGenerator. This time, we can use rngs(), or leaps() which implement the behavior specific to this type of generator:

LeapableGenerator leapableRnd
   = LeapableGenerator.of(“Xoshiro256PlusPlus”);
List<int[]> listOfArrOfIntsLG = leapableRnd.leaps(5)                  
   .map(r -> {
        LeapableGenerator lg = (LeapableGenerator) r;
        int[] ints = new int[10];
        for (int i = 0; i < 10; i++) {
           ints[i] = lg.nextInt();
           lg.leap();
        }
        return ints;
   })
   .collect(Collectors.toList());

Next, let’s see how we can interleave legacy and new pseudo-random generators

Leave a Reply

Your email address will not be published. Required fields are marked *