10

I create a random stream

Random random = new Random();
Stream<Integer> boxed = random.ints(0, 100000000).boxed();

But I need 60% of the numbers generated to be 0, while the remaining can be truly random. How can I do it?

EDIT:

And I need only positive numbers and between 0-100

1
2
0
0
9
0
0
1
12
Eugene
  • 117,005
  • 15
  • 201
  • 306
ip696
  • 6,574
  • 12
  • 65
  • 128
  • 2
    You want to randomly generate 60% of zeroes? This makes no sense – Eugene Nov 03 '17 at 18:55
  • 1
    60% values == 0 40% values == random – ip696 Nov 03 '17 at 18:55
  • exactly 60%? or on average? i.e. is the 60-40 split also a random distribution (a coin flip) or something more deterministic? – the8472 Nov 03 '17 at 22:16
  • 6
    Your question lacks precision, as it is currently phrased returning 60 zeroes followed by 40 random numbers would fulfill fulfill it if the size can be known in advance. Or a fixed rotation for an infinite stream. – the8472 Nov 03 '17 at 22:21
  • Read [ask] and apply it to the questions you ask in the future. – Nic Nov 03 '17 at 23:11
  • 5
    You need to edit the question to clarify. Do you mean you need a 60% chance of generating a zero for each element, or do you mean that you need a list of known size where exactly 60% of them are (*nonrandomly*) 0? In the former case, you will not get exactly 60% of the list containing 0s; each element of the list will have a 60% probability of being a zero. In the latter case, you actually need to just generate 40% of the length randomly and then concatenate 60% of the length 0s (and maybe shuffle the result if necessary). The latter is a *very* strange requirement. – jpmc26 Nov 04 '17 at 00:41

8 Answers8

17

I'll assume the OP wants approximately 60% of the generated values to be zero, and the remaining approximate 40% to be (pseudo-)random values in the range 1-100, inclusive.

The JDK library makes it easy to generate a stream of N different values. Since there are 100 values in the range [1,100], and this represents 40% of the output, there need to be 150 values that map to zero to cover the remaining 60%. Thus N is 250.

We can create a stream of ints in the rage [0,249] (inclusive) and map the lowest 150 values in this range to zero, leaving the remainder in the range [1,100]. Here's the code:

    IntStream is = random.ints(0, 250)
                         .map(i -> Math.max(i-149, 0));

UPDATE

If the task is to produce exactly 60% zeroes, there's a way to do it, using a variation of an algorithm in Knuth, TAOCP Vol 2, sec 3.4.2, Random Sampling and Shuffling, Algorithm S. (I explain this algorithm in a bit more detail in this other answer.) This algorithm lets one choose n elements at random from a collection of N total elements, making a single pass over the collection.

In this case we're not selecting elements from a collection. Instead, we're emitting a known quantity of numbers, requiring some subset of them to be zeroes, with the remainder being random numbers from some range. The basic idea is that, as you emit numbers, the probability of emitting a zero depends on the quantity of zeroes remaining to be emitted vs. the quantity of numbers remaining to be emitted. Since this is a stream of fixed size, and it has a bit of state, I've opted to implement it using a Spliterator:

static IntStream randomWithPercentZero(int count, double pctZero, int range) {
    return StreamSupport.intStream(
        new Spliterators.AbstractIntSpliterator(count, Spliterator.SIZED) {
            int remainingInts = count;
            int remainingZeroes = (int)Math.round(count * pctZero);
            Random random = new Random();

            @Override
            public boolean tryAdvance(IntConsumer action) {
                if (remainingInts == 0)
                    return false;

                if (random.nextDouble() < (double)remainingZeroes / remainingInts--) {
                    remainingZeroes--;
                    action.accept(0);
                } else {
                    action.accept(random.nextInt(range) + 1);
                }
                return true;
            }
        },
        false);
}

There's a fair bit of boilerplate, but you can see the core of the algorithm within tryAdvance. If no numbers are remaining, it returns false, signaling the end of the stream. Otherwise, it emits a number, with a certain probability (starting at 60%) of it being a zero, otherwise a random number in the desired range. As more zeroes are emitted, the numerator drops toward zero. If enough zeroes have been emitted, the fraction becomes zero and no more zeroes are emitted.

