Click here to Skip to main content
15,896,201 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
basically for loop from 1 to 20 i need to give the user different random questions by ID from 1-20, now here is the problem, basically on start i create a "AskedQuestions "dictionary variable <int,bool> and im making a loop that will set all the values from key 1-20 to false,

the function that gets random number is here.

C#
public int GetRandomNumber()
        {
            Random rnd = new Random();
            int returnpoint = _rnd.Next(1, _questionLength+1);
            if (AskedQuestions[returnpoint] == true) GetRandomNumber();
            AskedQuestions[returnpoint] = true;
            return returnpoint;
        }
public void AskQuestion(int QuestionID)
        {
            //read the question from databaza with the questionID
        }


right here im using the commands like this... AskQuestion(GetRandomNumber());
but i dont understand why some questions are shown multiple times? by the code i made, it shouldn't do that problem... what do you think .. ?!

i hope you understand my problem here, if not .. ask me i will reply for additional info.. tnx in advance...
Posted
Updated 2-Dec-14 11:40am
v2
Comments
BillWoodruff 2-Dec-14 18:58pm    
Sound like you have a solution now; if you want to see how doing this using Linq is very easy, just ask.
PIEBALDconsult 2-Dec-14 19:10pm    
Linq is never the answer.
BillWoodruff 2-Dec-14 19:24pm    
Hi Piebald, Okay, I'll bite the (metaphysical ?) hook: what would be wrong with using Linq to make the task at hand here easy to express in human-readable code ?

Note that "answer" is your word, and my word was "how" :)

cheers, Bill
PIEBALDconsult 2-Dec-14 19:52pm    
In this particular case, he only wants one value at time, hasn't specified how many he wants, and I suppose the user can stop the process at any time.
I _assume_ that what you propose will simply provide a list of _all_ the numbers in a reasonably random order even if only a small fraction of them are actually required. Furthermore, I fail to see how Linq will work in the OP's GetRandomNumber method -- maybe that's just me. I _can_ see how it could work if a class is used to encapsulate the Linq and hide the details, but that's more boiler-plate in an effort use Linq with the stated goal of making things simpler and clearer. And breathe.
BillWoodruff 2-Dec-14 20:19pm    
To me the intent of the OP is clear: this is a questionnaire which will ask twenty-questions in a random order. I'll post some code, and let's see what you think after you taste-test.

Here's a proposal for you: "I won't assume what you will propose, if you don't assume what I will propose" :)

0) Make the Random a static member of the class.
1) Please don't use recursion to do this.
2) That's a poor algorithm.
Most of all...
3) Change
if (AskedQuestions[returnpoint] == true) GetRandomNumber();
to
if (AskedQuestions[returnpoint] == true) returnpoint= GetRandomNumber();
or
if (AskedQuestions[returnpoint] == true) return GetRandomNumber();

But, seriously, use a better algorithm.

Here, using mostly your own code, still a bad algorithm, but without recursion:

C#
private static Random rnd = new Random();
public int GetRandomNumber()
        {
            int returnpoint ;
            do
            {
              returnpoint = _rnd.Next(1, _questionLength+1);
            }
            while (AskedQuestions[returnpoint]);
            AskedQuestions[returnpoint] = true;
            return returnpoint;
        }
 
Share this answer
 
v5
Comments
SrgjanX 2-Dec-14 18:22pm    
yeah works now, i think its fine algorithm, but if you say so, i will try my best to make it better. thanks anyway :v
PIEBALDconsult 2-Dec-14 18:34pm    
The main problem with that algorithm is that the quest for an unused number becomes more time consuming as you select more values.
On the first selection, you have a 0% chance of getting a repeat.
On the second selection, you have a 10% chance of getting a repeat.
...
On the ninth selection, you have an 80% chance of getting a repeat.
If you want to select all the values you could be at it a while.

Plus recursion is just not a good idea -- if you wind up hitting a repeat many times you may blow the stack.
SrgjanX 2-Dec-14 18:49pm    
mhm, yeah, you are right about that thanks again
The reason that some questions are asked multiple times is due to the way the code handles the case where a randomly selected question has been asked before.

Consider on the first time through (when no questions have been asked before), let's say Question 5 is randomly selected. The if statement will be false and will mark Question 5 as 'has been asked' and will return the number 5. So far so good.

