4

I'm making a quiz app. The app uses a .json file as the 'database' of questions and answers. This .json file looks as follows...

      {
        "id" : "1",
        "question": "Earth is a:",
             "answers": [
            "Planet",
            "Meteor",
            "Star",
            "Asteroid"
          ],
          "difficulty": "1"
      }

...and just keeps going for over 500 questions.

I display the questions randomly by using the following code:

    let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
    LoadQuestion(randomNumber)

This work fine, but because it selects the questions randomly I'm finding that questions are repeating too regularly (even though I have over 500 questions!).

I've researched this extensively and think perhaps I've read too much as I'm now quite confused. I've read about 'seeding', about saving an index of questions asked, and trying to use NSUserDefault.

In short, how can I modify my code to achieve one of the following outcomes instead:

  1. To not repeat any questions at all, but to stop asking questions when all questions have been asked once;
  2. Similar to 1 above, but have the number of questions being asked set by the user rather than asking all questions in the database;
  3. To not repeat any questions at all until all questions have been asked first; or,
  4. To not repeat any questions that have previously been answered correctly, but those that were answered incorrectly may be repeated.

Below is what I think are the relevant bits of code:

    LoadAllQuestionsAndAnswers()

    let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
    LoadQuestion(randomNumber)


func LoadAllQuestionsAndAnswers()
{
    let path = NSBundle.mainBundle().pathForResource("content", ofType: "json")
    let jsonData : NSData = NSData(contentsOfFile: path!)!
    allEntries = (try! NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers)) as! NSArray
    //println(allEntries)

}


func LoadQuestion(index : Int)
{
    let entry : NSDictionary = allEntries.objectAtIndex(index) as! NSDictionary
    let question : NSString = entry.objectForKey("question") as! NSString
    let arr : NSMutableArray = entry.objectForKey("answers") as! NSMutableArray

    //println(question)
    //println(arr)

    labelQuestion.text = question as String

    let indices : [Int] = [0,1,2,3]
    //let newSequence = shuffle(indices)
    let newSequence = indices.shuffle()
    var i : Int = 0
    for(i = 0; i < newSequence.count; i++)
    {
        let index = newSequence[i]
        if(index == 0)
        {
            // we need to store the correct answer index
            currentCorrectAnswerIndex =  i

        }

        let answer = arr.objectAtIndex(index) as! NSString
        switch(i)
        {
        case 0:
            buttonA.setTitle(answer as String, forState: UIControlState.Normal)
            break;

        case 1:
            buttonB.setTitle(answer as String, forState: UIControlState.Normal)
            break;

        case 2:
            buttonC.setTitle(answer as String, forState: UIControlState.Normal)
            break;

        case 3:
            buttonD.setTitle(answer as String, forState: UIControlState.Normal)
            break;

        default:
            break;
        }



    }
    buttonNext.hidden = true
    // we will need to reset the buttons to reenable them
    ResetAnswerButtons()

}



@IBAction func PressedButtonNext(sender: UIButton) {
    print("button Next pressed")
    let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
    LoadQuestion(randomNumber)

}
jscs
  • 63,694
  • 13
  • 151
  • 195
Monomeeth
  • 753
  • 3
  • 13
  • 29

1 Answers1

3

If I understand your requirements correctly:

  • You want to persist the randomly generated numbers between View Controller instances and application launches
  • you never want to repeat any question

A quick way to achieve this is to use NSUserDefaults to keep track of the order and value of (pseudo)random numbers from your PRNG. This way, you can instantiate the array of avaiable questions on instantaition by replaying previous dice rolls, removing those questions from the pool alongside in the right order.

This also lets you handle the PRNG as a black box, not caring about seeding and replay.

Also, make sure to remove any chosen questions from the current pool for any dice roll.

Side note: You are not following naming conventions - functions are to be starting lower case, for example.

For your own good and readability, please follow prevalent conventions, at least when sharing your code with others. Better yet, all the time.

Edit:

To be more verbose:

  • NSUserDefaults can save small amounts of data for you in an easy way. If you 'just want to remember an array of numbers', it's the go to place.
  • suppose your data is helt in an instance variable like so:

    var questions : [Question] = // load the questions - carefully, see below.
    

    you can easily remove the question your PSNG (pseudo-random number generator) selects like so:

    let randomNumber = Int(arc4random_uniform(UInt32(questions.count)))
    questions.removeAtIndex(randomNumber)
    

    Thus the next dice roll (e.g. invocation of your PSNG) is guaranteed not to select the question already asked because it is no longer in the pool of avaiable questions it selects from.

  • This approach does need you to save all your random numbers in the right order to 'replay' what has happened before when you are instantiating a new instance (for example, because the app has been reopened). That's where NSUserDefaults' setObject:forKey: and objectForKey: come into play when loading the questions initially and 'remembering' every new dice roll.

I hope, this covers what you might didn't understand before.

Tobi Nary
  • 4,566
  • 4
  • 30
  • 50
  • Thanks Jan for your advice. This is all new to me, so most of what you said was like reading a foreign language, but I will try to research NSUserDefaults again. If there's anything you can refer me to, or even some code examples, that'd be great. And thanks for your advice on following naming conventions - I didn't even know that functions should start with lowercase, so I'll correct all my code moving forward. – Monomeeth Jan 25 '16 at 12:40
  • OP, I did try to be even more verbose w/o writing the code for you so you can understand what's going on rather than cp'ing code from SO;) – Tobi Nary Jan 25 '16 at 13:09
  • That's great. I'm researching NSUserDedaults now to try and get my head around it. One thing though, could you clarify why I need to save all the random numbers in the right order? I'm trying to get my head around why the order matters, and also why it wouldn't automatically sve in the right order anyway? – Monomeeth Jan 27 '16 at 00:08
  • Ok, I never did get this to quite work - obviously beyond my ability at the moment. However, I did come to realise that perhaps I was looking at this all wrong. Instead, wouldn't I achieve the same result by just shuffling the questions and then asking them one by one? Just have to try and make sense of how I can adapt the info at http://stackoverflow.com/questions/24026510/how-do-i-shuffle-an-array-in-swift to my scenario. – Monomeeth Jan 31 '16 at 11:06
  • Yes. Standard example of the xy problem:/ – Tobi Nary Jan 31 '16 at 11:35