Search code examples
javarandom

How to make a random event in java


I want to write a java program where there 50% chance for event a to occur, 35% chance for event b to occur and 15% chance for event c to occur and finally print the event which took place. Thanks.

I tried using math.random but it ended up being too complicated for me to understand.


Solution

  • Math.random() should almost never be used. It oversimplifies the job of 'create randomness' and in doing so, just adds confusion and subtle bugs.

    Instead, the right way to do randomness is to work with an instance of java.util.Random.

    A trivial way to make one is simply new Random(). There are variants on the theme - you can for example go with SecureRandom.getInstanceStrong() instead - just new Random() tends to already be a source of randomness that is robust enough for security principles, but SecureRandom.getInstanceStrong() guarantees this and will crash (throw an exception) if for whatever reason the system is not capable of providing a random source that is suitable for use for providing randomness when generating keys for cryptography and the like. As a beginner, just use new Random().

    An instance of j.u.Random has various methods. The most important one is .nextInt(x). This method returns a uniform-random value between 0 (inclusive) and x (exclusive). Thus:

    Random r = new Random();
    
    …
    
    // Roll 10 dice!
    for (int i = 1; i <= 10; i++) {
      System.out.println("Diceroll " + i + ": " + (rnd.nextInt(6) + 1));
    }
    

    rnd.nextInt(6) can give 6 possible answers: 0, 1, 2, 3, 4, or 5. It will return one chosen uniformly randomly (uniform, meaning, each of those 6 options is exactly as likely as any of the other ones).

    You may have read in a book or tutorial that you should use 1 + (int) (Math.random() * 6) instead. This is subtly broken. It takes a university level understanding of the fundamentals of informatics to know why1. Besides, rnd.nextInt(6) is simply easier to read and easier to use.

    Let's now look at your question. You've stated all cases in nice whole percentages, that add up to 100. So, then, that's easy!

    One trick is that you just have one single randomness occurrence - one roll of the dice and off of that single roll we know whether to do event A, or event B, or event C. It's just.. it's a die with 100 faces. 50 of the faces should result in A, 35 of the faces in B, and the remaining 15 faces should result in C.

    Hence, we only want a single random number. Possibly you're trying to do this by first determining randomly if 'event A' occurs, and then if A did not occur, use a second random number to determine of 'B' should occur. This is just making things needlessly complicated, don't do that. Ask for one random number only:

    int x = rnd.nextInt(100);
    // x is now 0, 1, 2, 3, 4, 5, ...., 97, 98, or 99. Uniformly chosen.
    if (x < 50) {
     // 0-49 are all choices that match here, that's exactly 50%.
     eventA();
    } else if (x < 85) {
     // 50-84 goes here, that's 35 options: 35% chance
     eventB();
    } else {
     //85-99 go here, that's 15 options, 15% chance
      eventC();
    }
    

    The only 'trick' we had to apply is to keep summing the odds. '85' is in the code above because 50+35 is 85. If you find this complicated you can let the computer do the math, if you prefer:

    int z = rnd.nextInt(100);
    int odds = 0;
    
    /* Event A, 50% chance */
    odds += 50;
    if (z < odds) {
       eventA();
       return; // or break - but we most not go any further than here!
    }
    
    /* Event B, 35% chance */
    odds += 35;
    if (z < odds) {
      eventB();
      return;
    }
    
    /* event C, 15% chance */
    odds += 15;
    if (z < odds) {
      eventC();
      return;
    }
    
    assert odds == 100;
    
    throw new RuntimeException("We cannot possibly get here");
    

    (1) If this goes over your head, it's not crucial to understand it - just know it's subtly broken. As in, broken, but you won't notice when testing and developing, so it shouldn't be used! - but, for the curious: It's the pigeonhole principle: Math.random() generates a double. Computers are finite concepts, so there is a large, but finite, amount of numbers that it can return. Unless that universe of answers it can give consists of Z numbers, where Z is exactly divisible by 6, then by pigeonhole principle we can prove that 1 + (int)(Math.random() * 6) cannot be exactly uniform: After all, if Z is, say, 16 numbers, how do you map those 16 results onto 6 outcomes such that each of the 6 is equally likely? That's not possible. QED. Computers, being binary things, tend to have everything be powers of 2. 6 is not a power of 2, hence it is rather unlikely Math.random()'s output universe so happens to have a size that is exactly divisible by 6. And, without getting into the specifics of how double in fact works, indeed, it isn't. Thus, not uniform.