However, some time later, Random.Next might return 5 again. This time the if statement is true and you recursively seek another number (if you're lucky). Let's say that this call to GetRandomNumber returns, let's say, the value 8. However this value is discarded and the original function call proceeds to return the value 5 (which is an already asked question number).

You might want to consider what happens when you run out of questions?

Perhaps a better approach is to build a list of question numbers, and then as they are used, remove them from the list, until the list is empty.

using System;
using System.Collections.Generic;

namespace TwentyQuestions
{
    public class Quiz
    {
        private const int NUM_QUESTIONS = 20;
        private List<int> UnaskedQuestions = new List<int>();
        private Random _rnd = new Random();

        public Quiz()
        {
            for (int i = 0; i < NUM_QUESTIONS; i++)
                UnaskedQuestions.Add(i+1);
        }

        public void Run()
        {
            try
            {
                while (true)                
                    AskQuestion(GetRandomNumber());                                        
            }
            catch (ApplicationException Ex)
            {
                Console.WriteLine(Ex.Message);
            }
        }

        public int GetRandomNumber()
        {
            if (UnaskedQuestions.Count == 0) 
                throw new ApplicationException("No more questions");
            int returnpoint = _rnd.Next(UnaskedQuestions.Count);
            int questionNumber = UnaskedQuestions[returnpoint];
            UnaskedQuestions.RemoveAt(returnpoint);
            return questionNumber;
        }       

        public void AskQuestion(int QuestionID)
        {
            Console.Write("Answer question " + QuestionID.ToString() + 
                " (or blank to exit)? ");
            string answer = Console.ReadLine();
            if (String.IsNullOrEmpty(answer)) 
                throw new ApplicationException("Blank answer");
        }
    }
}
 
Share this answer
 
v2
Comments
SrgjanX 2-Dec-14 18:53pm    
im already handling - if out of questions, by the way thanks for the explanation and for the code, its definitely better idea then mine with recursion. gonna edit the functio...
Using a little Linq we can simplify this, and make it "flexible:"
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

private const int NumberOfAvailableQuestions = 100;

private const int NumberOfQuestions = 20;

private Dictionary<int,bool> TestResults = new Dictionary<int,bool>(); 

private List<string> AvailableQuestions = new List<string>(NumberOfAvailableQuestions);

private List<string> currentQuestionList = new List<string>(NumberOfQuestions); 

private Random getRand = new Random(DateTime.Now.Millisecond);

// for testing
private void mockInitializeQuestions()
{
    for (int i = 0; i < NumberOfAvailableQuestions; i++)
    {
        AvailableQuestions.Add(string.Format("question #{0}", i + 1));
    } 
}

private void mockStartTest()
{
    // initialize
    if(AvailableQuestions == null) mockInitializeQuestions();

    TestResults.Clear();
    currentQuestionList.Clear();

    // copy the available questions
    string[] copiedQuestions = new string[NumberOfAvailableQuestions];
    AvailableQuestions.CopyTo(copiedQuestions);

    // random sort the copied list of all questions
    // and take the first #NumberOfQuestions of them
    currentQuestionList = copiedQuestions.OrderBy(q => getRand.Next(0, NumberOfAvailableQuestions)).ToList().GetRange(0, NumberOfQuestions);

    // start the test
    foreach (string question in currentQuestionList)
    {
        // show the question, get an answer
        // update the TestResults Dictionary

        // for testing only
        Console.WriteLine(question);
    }
}
Good questions to ask:

1. why do you copy all the available questions ? because I have made the assumption/guess that the "original" list of all questions should be kept in order-created for good reason.

2. isn't it wasteful to copy all those strings ? no: 'CopyTo performs a "shallow copy."

3. if you claim that 'CopyTo performs a reference-only copy (shallow) then doesn't sorting the copy sort the original list ? no.

I would guess that in the future what are now the 'strings will be replaced by instances of some Class that will contain other data needed to present a question with things like "multiple choice" answers: when that's the case then the cost any copying will be minimal since it's just references copied.
 
Share this answer
 
v3
Comments
PIEBALDconsult 2-Dec-14 21:27pm    
Your generic types got endtagified. Ah, better.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900