0

im making a quiz and i want the radiobuttons to be in different positions. ive got it working to a extent as it would be in random order in the first question however it would then stay in that order for the rest of the quiz. I want it to randomize every time.

class MultipleChoice(QtGui.QWidget):
showTopicsSignal = pyqtSignal()

def __init__(self,parent=None):
    super(MultipleChoice, self).__init__(parent)

    self.initUI()


def initUI(self):
    self.Questions=[]
    self.Questionum = QLabel()
    self.Questioninfo = QLabel()
    self.Correctanswer = QRadioButton()
    self.Incorrectans1 = QRadioButton()
    self.Incorrectans2 = QRadioButton()
    self.Incorrectans3 = QRadioButton()
    self.Correctanswer.setAutoExclusive(True)
    self.Incorrectans1.setAutoExclusive(True)
    self.Incorrectans2.setAutoExclusive(True)
    self.Incorrectans3.setAutoExclusive(True)
    layout = QVBoxLayout(self)
    layout.addWidget(self.Questionum)
    layout.addWidget(self.Questioninfo)
    randomnumber = randint(0,3)
    if randomnumber == 0:
        layout.addWidget(self.Correctanswer)
        layout.addWidget(self.Incorrectans1)
        layout.addWidget(self.Incorrectans2)
        layout.addWidget(self.Incorrectans3)
    elif randomnumber == 1:
        layout.addWidget(self.Incorrectans1)
        layout.addWidget(self.Correctanswer)
        layout.addWidget(self.Incorrectans2)
        layout.addWidget(self.Incorrectans3)
    elif randomnumber == 2:
        layout.addWidget(self.Incorrectans1)         
        layout.addWidget(self.Incorrectans2)
        layout.addWidget(self.Correctanswer)
        layout.addWidget(self.Incorrectans3)
    elif randomnumber == 3:
        layout.addWidget(self.Incorrectans1)         
        layout.addWidget(self.Incorrectans2)
        layout.addWidget(self.Incorrectans3)
        layout.addWidget(self.Correctanswer) 
Spike7
  • 128
  • 1
  • 4
  • 15

2 Answers2

3

If you load your answer choices into a container, you can use Python's random (link) library with one or both of the following tools:

  • random.choice(seq) (link) which makes a random choice of the possible items provided in seq
  • random.sample(pop, k) (link) which choice k unique elements from the possible choices provided in pop, without replacing, which I think is key in your situation because you don't want to have the same answer displayed twice on the same question.

This would simplify your current if-statement block, from

if randomnumber == 0:
    layout.addWidget(self.Correctanswer)
    layout.addWidget(self.Incorrectans1)
    layout.addWidget(self.Incorrectans2)
    layout.addWidget(self.Incorrectans3)

to

choices = [self.Correctanswer, self.Incorrectans1, self.Incorrectans2, self.Incorrectans3]
for q in range(4):
    layout.addWidget(random.sample(choices, 1))

Also just thought of another cool idea. If you have a large "pool" of possible incorrect answers, you can define a function specifically to provide a random selection of wrong answers. Something like:

def provideWrongAnswers(self, number):
    return random.sample(self.allWrongAnswersPool, number)

which would provide you with number incorrect answers to add to the layout along with the correct.

In [7]: choices = range(1,11)
In [9]: random.sample(choices, 3)  # returns a different order of choices each time
Out[9]: [1, 7, 3]
In [10]: random.sample(choices, 3)
Out[10]: [6, 9, 3]
In [11]: random.sample(choices, 3)
Out[11]: [5, 4, 2]
In [12]: random.sample(choices, 3)
Out[12]: [3, 6, 1]
In [13]: random.sample(choices, 3)
Out[13]: [10, 8, 3]
In [14]: random.sample(choices, 3)
Out[14]: [1, 7, 2]
In [15]: random.sample(choices, 3)
Out[15]: [9, 7, 3]

Finally, random.shuffle(x) (link) be another method, as it takes the sequence x and mixes them up in place. So you could combine the random.sample(wrongChoices, 3) idea which gives you three random incorrect answers, then add in the correct answer and to make sure the correct answer isn't always at the bottom, give em a final stir with random.shuffle(allAnswerChoices)

