36

In C#, how do I get a random number from a range of values - like 1..100, but that number should not be in some specific list of values, like 5, 7, 17, 23?

iusting
  • 7,850
  • 2
  • 22
  • 30
tenthplanet0
  • 373
  • 1
  • 3
  • 6

11 Answers11

56

Since no-one has posted any example code:

private int GiveMeANumber()
{
    var exclude = new HashSet<int>() { 5, 7, 17, 23 };
    var range = Enumerable.Range(1, 100).Where(i => !exclude.Contains(i));

    var rand = new System.Random();
    int index = rand.Next(0, 100 - exclude.Count);
    return range.ElementAt(index);
}

Here's the thinking:

  1. Build a Hashset of numbers you want to exclude
  2. Create a collection of all the numbers 0-100 which aren't in your list of numbers to exclude with a bit of LINQ.
  3. Create a random object.
  4. Use the Random object to give you a number between 0 and the number of elements in your range of numbers (inclusive).
  5. Return the number at that index.
Bridge
  • 29,818
  • 9
  • 60
  • 82
  • I am getting error in the 7th line : int index = rand.Next(0,100-exclude.Count); because of (.Next()), in which system directive i can find this? the error I am getting is error CS1061: Type `UnityEngine.Random' does not contain a definition for `Next' and no extension method `Next' of type `UnityEngine.Random' could be found (are you missing a using directive or an assembly reference?) – tenthplanet0 Aug 31 '13 at 16:21
  • @Bridge, thanks for the code, But I am not getting unique numbers – tenthplanet0 Sep 02 '13 at 05:46
  • 6, 4, 19, 4, 11, 14, 14, 14, 4, 4, 19, 4, 17, 6, 17, 18, 15, 17, 15, 13 are the numbers I got when changed the range from 1 to 20 – tenthplanet0 Sep 02 '13 at 05:52
  • 1
    @tenthplanet0 You never mentioned you wanted unique results, the code I gave here only generates a single number. It all depends how you're generating your numbers: if you're using LINQ it might just be a matter of adding `.Distinct()`, or if you're adding your numbers to a `List` or similar, how about adding them to a `Hashset` (as in the answer here, only permits unique values) instead? If this doesn't cover it, I suggest posting the code you've written to generate that sequence as a new question. – Bridge Sep 02 '13 at 07:28
  • If the range is very huge and the possible numbers to exclude is very small this is pretty ineffficient because you have to create a huge list/set first just to pick out a number. Then a `while`-loop that runs if the generated random number should be excluded would be much more efficient(trial&error). Note that `ElementAt` needs to enumerate the whole sequence to find the index. – Tim Schmelter Sep 25 '17 at 08:44
  • @TimSchmelter Fair enough, but I couldn't think of a better way to do this four years ago - feel free to use one of the other potentially more efficient answers posted in the years since! – Bridge Sep 25 '17 at 10:01
  • Thanks for your helping but the problem is that the asp.net MVC framework 6.41 does'nt know Exclude.Contains .how can we use it . – mahdi Nov 16 '21 at 13:18
  • 1
    @tenthplanet0 You probably solved this but for other Unity users having issues: instead of `rand.Next(0, 100 - exclude.Count);` use `Random.Range(0, 100 - exclude.Count);`. Unity provides it's own static Random overwriting the system one. You will also need to add `using System.Linq;` at the top of your file otherwise the Enumerable will not be found. – iusting Mar 01 '23 at 10:56
10

If you care about Big O, check out this algorithm. It assumes that the excluded values array is sorted in ascending order and contains values within 0 and n-1 range (inclusive).

public static int random_except_list(int n, int[] x) 
{
    Random r = new Random();
    int result = r.Next(n - x.Length);

    for (int i = 0; i < x.Length; i++) 
    {
        if (result < x[i])
            return result;
        result++;
    }
    return result;
}

If you call it with:

random_except_list(8, new int[]{3,4,6})

it will return one of the following values: 0, 1, 2, 5, 7.

Voicu
  • 16,921
  • 10
  • 60
  • 69
2

This is the Extention method I use:

Random random = new Random();
public static int RandomNumber(int minN, int maxN, IEnumerable<int> exNumbers)
    {
        int result = exNumbers.First(); 
        while (exNumbers.ToList().Contains(result))
        {
            result = random.Next(minN, maxN + 1);
        }
        return result;
    }
Mayer Spitz
  • 2,577
  • 1
  • 20
  • 26
1

This is what I do in this situation, it's not perfect but works well for me. I usually do it only for 1 number but this is how it can be for a group of excluded numbers:

Let's say I want to exclude [5, 7, 17, 23] from a random between 1-100. I always have a substitution for each of the excluded numbers such as [6, 8, 18, 24]. If the random number falls into any of the excluded numbers, I replace it with its substitution.

I came here looking for a better solution but I couldn't find any so I ended up sharing mine.

Maher Manoubi
  • 585
  • 1
  • 6
  • 18
1

you can use a do-while statement to pick another Random if it equals what number you want to exclude. this code is to exclude the number you picked before

    int newNumber;
do {
    newNumber = Random.Range (0, 100);
} while(number == newNumber);

number = newNumber;
1

From Java, but I'm pretty sure you can change the language easily :)

Solution:

    /**
     * Get a random number between a range and exclude some numbers
     *
     * @param start start number
     * @param end end number
     * @param excludes list of numbers to be excluded
     * @return value between {@code start} (inclusive) and {@code end} (inclusive)
     */
    private int getRandomWithExclusion(int start, int end, List<Integer> excludes) {
        Collections.sort(excludes); // this method only works with sorted excludes

        int random = start + new Random().nextInt(end - start + 1 - excludes.size());
        for (int exclude : excludes) {
            if (random < exclude) {
                break;
            }
            random++;
        }
        return random;
    }
0

Put the allowed numbers into an array, generate a random integer from 0 to the length of this array minus one. Use this integer as an index to get the random number itself from the array of allowed numbers.


If the original array contains large objects instead of numbers, then making another array by deep copying the allowed objects won't be effective. In this case, the array of allowed objects should contain only a pointer, a reference, or an index to the objects in the original array. In this case you generate a random integer to select one element of this array, and use this pointer/reference/index to get the selected object itself from the original array.

Here is a working example for the general case (just one possible solution!):

using System;
using System.Collections.Generic;

public static class RandomElementSelector
{
    public static IList<T> CollectAllowedElements<T>(IList<T> allElements, IList<T> excludedElements)
    {
        List<T> allowedElements = new List<T>();
        foreach (T element in allElements)
            if (!excludedElements.Contains(element))
                allowedElements.Add(element);
        return allowedElements;
    }

    public static T SelectRandomElement<T>(IList<T> allowedElements)
    {
        Random random = new Random();
        int randomIndex = random.Next(allowedElements.Count);
        return allowedElements[randomIndex];
    }

    public static T SelectRandomElement<T>(IList<T> allElements, IList<T> excludedElements)
    {
        IList<T> allowedElements = CollectAllowedElements(allElements, excludedElements);
        return SelectRandomElement(allowedElements);
    }
}

public class Test
{
    public static void Main()
    {
        const int N = 100;

        // Example #1
        int[] allNumbers = new int[N];
        for (int i = 0; i < allNumbers.Length; ++i)
            allNumbers[i] = i + 1;
        int[] excludedNumbers = { 5, 7, 17, 23 };
        Console.WriteLine(RandomElementSelector.SelectRandomElement(allNumbers, excludedNumbers));

        // Example #2
        List<string> allStrings = new List<string>();
        for (int i = 0; i < N; ++i)
            allStrings.Add("Item #" + (i + 1));
        string[] excludedStrings = { "Item #5", "Item #7", "Item #17", "Item #23" };
        Console.WriteLine(RandomElementSelector.SelectRandomElement(allStrings, excludedStrings));
    }
}
kol
  • 27,881
  • 12
  • 83
  • 120
  • Thanks for the response. Can you be more specific? Can I get the code for this? as my array can grow huge – tenthplanet0 Aug 28 '13 at 10:14
  • hey kol, thanks for your time. I have asked one more question at another link. Would like to have solution from you if you desire to...http://stackoverflow.com/questions/18568050/how-to-get-a-list-of-random-numbers-that-should-be-unique – tenthplanet0 Sep 02 '13 at 07:50
0

Create an array containing all the numbers you want (or whatever container your language uses) minus all the number you don't want and select at random from the array.

stmfunk
  • 663
  • 5
  • 20
0

Try this:

public static int GetRandomNumber(int Min, int Max, int[] ExcludedNumbers)
    {
        Random randomGenerator = new Random();
        int currentNumber = randomGenerator.Next(Min, Max + 1);

        while (ExcludedNumbers.Contains(currentNumber))
        {
            currentNumber = randomGenerator.Next(Min, Max + 1);
        }
        return currentNumber;
    }

Here is an example of its usage:

int randomNumber = GetRandomNumber(1, 100, new int[] { 5, 7, 17, 23 });
Jonathan Barraone
  • 489
  • 1
  • 3
  • 20
0

Edit: Only for Unity3D, I didn't know that Random is a class from Unity. Still it might help someone, so I'll leave it here.

I'm super late to answer the question, but let's contribute to the conversation. This is my expansion of the already known Random.range(x,y), without lists or something more complicated:

public static int RandomExceptList(int min, int max, int[] forbiddenElements) 
{
    bool numberAllowed = true;
    
    for(int i=0;i<forbiddenElements.Length;i++)
    {
        if(forbiddenElements[i]<min || forbiddenElements[i]>max)
        {
            Debug.Log("All forbidden numbers have to be inside given range.");
            return min;
        }
        for (int j=i+1; j<forbiddenElements.Length;j++)
        {
            if(forbiddenElements[i]==forbiddenElements[j])
            {
                Debug.Log("Forbidden numbers have to be different between each other.");
                return min;
            }
        }
    }
        
    if (max<min)
    {
        Debug.Log("Minimum limit has to be lower than maximum limit...");
        return min;
    }
    
    if (forbiddenElements.Length>(max-min))
    {
        Debug.Log("You can't forbid so many numbers.");
        return min;
    }
    
    if (forbiddenElements.Length>=1)
    {
        //this part here checks indefinitely for an allowed randomly generated number (outside "forbiddenElements" array). Eventually it will find it and return it and therefore terminate the loop.
        while(true)
        {
            numberAllowed=true;
            
            int r = Random.Range(min,max+1);
        
            for(int i=0;i<forbiddenElements.Length;i++)
            {
                if (r==forbiddenElements[i])
                    numberAllowed=false;
            }
            if(numberAllowed)
                return r;
        }
    }
    else
    {
        int r = Random.Range(min,max+1);
        return r;
    }
}

Note that in this case, you must always enter a forbiddenElements parameter, even if it's an empty array. Random.range doesn't use top limit in the number pool, so I've added "max+1". Hope this helps

-1

Use a function to generate random numbers between 1 and 100, than write an if statement e.g. if random number is equal to 5, 7, 17, 23, generate the random number again, else use the random number that was generated in the first place.

Saint
  • 480
  • 1
  • 6
  • 21