0

I am implementing this code in my discord bot, where I want to be able to generate unique numbers from 1 to 10 (as suggested above) whenever I input a single command.

Turns out that the values sometimes are repeated. Therefore I was suggested to add an array (used[ ]) and a loop in order to check every time if the value has been generated already.

Random random = new Random();
    int[] used = new int[10];
    int rng = 0;
    while (!used.Contains(rng))
    {
        rng = random.Next(1, 10);
    }

    /* 
    I wish to store the generated value "rng" to the "used" array.
    e.g.
    used[0] = rng
    used[1] = rng
    used[2] = rng
    etc.
    */
    Console.WriteLine("The number generated is " + Convert.ToString(rng));

However, I don't know how to constantly add values to an array in an arranged order. (as seen by the commentations above)

In a more simple way, For 10 times I want the system to generate a number, and those numbers are randomly picked out of [1,10] and only once. If all the numbers have been generated once, all are free to be generated again.

Sorry for my bad interpretation. I have refined it with the comments that everyone has contributed.

Tsain
  • 67
  • 6
  • 2
    So if i got it right you want to have the program run until every possible number in the range you decided have been picked while discarding eventual double values? – sMuLe Mar 16 '22 at 11:10
  • yes that's what I meant. – Tsain Mar 16 '22 at 11:11
  • 1
    `Turns out that the values sometimes are repeated. ` - that's the nature of randomness. do you instead want to take the numbers 1-10 in a [random _order_](https://stackoverflow.com/questions/108819/best-way-to-randomize-an-array-with-net)? – Franz Gleichmann Mar 16 '22 at 11:11
  • yes I suppose, I am not sure if I fully understand what you meant though. – Tsain Mar 16 '22 at 11:13
  • 3
    Rather than putting values into an array as you use them, consider generating the full array first and then *shuffling it*. Then just take items from the array until it's empty. That's logically the same thing. – Damien_The_Unbeliever Mar 16 '22 at 11:16
  • I will give that a try I guess. – Tsain Mar 16 '22 at 11:19
  • I don't get it. At what point is it ok to "repeat" a number again? You cannot possibly "run forever" and only have 10 numbers. So do I get this right: For 10 times you pick a number, you want them to be randomly picked out of [1,10] and only once. If all the numbers have been picked once, all are free to be picked again. Is that correct? – Fildor Mar 16 '22 at 11:21
  • 1
    Thats exactly what I meant! I really have to improve my English. – Tsain Mar 16 '22 at 11:23

4 Answers4

3

Here is a solution, that shuffles the values and returns each value until the list is exhausted, then starts all over:

int[] GenerateNewArray(int n)
{
    // Define an array of values we wish to return and populate it
    int[] baseValues = Enumerable.Range(1, n).ToArray();
    Random rnd=new Random();
    // Shuffle the array randomly using Linq
    return baseValues.OrderBy(x => rnd.Next()).ToArray(); 
}

void Main()
{
    int nNumbers = 10;
    while (true)
    {
        // Generate a new randomized array
        var values = GenerateNewArray(nNumbers);
        // Print each value 
        for (int i=0;i<nNumbers;i++)
        {
            Console.WriteLine($"The number generated is {values[i]}");
        }
    }
}

EDIT Parameterized the number of unique values.

Palle Due
  • 5,929
  • 4
  • 17
  • 32
  • I have never thought of it like that, but if I were to randomize a value between 1 and a larger number (for example 9999), there is no way that I have to insert the values one by one into the initial array, is there another way to do that as well? – Tsain Mar 16 '22 at 14:03
  • See my edit. I kept the 10, but you can just change nNumbers to any reasonable number. For very large numbers you would probably initialize the values outside the loop and shuffle a copy. – Palle Due Mar 16 '22 at 14:23
  • Actually it's already shuffling a copy, so just move initializing out of GenerateNewArray. – Palle Due Mar 16 '22 at 15:01
  • Thanks! I am able to understand most of the code lines in just a short while. – Tsain Mar 16 '22 at 15:59