TCAllen07
  • 1,294
  • 16
  • 27
  • FYI, while the documentation for `random.sample()` says it does it without replacement, that is only true for a single call (the results for a single call will never return more than one instance of an element in the initial population). However, it does not modify the input list, meaning the repeat calls to `random.sample` in a loop will likely result in the same element be drawn more than once. You want instead: `for w in random.sample(choices,4): layout.addWidget(w)` – three_pineapples Mar 29 '15 at 00:01
  • @TCAllen07 thanks for your answer, i tried the random sample method and i got this error "error layout.addWidget(random.sample(choices, 1)) AttributeError: 'builtin_function_or_method' object has no attribute 'sample'" – Spike7 Mar 29 '15 at 12:21
  • That error usually means you *assigned* a function as opposed to *calling* the function, such as `print(mystring.lower())` yields `"hello world"` while `print(mystring.lower)` (note the lack of `()` after `lower`) yields ``. I think you might have **imported** the module wrong, specificaly imported `random.random` instead of just `random`: `import random; random.sample(range(10))` works, while `import random.random; random.sample(range(10))` throws that `AttributeError`. The first `random` is the module, the second `random` is a built-in. – TCAllen07 Mar 29 '15 at 13:36
  • 1
    Oh, and you'll need to use a `for` loop. `random.sample()` returns a list, but `addWidget()` only accepts one widget at a time. so use `c = random.sample(choices); addWidget(c)` or alternatively just reference the first element of the returned list by adding a `[0]` like so: `addWidget(random.sample(choices)[0])` – TCAllen07 Mar 29 '15 at 13:42
  • Hey @Spike7, dif this work for you? Or can I further clarify anything? Let me know! – TCAllen07 Apr 02 '15 at 14:21
  • Yeah it worked, just was confused on what you meant on the imports for a while. but my teacher understood – Spike7 Apr 02 '15 at 21:18
0

The current structure of your seems over-complicated. It would be much simpler to randomize the question choices, rather than the widgets themselves.

Here's a simple demo that shows how that could be done:

import sys, random
from PyQt4 import QtGui

class MultipleChoice(QtGui.QWidget):
    def __init__(self, parent=None):
        super(MultipleChoice, self).__init__(parent)
        self.Questions = [
            ['What is the capital of Bhutan?',
             'Thimphu', 'Bujumbura', 'Suva', 'Kigali'],
            ['What is the capital of Yemen?',
             'Sana\'a', 'Malabo', 'Riyadh', 'Muscat'],
            ['What is the capital of Togo?',
             'Lomé', 'Dakar', 'Abuja', 'Nouakchott'],
            ]
        self.Questionum = QtGui.QLabel(self)
        self.Questioninfo = QtGui.QLabel(self)
        layout = QtGui.QVBoxLayout(self)
        layout.addWidget(self.Questionum)
        layout.addWidget(self.Questioninfo)
        self.buttonGroup = QtGui.QButtonGroup(self)
        self.buttonGroup.buttonClicked.connect(self.handleChoice)
        for index in range(4):
            button = QtGui.QRadioButton(self)
            self.buttonGroup.addButton(button, index)
            layout.addWidget(button)
        self.buttonNext = QtGui.QPushButton('Next', self)
        self.buttonNext.clicked.connect(self.handleNext)
        layout.addWidget(self.buttonNext)
        self.index = -1
        self.handleNext()

    def handleNext(self):
        self.index += 1
        if self.index >= len(self.Questions):
            self.index = 0
        question = self.Questions[self.index]
        self.Questionum.setText('Question %d:' % (self.index + 1))
        self.Questioninfo.setText(question[0])
        choices = question[1:]
        random.shuffle(choices)
        self.buttonGroup.setExclusive(False)
        for index, choice in enumerate(choices):
            button = self.buttonGroup.button(index)
            button.setChecked(False)
            button.setText(choice)
        self.buttonGroup.setExclusive(True)

    def handleChoice(self, button):
        answer = self.Questions[self.index][1]
        if button.text() == answer:
            print('Right!')
        else:
            print('Wrong...')

if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)
    window = MultipleChoice()
    window.show()
    sys.exit(app.exec_())
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • thanks for your answer, but im getting my answers from a table in my database and setting the text each time the next button is pressed. Self.Questions is connected to a mysql query. Could i still incorporate your answer? – Spike7 Mar 29 '15 at 12:23
  • @Spike7. Yes, that's exactly how my answer works. I've used a list of lists to hold the questions/answers, which looks very similar to what you show in your example code. It should be very easy to adapt it. – ekhumoro Mar 29 '15 at 16:34