If few zeroes are emitted, the denominator drops until it gets closer to the numerator, increasing the probability of emitting a zero. If few enough zeroes are emitted, eventually the required quantity of zeroes equals the quantity of numbers remaining, so the value of the fraction becomes 1.0. If this happens, the rest of the stream is zeroes, so enough zeroes will always be emitted to meet the requirement. The nice thing about this approach is that there is no need to collect all the numbers in an array and shuffle them, or anything like that.

Call the method like this:

IntStream is = randomWithPercentZero(1_000_000, 0.60, 100);

This gets a stream of 1,000,000 ints, 60% of which are zeroes, and the remainder are in the range 1-100 (inclusive).

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
14

Since the size of the target interval is divisible by ten, you can count on the last digit of generated numbers being uniformly distributed. Hence, this simple approach should work:

  • Generate numbers in the range 0..1000
  • If the last digit of the random number r is 0..5, inclusive, return zero
  • Otherwise, return r / 10

Here is this approach in code:

Stream<Integer> boxed = random.ints(0, 1000).map(r -> r%10 < 6 ? 0 : r/10).boxed();

Demo.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 4
    I like this approach. It's probably the most efficient. However, I fear it's pushing the boundary of being too "clever" to be immediately obvious what you're doing. – Michael Nov 03 '17 at 19:18
  • 2
    @Michael "too clever" is almost inevitable when you are dealing with random distributions. A heavy block of comments is in order above this line of code. There's one more issue here to consider - the percentage of zeros will be slightly above 60%. Specifically, it would be 60.4%, with the remaining 0.4% coming from zeros that occur "naturally" in the sequence. – Sergey Kalinichenko Nov 03 '17 at 19:22
  • may be I need create another question. but I ask here. it is possible to make so that 1000 000 000 000 ? – ip696 Nov 03 '17 at 19:28
  • 1
    @ip696 What do you mean? So that there are that many elements? Use the `limit` method. A trillion iterations of *anything* is going to take a while to finish, though. – Michael Nov 03 '17 at 19:29
  • I can not use limit(1000000000000) it more Integer – ip696 Nov 03 '17 at 19:34
  • This does not guarantee zero values > non zero values, it can be less. @ip696 Wasn't this a requirement in one of your coments? – Oleg Nov 03 '17 at 19:36
  • I create test task for job. I need miltiplay 2 matrix 1000_000x1000_000 I need create matrix from Stream – ip696 Nov 03 '17 at 19:38
  • @Oleg This is biased toward having more, not less, than 60% of zeros. As I mentioned in my comment above, this should give OP 60.4% of zeros under regular circumstances. – Sergey Kalinichenko Nov 03 '17 at 19:38
  • Doesn't matter what it's biased towards. You can get 0% zeros, with small amount of numbers it will happen frequently enough. If it's `100000000` numbers, then it's not a problem. There is still a chance for less zeroes then other numbers but it's negligible. – Oleg Nov 03 '17 at 19:39
  • 1
    @ip696 [This Q&A](https://stackoverflow.com/a/37078194/335858) shows how to convert a stream to a 2D matrix. Is this what you are looking for? – Sergey Kalinichenko Nov 03 '17 at 19:41
  • @dasblinkenlight It is Sparse matrix. For this matrix I use CRS format based for 3 arrays. If I will use 2D array I get OutOfMemory error – ip696 Nov 03 '17 at 19:45
  • 3
    @ip696 This sounds like an interesting question in its own right. Something like "How to convert an infinite Java stream to a sparse 2D matrix" will probably get some good answers. – Sergey Kalinichenko Nov 03 '17 at 19:59
  • 1
    Are you certain this doesn't introduce some bias into the randomness of the remaining numbers? – jpmc26 Nov 03 '17 at 22:48
  • 3
    @jpmc26 I think it's slightly biased toward 0 because both 0 and 1000 are both in the distribution so there is one more number that can end in 0 than any other number. Changing the range to 0,999 ought to fix that though. – IllusiveBrian Nov 03 '17 at 23:21
  • @IllusiveBrian That's a great catch. In fact, 999 would mean the range of possible outcomes was 0-99 incl. He would need to input the range 0-1009 to accomplish what OP wanted. – Michael Nov 04 '17 at 09:16
  • @IllusiveBrian It is already 0-999, because upper bound is *exclusive* in `Random.ints` – Sergey Kalinichenko Nov 04 '17 at 11:14
  • @dasblinkenlight I figured that might be the case, but I couldn't find documentation for the method. – IllusiveBrian Nov 04 '17 at 11:15
  • @IllusiveBrian True, the method is added only in Java-8, so finding it requires picking the right javadoc location. Anyway, [here is the link](https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#ints-int-int-). – Sergey Kalinichenko Nov 04 '17 at 11:18
7

You can use IntStream.map and re-use your Random instance to generate a random number from 0 to 9 inclusive, returning zero if it's in the first 60%, else the generated number:

Stream<Integer> boxed = random.ints(0, 100)
                              .map(i -> (random.nextInt(10) < 6) ? 0 : i)
                              .boxed();
Michael
  • 41,989
  • 11
  • 82
  • 128
  • 4
    The probability of each element you pick from the stream being 0 is 60%, but that doesn't guarantee that exactly 60% of the elements in the stream are 0 – Alexis C. Nov 03 '17 at 19:05
  • @AlexisC. I don't understand what you think the difference is – Michael Nov 03 '17 at 19:06
  • @AlexisC. The stream is infinite so there is no way to have exactly 60%. – Bubletan Nov 03 '17 at 19:06
  • Well the OP is asking to have 60% of the elements being 0, that is, if I take a stream of size 10, he expects 6 zeroes and 4 random values. Your stream can generate 10 random values or 10 zeroes or 5 zeroes and 5 random values but does not guarantee exactly 6 zeroes and 4 random values. – Alexis C. Nov 03 '17 at 19:08
  • not necessarily 60% I just need zero values > not zero values – ip696 Nov 03 '17 at 19:10
  • @AlexisC. Given that he's already dealing with randomness, it seems like approximately 60% should suffice. It's impossible to guarantee it stays at precisely 60% anyway. – Michael Nov 03 '17 at 19:10
  • 4
    Yes sure, (if you ask for 12 numbers then you can provide 7 zeroes = 58% or 8 zeroes = 66%) but your stream cannot guarantee that it will provide approximately 60% of zeroes each time you use it and this the requirement asked by the OP. – Alexis C. Nov 03 '17 at 19:18
  • @AlexisC. See the OP: out of 9 number, only 4 are `0` (= 44%). That makes me think like the 60% is a probability, like this answer does. – Olivier Grégoire Nov 03 '17 at 20:19
  • @AlexisC. The requirement as you understand it doesn't make any sense. The `0`s are not random at all if there's exactly 60%. If that's the case, you may as well just generate fewer numbers and then concatenate the exact number of zeros needed to them. (E.g., for the OP's example of 100 million, you could generate 40 million numbers and concatenate 60 million `0`s, then maybe shuffle if needed.) – jpmc26 Nov 03 '17 at 22:55
  • @jpmc26 Who said that the 0's should be random ? The OP's requirement, is: _"I need 60% of the numbers generated to be 0"_. So yes, the distribution will contain 60% of 0's and 40% of random numbers, and this answer does not guarantee that. – Alexis C. Nov 03 '17 at 23:47
  • @jpmc26, well, if you had a sequence of numbers with a known length (that is a multiple of ten), you could arrange for exactly 60 % of them to be zeroes, and 40 % something else, with the zeroes placed randomly in the sequence. But if you don't know the length of the sequence to begin with, then zeroing the numbers individually with a 60 % probability seems the only option. – ilkkachu Nov 04 '17 at 00:04
  • @AlexisC. The OP's example output contains 40% `0`s and 60% non-zeros, which strongly suggests the OP just worded things poorly. Perhaps we should just downvote and close as Unclear. – jpmc26 Nov 04 '17 at 00:32
  • @jpmc26 Well the title + the question above the edit + the accepted answer reflects that. But my point was that in any case this answer provides a way to guarantee approximately X% of 0's and 100-X% of random values which is what the OP was looking about :-). – Alexis C. Nov 04 '17 at 00:56
3

Construct 10 objects. 6 of them returns 0 all the time. and rest 4 returns random based on your specs.

Now randomly select one of the object and call

List<Callable<Integer>> callables = new ArrayList<>(10);
for (int i = 0; i < 6; i++) {
    callables.add(() -> 0);
}
Random rand = new Random();
for (int i = 6; i < 10; i++) {          
    callables.add(() -> rand.nextInt());
}

callables.get(rand.nextInt(10)).call();

This is simpler way to implement it. You can optimize it further.

jmj
  • 237,923
  • 42
  • 401
  • 438
  • What advantage does using a `List>` rather than simply using a `List>` have here. I believe `list.get(rand.nextInt(10));` would have worked just fine? – Chetan Kinger Nov 03 '17 at 19:05
  • `List>` I didn't follow, How are you thinking to structure it ? – jmj Nov 03 '17 at 19:06
  • You can hold `0` static content inside it. How would you take care of 40% random data ? – jmj Nov 03 '17 at 19:16
  • I could be wrong but what's the need of generating a new random number for the 40% cases. The OP doesn't mention that the random numbers can't be static? The 40% cases will still be random even if you use a `List` and add 6 elements using `rand.nextInt` – Chetan Kinger Nov 03 '17 at 19:20
  • 1
    I don't think that is what OP is seeking for. unless OP is referring to https://imgs.xkcd.com/comics/random_number.png – jmj Nov 03 '17 at 19:37
1

If you want exactly 60% of zeros and 40% of strictly positive numbers, you can simply use a modulus check:

Stream<Integer> boxed = IntStream.range(0, 100_000_000)
                            .map(i -> (i % 10 < 6) ? 0 : r.nextInt(Integer.MAX_VALUE) + 1)
                            .boxed();

You may want to "shuffle" the stream after that to avoid having 6 zeros in a row every ten positions.

assylias
  • 321,522
  • 82
  • 660
  • 783
1

Why not generate an array of those 60% (those are zero values to start with) and just randomly generate the other 40%:

List<Integer> toShuffle = IntStream
            .concat(Arrays.stream(new int[60_000_000]),
                    random.ints(40_000_000, 0, Integer.MAX_VALUE))
            .boxed()
            .collect(Collectors.toCollection(() -> new ArrayList<>(100_000_000)));

    Collections.shuffle(toShuffle);
Eugene
  • 117,005
  • 15
  • 201
  • 306
0

You can controll your stream to generate exactly 60% of zero by counting the generated values and producing zero to reach the desired 60%.

public class RandomGeneratorSample {
    public static void main(String... strings) {
        Random random = new Random();
        Controll60PercentOfZero controll60 = new Controll60PercentOfZero();

        Stream<Integer> boxed = random.ints(0, 100).map(x -> controll60.nextValueMustBeZero(x) ? 0 : x).boxed();

        boxed.forEach(System.out::println);
    }

    static class Controll60PercentOfZero {
        private long count_zero = 1;
        private long count_not_zero = 1;

        public boolean nextValueMustBeZero(int x) {

            if (x == 0) {
                count_zero++;
            } else {
                count_not_zero++;
            }           
            boolean nextValueMustBeZero= (count_zero * 100 / count_not_zero) < 60;
            if(nextValueMustBeZero){
                count_zero++;
            }
            return nextValueMustBeZero;
        }
    }

}
SEY_91
  • 1,615
  • 15
  • 26
0

Interesting question.
Most of other answers are very relevant and each one handles it from a different point of view.
I would like to contribute.

To get a Collection (and not a stream) with exactly 60% of 0 and that these appear "pseudo-randomly", you could :

  • declare and instantiate a List

  • then loop on the number of elements that you want to add it

  • inside it, every 6 iterations on 10, you add 0 at a random index in the List. Otherwise, you add a random value at a random index in the List.

The drawback is that about 40 % of the time, nextInt() is invoked twice: once to generate the value and another one to generate the index where inserting the value in the List.

Here is a sample code that generate 1000 elements from 0 to 1000 which exactly 60% are 0 :

Random random = new Random();
List<Integer> values = new ArrayList<>();

for (int i = 0; i < 1000; i++) {
    int nextValue = i % 10 < 6 ? 0 : random.nextInt(1000) + 1;
    int indexInList = values.size() <= 1 ? 0 : random.nextInt(values.size() - 1);
    values.add(indexInList, nextValue);
}
davidxxx
  • 125,838
  • 23
  • 214
  • 215