1

Rather than storing in an array, looking for duplicates etc., just use a HashSet This only accepts unique values, and if you try to add a duplicate it will be simply ignored.

Or you can just do a check to see if it exists

HashSet<int> integerSet = new HashSet<int>();

if (hashSet.Contains(rng ))
  // element already exists in set
else 
   //Doesn't exist

Of course, if you just keep generating each number randomly until you have generated everything in the range, then you might as well just simply generate an array/list of elements with every number and save yourself processing time!

jason.kaisersmith
  • 8,712
  • 3
  • 29
  • 51
  • 1
    Yes perhaps I can use methods other than arrays to produce what I want, but I would have to fully understand how the "HashSet" thing works, as I might use that in other programs pretty soon as well. Thanks for your suggestion! – Tsain Mar 16 '22 at 11:29
1

Using a list is a good way to start, but before continuing the comment go check out the list documentation

In small talk a list is an IEnumerable of a type you choose, in your case to declare and instanciate a list you should write like this List<int> listName = new List<int>(); and the main difference between an array and a list is that you can populate the list whenever you want without the length declaration, to do that you can add listName.Add(2); (2 : the integer value that you want to add) or remove values listName.Remove(0) (0 : the position of the list that need to be removed, ex: 0 is the first value inside the list because position 0 is the start)

Random random = new Random();
List<int> listName = new List<int>();
int rng = 0, counterOfValues = 0;

while (counterOfValues < 10)
{
    rng = random.Next(1, 10);

    If(!listName.Contains(rng))
    {
        listName.Add(rng);
        Console.WriteLine("The number generated is " + listName.Last().ToString());
        
        If(counterOfValues < 10)
        {
            counterOfValues++;
        }
        else
        {
            counterOfValues = 0;
        }
    }
}

This should probably do the work ("should" because it's some code I wrote out of my head, so sorry about caps ecc) Let me know if it works as intended or we need to correct something!

P.S: If you want the user to select the range of the randomic values just change the value of counterOfValues in the lowest value and the 10's with the higher value

sMuLe
  • 123
  • 8
  • I have just got into coding not long ago so from my perspective, there are still parts where I can't understand. However, you have gave a very detailed explanation of how to make use of the `list`. Thanks! I will definitely try yours too. – Tsain Mar 16 '22 at 14:22
  • And remember, after you found your perfect method to mark as accepted the aswer that made the magic, that's a good habit to hold onto to help the others involved! – sMuLe Mar 16 '22 at 15:38
  • Oh yes of course :) I think each of these solutions works tho. Guess I will just pick the one that I think is the most comprehensive and versatile. – Tsain Mar 16 '22 at 15:58
  • Yeah you're right, if each one works you should definitely mark the most versatile one! – sMuLe Mar 16 '22 at 16:27
1

Using a HashSet is better to know the numbers that you are use. But in your case, I think it's better use a List and remove elements:

var list = new List<int>();
for (int i = 1; i <= 10; i++)
   list.Add(i);

// List is 0-index
// list = 1 2 3 4 5 6 7 8 9 10
int rng = random.Next(0, list.Count - 1);
// Suppose rgn=3: You get 3 value and remove it from the list
var value = list[rgn];
list.RemoveAt(rgn);

// list = 1 2 4 5 6 7 8 9 10
// Now you get a value between 0...8 (list.Count is 9)
rng = random.Next(0, list.Count - 1);
// Suppose rgn=3: You get 4 value and remove it from the list
var value = list[rgn];
list.RemoveAt(rgn);

// list = 1 2 5 6 7 8 9 10
// Now you get a value between 0...7 (list.Count is 8)
rng = random.Next(0, list.Count - 1);
// Suppose rgn=5: You get 7 value and remove it from the list
var value = list[rgn];
list.RemoveAt(rgn);

// list = 1 2 5 6 8 9 10
...

When list has one element you don't need use random. When your list is empty, fill again and repeat the whole process.

