Random Numbers With Exceptions

Hello!
I want to randomly generate 4 numbers between 0 - 150. But, I don’t want any of the numbers to be the same. How would I do that in 1 frame??

Thank you-!

Make a HashSet and while there are fewer than 4 items in it, add a random number from 0 to 150

Then pluck the four guaranteed unique numbers out of the HashSet

2 Likes

You could generate a random number, and compare it to the previous random numbers. If the new one is a duplicate you throw it away and try again. If it is unique, you keep it. You repeat until you have kept 4 numbers. Technically it is not impossible for the same 4 random numbers to always be the numbers generated randomly, getting you stuck in an infinite loop, but the chances of that happening are infinitesimally small.

Technically Joe-Censored, that’s what Kurt-Dekker’s suggestion does since a HashSet is unordered and unique (you can’t add the same entry twice).

I will say the distribution of both of your suggestions won’t be uniform, but OP likely doesn’t care about that. And it wouldn’t really be noticeable since most sets are so small.

1 Like

Good point. They won’t.

EDIT to my original post: Shuffle 150 numbers, pick the first 4.

Pretty sure you could take care of that “in one frame.” :slight_smile:

Good spot Lord!

EDIT to the EDIT: @lordofduct now I’m wondering… are you sure?? Coz once that first number is taken, even though I’m re-randomizing over the next 150 and COULD double it, when I do re-choose the same number, I drop it… ar… probabilities are hard to reason about.

Like taking 2 numbers out of possible 3 numbers… intuitively it seems my first approach would be balanced probability…

Another solution would be to just take that same set of 150 numbers and select 4 at random indices, and dropping out the selected as you go.

Note this would effectively be the same because the only uniform shuffles that exist out there basically is the act of picking at random and placing it at some vertex.

Such as this shuffle algorithm:

        public static void Shuffle<T>(T[] arr, IRandom rng = null)
        {
            if (arr == null) throw new System.ArgumentNullException("arr");
            if (rng == null) rng = RandomUtil.Standard;

            int j;
            T temp;
            for (int i = 0; i < arr.Length - 1; i++)
            {
                j = rng.Next(i, arr.Length);
                temp = arr[j];
                arr[j] = arr[i];
                arr[i] = temp;
            }
        }

But yeah, if you wanted to say make a poker game, or black jack, or the sort. You should set it up the same way you did at a real game. Black jack generally just gets a huge deck (like 5, 10, or N decks) shuffled together and then deals and discards as you play.

You can do this in code by just shuffling a set of numbers and filling up a Stack with them and popping them off 1 by 1 until gone.

But in the end, the original solutions are the ‘easiest’ to accomplish since well. This obsessiveness over uniformity would really only matter if uniformity was critical to your game.

1 Like

The point of my suggestion was to avoid the HashSet, since people new to C# have probably never heard of it. @Kurt-Dekker 's approach is the better one overall though IMO.

1 Like

Using the HashSet will result in your 4 numbers being sorted when you’re done (I’m not sure what order they’d be sorted into, but HashSet won’t preserve the order in which they were chosen). If you want 4 random numbers in random order that would be an issue. If the order of the chosen numbers doesn’t matter, then it should be fine.

The distribution of the 4 numbers as a set should be uniform. (Well, assuming that the random generator you were using to pick them was uniform.) e.g. the set {1, 2, 3, 4} and the set {19, 27, 108, 111} should be equally common.

As a general safety measure, when I expect a loop to terminate randomly, I usually throw in a maximum number of iterations just to be safe.

// Instead of:
while (true)
{
    int r = someRandomValue();
    if (r satisfies some criteria) break;
}

// Do:
const int maxIterations = 10000;
int i;
for (i = 0; i < maxIterations; ++i)
{
    int r = someRandomValue();
    if (r satisfies some criteria) break;
}
if (i >= maxIterations)
{
    // We tried 10,000 times and couldn't get a valid answer;
    // Give up and send some sort of error signal
}
3 Likes

Wow, that’s a lot awesome information. Thank you everyone-!
@lordofduct , thank you I’ll try to use that code and see how it goes :slight_smile:
@Kurt-Dekker Maybe I’m dumb, but I didn’t really understand how the hashsets worked :frowning:
By any chance, could you demonstrate what the hashset solution in c# would look like?

It’s a box that won’t let you put more than one copy of something in.

So you make a hashset, it has 0 elements.

  • you make a while loop that keeps looping as long as the hashset has fewer than 4 elements

  • in that loop you add a random number to it. you don’t know or care at that point… the while loop will check “did we reach 4 unique items yet?”

1 Like

Pretty much a one-to-one-translation of what I said above, plus a printer-outer:

using System.Collections.Generic;
using UnityEngine;

// @kurtdekker - pick 4 different numbers randomly from 0 to 149

public class GoodNight9 : MonoBehaviour
{
    HashSet<int> bag = new HashSet<int>();

    void Update()
    {
        bag.Clear();

        while (bag.Count < 4)
        {
            bag.Add(Random.Range(0, 150));  // 0 to 149
        }

        string s = "";

        foreach (var i in bag)
        {
            s += i.ToString() + "   ";
        }

        Debug.Log(s);
    }
}
2 Likes

Hahaha, what did I say… :smile:

You’re not dumb by the way. :slight_smile:

2 Likes

Wow, never knew there were so many different ways to solve this. Thank you so much everyone. Just tried out each approach and they all work beautifully. Thank you :slight_smile:

One last question @Kurt-Dekker . How would I augment the code so that if I had an array of ints.
I could fill them in with the numbers instead of a string?

So like the result might look something like:
value[0] = 3;
value[1] = 1;
value[2] = 0;
value[3] = 2;

1 Like

If you use a List instead of an array you can just go

myList.AddRange(bag);

If you want an actual array, then it would look more like

int[] myArray = new int[bag.Count];
int index = 0;
foreach (int val in bag)
{
    myArray[index] = val;
    ++index;
}
2 Likes