5
\$\begingroup\$

I've written this script to practice (not necessarily memorize, but familiarize myself with) the definitions of a lot of words.

import csv
import random

WORDS = 5    # Number of words
CHOICES = ["A", "B", "C", "D", "E"]    # Number and labelling of choices

with open("wordlist.csv") as csvfile:
    wordreader = csv.reader(csvfile)
    wordlist = [row for row in wordreader]
    words = [pair[0] for pair in wordlist]
    definitions = [pair[1] for pair in wordlist]

score = 0

count = 1
while count <= WORDS:
    print("\nQuestion", str(count))
    randomnumber = random.randint(0, len(words)-1)
    print("Word:", words[randomnumber])

    correct_answer = definitions[randomnumber]
    answers = []
    answers.append(correct_answer)
    for i in range(len(CHOICES)-1):
        randomanswer = random.randint(0, len(words)-1)
        if definitions[randomanswer] in answers:
            randomanswer -= 1
        answers.append(definitions[randomanswer])
    answers.sort()
    correct_index = answers.index(correct_answer)

    for i in range(len(CHOICES)):
        print(CHOICES[i] + ": " + answers[i])

    userinput = input("Answer: ")
    if userinput[0].upper() in CHOICES:
        userindex = CHOICES.index(userinput)
        if userindex == correct_index:
            score += 1
            print("Correct")
    else:
        print("Wrong: " + correct_answer)

    count += 1

print(str(score) + "/" + str(WORDS))

Sample output:

Python 3.4.5 (default, Jul 03 2016, 13:55:08) [GCC] on linux-027e.site, Standard
>>> 
Question 1
Word: accentuate
A: Adapt
B: Forgiveness of their sins
C: Stress
D: Superintendent of a monastery
E: Unreasonable
Answer: C
Correct

Question 2
Word: ackja
A: Sami sledge
B: Singing without instruments
C: Speech problems
D: Superintendent of a monastery
E: Unreasonable
Answer: A
Correct

Question 3
Word: a cappella
A: Adapt
B: Adapt
C: Appropriate
D: Forgiveness of their sins
E: Singing without instruments
Answer: E
Correct

Question 4
Word: adequate
A: Adapt
B: Appropriate
C: Forgiveness of their sins
D: Intangible
E: Sami sledge
Answer: B
Correct

Question 5
Word: ackja
A: Adapt
B: Forgiveness of their sins
C: Sami sledge
D: Speech problems
E: Stress
Answer: C
Correct
5/5

Note that there shouldn't be duplicate questions, it's just more likely with a short wordlist like the one at the end of this post and the current implementation. Also, the same definitions shouldn't appear twice in the same question (like in Question 3) My solution to that seems to work most of the time, it's unclear to me why it doesn't work for Question 3 above.

There are a number of things that I'm wondering about:

  • Use of random.sample to choose words and answers, it doesn't seem like this will make the code any shorter or concise.
  • I'm not quite satisfied with the code for choosing the random answers, it seems to be too complicated, as well as the code for checking the user answer. Is there an easier way to verify that the chosen letter is the one that stands for the correct definition?
  • How could I eliminate the chance of the same word being asked twice in one run of the script?

Here is a sample wordlist.csv, where I've translated 10 of the ca. 3500 original entries:

a cappella,Singing without instruments
absurd,Unreasonable
abbot,Superintendent of a monastery
accentuate,Stress
Aphasia,Speech problems
ackja,Sami sledge
abstrakt,Intangible
absolution,Forgiveness of their sins
adequate,Appropriate
accommodate,Adapt
\$\endgroup\$
2
  • \$\begingroup\$ Some words you are more familiar with, some are less - your program does not take that into account. Using spaced repetition would be more effective, you could look at Anki's source for inspiration. \$\endgroup\$ – Daerdemandt Sep 8 '16 at 15:15
  • \$\begingroup\$ @Daerdemandt Indeed, but I didn't really want to make it as complicated. It's a list of around 3.5k words after all, and it's only revision for a word test of 40 random words (some of which may not even be in my practice list) :) \$\endgroup\$ – Dartmouth Sep 8 '16 at 16:00
3
\$\begingroup\$

In python it is usually frowned upon iterating with:

l = [1, 2, 3]
for i in range(len(l)):
    print l[i]

and it is preferred to iterate over the iterable directly:

l = [1, 2, 3]
for x in l:
    print x

Similarly, it is a lot easier to use random.sample (or random.choice if you don't care if a word is asked multiple times) to choose a random word to test now.

Also, it is easier to leave the word and its definition bundled in a tuple, because most of the time you need both of them.

Your choices included sometimes multiple times the same words, because you check only once whether the random answer is already included (if it is, you choose randint - 1, which might also already be included). You should keep on polling until you find a word not included in answers yet (This assumes your dictionary to have more words than CHOICES).

Similar for your user choice. It is nicer (to the user) to keep on asking for an answer until he answers with a possible choice.

I made you enumeration of the choices to be numbers rather than letters because it makes it easier to validate the correct answer.

You should in general use python's str.format methods rather then string addition. It is usually faster and also more readable (and extendable, you can add text justification, padding, etc).

I also added random.shuffle(answers) instead of answer.sort() just so it does not get too boring.

Final code:

import csv
import random

WORDS = 5    # Number of words
CHOICES = 5    # Number of choices

with open("wordlist.csv") as csvfile:
    words = list(csv.reader(csvfile))

score = 0
count = 1

while count <= WORDS:
    print("\nQuestion {}".format(count))
    word, correct_answer = random.choice(words)
    print("Word: {}".format(word))

    answers = [correct_answer]
    while len(answers) < CHOICES:
        _, random_answer = random.choice(words)
        if random_answer not in answers:
            answers.append(random_answer)
    random.shuffle(answers)
    correct_index = answers.index(correct_answer) + 1

    for choice, answer in enumerate(answers, start=1):
        print("{}: {}".format(choice, answer))

    userinput = 0
    while not 0 < userinput <= CHOICES:
        try:
            userinput = int(input("Answer: "))
        except ValueError:
            continue
    if userinput == correct_index:
        score += 1
        print("Correct")
    else:
        print("Wrong: {}".format(correct_answer))
    count += 1

print("{}/{}".format(score, WORDS))
\$\endgroup\$
7
  • \$\begingroup\$ The problem is that csv.reader returns each line as a list, not a tuple. So the first word/definition pair (first item in words) actually looks like this: ['a cappella', 'Singing without instruments']. So unpacking raises an error, because there is only 1 item. \$\endgroup\$ – Dartmouth Sep 8 '16 at 13:52
  • 1
    \$\begingroup\$ @Dartmouth tuple unpacking works perfectly fine with any iterable of the correct length. \$\endgroup\$ – Graipher Sep 8 '16 at 13:53
  • \$\begingroup\$ ah, partially my mistake, I was looking at the wrong unpacking... The error I'm getting is on line 16: word, correct_answer = random.sample(words, 1), which gives me: Exception "unhandled ValueError". need more than 1 value to unpack. This unpacking doesn't work, because random.sample(…) returns a nested list like [['absurd', 'Unreasonable']] \$\endgroup\$ – Dartmouth Sep 8 '16 at 14:08
  • \$\begingroup\$ Putting a [0] at the end of lines 16 and 21 (where the same problem exists) solves this though. \$\endgroup\$ – Dartmouth Sep 8 '16 at 14:11
  • 1
    \$\begingroup\$ Instead of [x for x in ...] you could also write list(...) directly. \$\endgroup\$ – ferada Sep 8 '16 at 19:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.