In this form you always get an element with each random call. Using HashSet you may try a lot of random calls to get a nonused number.

A class for that maybe:

public class RandomClass
{
   private readonly List<int> _list;

   private readonly Random _random;

   public RandomClass()
   {
      this._list = new List<int>();
      this._random = new Random();
   }

   private void PopulateList()
   {      
      for (int i = 1; i <= 10; i++)
         this._list.Add(i);
   }

   public int GetNumber()
   {
      if (this._list.Count == 0)
      {
         this.PopulateList();
      }

      if (this._list.Count > 1)
      {
         int rng = this._random.Next(0, this._list.Count);
         var value = this._list[rgn];
         this._list.RemoveAt(rgn);
         return value;
      }
      else
      {
         var value = this._list[0];
         this._list.RemoveAt(0);
         return value;
      }
   }
}

UPDATE: Some about List, Arrays and other collections

Array and List are very similar. The main difference is that an array is a continuous block of memory while List (in memory) it's not a block. Array is faster to iterate (address of first element + multiply for sizeof each element) but slower when you want to add/remove elements (you must create other array and copy the elements). From the point of view of an user, both of them are used to store a list of items and iterate them.

Other collections very interesting are HashSet and Dictionary. These collections are sets, you can't access their elements using an index. You saw an example for HashSet in this page: Add a number to the set and query it if you want know if that number appear before. The key with this sets are that you access to their elements using a hash. Instead of an index, you use whatever you want as a key and an algorithm get quickly the position of the element in the set. Dictionary is like a HashSet but you can store addicional data asociated to the key.

In many cases, you'll use both of them: a list to iterate sequencially and a dictionary to direct access to elements. For example, you have a lot of Persons that you show in some order (name, surname...). You store them in a List an allow user to change the order (using Sort of the list). You use a list because you show and use that order many times: you need ordered Persons.

But you have many, many Persons. Suppose that your program allow search Persons by Id and Name. Each time a user search for Persons you must iterate a very long list. It's not efficient. So you define two Dictionary:

Dictionary<int, List<Person>> personsById;
Dictionary<string, List<Person>> personsByName;

Also, you have a List:

List<Person> allPersons;

And we need some operations:

public void AddPerson(Person person)
{
    Person existingPerson;
    if (personsById.TryGetValue(person.Id, out existingPerson))
    {
        // Already exists: don't add duplicated persons
        return;
    }

    personsById.Add(person.Id, person);

    // Maybe more than one person with the same name. It's the reason why use a List here
    List<Person> list;
    if (personsByName.TryGetValue(person.Name, out list))
    {
        // There are other persons with this name, so the list exists. Simply add person to list
        list.Add(person);
    }
    else
    {
        // Is the first person with this name: create the list and associate to the name
        personsByName.Add(person.Name, new List<Person> { person });
    }

    // Always add to main list
    allPersons.Add(person);
}

public void RemovePerson(Person person)
{
    if (!personsById.TryGetValue(person.Id, out _))
    {
        // Not exists: do nothing
        return;
    }

    personsById.Remove(person.Id);

    // NOTE: Here we are removing person assuming that you never have different instances 
    // of person. If you can create different instances for a person, here we must iterate 
    // the lists to find the person
    List<Person> list;
    if (personsByName.TryGetValue(person.Name, out list))
    {
        list.Remove(person);
    }

    allPersons.Remove(person);
}

public Person GetPerson(int id)
{
    return this.personsById.TryGetValue(id, out Person person) ? person : null;
}

public List<Person> GetPersons(string name)
{
    return this.personsByName.TryGetValue(name, out List<Person> list) ? list : null;
}

It's a very basic example but I think maybe a good one to learn about this classes.

Victor
  • 2,313
  • 2
  • 5
  • 13
  • I am a beginner in coding, so I can't really grasp everything you have mentioned. However, you have helped me to differentiate the use of HashSet, List, and arrays! That is all I care about and that definitely helps me in the future, and of course I will also give your suggestion a shot. – Tsain Mar 16 '22 at 14:19