Resolved Most efficient way to choose random item from reducing list?

Ren6175

Member
Joined
Aug 5, 2019
Messages
22
Programming Experience
Beginner
Hello, complete Noob here with C#. Coded in Basic 25 years ago. Started C# 2 days ago for fun to create an app for personal use. Need to be able to hit a button and return a random item from a list. Then when I hit the button again return a different item excluding the first item and so on until all items are exhausted. The specific application is going to choose a monster to activate in a board game then another monster etc until the round is over then it will reset.

I can think of ways to do it like assign numbers and choose a random number. Then when I hit the button again, if it chooses the same number I can have it “reroll” until it chooses a different one. This seems inelegant and likely to slow down the program.

Is there an efficient way to do it?
 
Use the random class and generate your numbers bases on the List<T>.count of your lists items. If your list has 5 items then generate your random choices between zero and six, and if your first random is one, then exclude that number from your next random call by choosing between two and six and so on so on. Post back with what you've tried and maybe we can work on making it better. For each number you receive back from your random generator, store it in a collection like a list, and then compare your next random number to ensure its not the same number as one generated previously.

Depending on how many numbers you need, you could add a range of numbers to a list and randomly rearrange that list. You could then iterate the list and you would then have a list of numbers randomly generated. There is a range of ways you could go about it.
 
Last edited:
I was going to suggest the same as Sheepings second option. You can generate a randomised list of numbers and put them in a queue, then just dequeue one by one. I won't write the code but I'd suggest looking Enumerable.Range, Enumerable.OrderBy, Random.Next and the Queue<T> class.
 
I was going to suggest the same as Sheepings second option. You can generate a randomised list of numbers and put them in a queue, then just dequeue one by one. I won't write the code but I'd suggest looking Enumerable.Range, Enumerable.OrderBy, Random.Next and the Queue<T> class.
Surprisingly I actually understand what you are saying. That is a good idea. I will write it and let you know what happens.
 
I was able to solve my problem based on your suggestions and a bunch of reading on MSDN. It probably isn't the most elegant solution, and it isn't complete, but it is doing what I want it to do. I just created a simple Windows Form to test it, so this isn't really my final solution. I also kept getting an error each time my Queue was empty, so I just wrote it to hide the button when the queue was empty. If you have any additional suggestions, feel free to comment. Also, I would like to mark this as resolved. I'll try to figure that out. Thanks.

C#:
List<string> monster = new List<string>();
        Queue<string> mq = new Queue<string>();
        Random rnd = new Random();               
        public Form1()
        {
          
            InitializeComponent();
            
            monster.Add("goblin");
            monster.Add("dragon");
            monster.Add("spider");
                      
            for (int i = 0; i < monster.Count;)
            {
            int nextmonster = rnd.Next(monster.Count);
                mq.Enqueue((string)monster[nextmonster]);
                monster.RemoveAt(nextmonster);
            }
            int qcount = mq.Count;
            if (qcount == monster.Count)
            {
                textBox1.Text = " ";
            }
         
        }

        private void Button1_Click(object sender, EventArgs e)
        {
                      
            textBox1.Text = Convert.ToString(mq.Peek());
            mq.Dequeue();
            textBox2.Text = Convert.ToString(mq.Count());
            if (mq.Count == 0)
            {
                button1.Hide();
            }

        }
 
Here's how I would create a random queue from a list:
C#:
var rng = new Random();
var monsters = new List<Monster>();

// ...

var monsterQueue = new Queue<Monster>(monsters.OrderBy(m => rng.NextDouble()));
The Queue<T> constructor accepts an IEnumerable<T>, so there's no need to enqueue items separately. If you know that you will only need a certain number then you can append a Take call:
C#:
var monsterQueue = new Queue<Monster>(monsters.OrderBy(m => rng.NextDouble()).Take(requiredMonsterCount));
 
Okay, I understand everything you've suggested, but what if I want this queue to happen on Form2? To explain, I've used all your code (from the other thread) to generate 2-5 random monsters. My program then opens a new Form and I need to cycle through those 2-5 random monsters each time a push a button. I have a button labeled "Activate a random monster" and another button that says, "Reset Monsters." So each round each monster will be "activated" in a random order. Anyway, I get an error when I try to add the monsters and I'm not sure how to fix it. Should I completely rebuild the monster class in Form2? (Sorry if this makes no sense. You can just tell me to forget it.)

Edit: or can I just run this code on form1 every time I push the button form2 and have the results display on form2?
C#:
private void Button11_Click(object sender, EventArgs e)
        {
            var rng = new Random();
            var monsters = new List<Form1.Monster>();
            monsters.Add([U]goblinarcher[/U]);
            monsters.Add([U]ettins[/U]);

            var monsterQueue = new Queue<Form1.Monster>(monsters.OrderBy(m => rng.NextDouble()).Take(monsters.Count));
 
Last edited:
Firstly, please don't cut off the leading whitespace on the first line of a code snippet and leave it on every other line. A lot of people seem to copy code by dragging from the first character to the last and just ignore that that makes the leading whitespace inconsistent. There are three better ways to select code to avoid this issue:

  1. Drag from the end of the code to the start.
  2. Select code as complete lines by dragging in the left margin.
  3. Depress the Alt key while dragging.
Option 2 will retain the leading whitespace on every line, including the first. Option 1 allows you to do the same by dragging past the first character.

It's preferable to have no leading whitespace beyond indenting but, if you are going to include it, it's better that it is consistent. That said, the code editor on this site will remove it all for you anyway, if you simply select all the code and hit Shift+Tab.

Option 3 is generally the best. If place the cursor on the first character of the block and depress the mouse button, then depress the Alt key, you can drag the mouse to select a rectangular block that will exclude whitespace from every line rather than just the first. For somewhere like Stack Overflow, where you need to have four leading spaces on each line, you can explicitly select that leading whitespace and no more this way.
 
As for your question, you shouldn't be declaring the Monster class inside the Form1 class to begin with. One type shouldn't really be declared inside another unless it is to be used only within that type. A Monster class should be declared as the sole type in a file named Monster.cs.

If Form2 needs data from Form1 then Form1 can just pass it over when it creates Form2, either by setting a property or as a constructor argument. If Form2 only needs the randomised queue then that's what should be passed over. If Form2 needs the whole list so that it can create its own randomised queues then that's what should be passed over.
 
First of all I was posting on my phone so it really got screwed up.

Secondly, how much money do you think you should have earned for all this coding you did for me?
 
Back
Top Bottom