4
\$\begingroup\$

Description:

I am building a quiz based learning system in Python. The idea is once a day you have to give this test to improve your learning, it's different from regular rote learning. The user will be shown question first and optionally series of hints, the user then can match his answer to the original answer and honestly gives score to his attempt. I didn't go for programmatically checking the answers because it would be difficult to check semantics of two strings.

Goal:

The end goal is to provide a flexible way to add new questions and hints and add new question types. I am not favour of very fancy UI but something which feels intuitive should work.

In the long run, I will be integrating it with Slack as a bot.

Code:

from enum import Enum

class Category(Enum):
  CS, MATH, PUZZLE = range(3)

class Score(Enum):
  BAD, OK, GOOD = range(3)

data = [{
  'question': 'What does the fox say?',
  'hints': ['not the sound of cat', 'not the sound of dog'],
  'answer': 'Its a mystery',
  'category': Category.PUZZLE,
  'score': Score.BAD
}, {
  'question': 'What is the worst case run time of binary search of n elements?',
  'hints': [
    'Think about the best case',
    'Would need to search exhaustively until one element'],
  'answer': 'Log(n) base 2',
  'category': Category.CS,
  'score': Score.BAD
}];

def runQuiz(questions, category):
  total = 0
  needHint = '/h'
  for q in filter(lambda q: q['category'] == category, questions):
    answer = input(q['question'])
    if answer == needHint:
      for hint in q['hints']:
        moreHints = input(hint)
        if moreHints != needHint:
          break
    print('Correct answer is: {0}'.format(q['answer']))
    rating = int(input('Rate your solution(1-3): '))
    q.update({'score': rating})
    total += rating
  print('You are done for today, your score is {0}'.format(total))
  return input('Want to start again?(y/*)')

def selectCategory():
  category = input("""
    Select category
      1. Computer Science
      2. Mathematics
      3. Puzzles
      4. Random (Default)
  """)
  selectedCategory = {
    1: Category.CS,
    2: Category.MATH,
    3: Category.PUZZLE
  }.get(int(category), Category.CS)
  return selectedCategory

def main():
  print('Make sure you are not feeling thirsty')
  input()
  print('Clean your surrounding')
  input()
  print('Remember your goal')
  input()
  print('If you need any hint type /h')
  input()
  print('Lets go')
  selectedCategory = selectCategory()
  print('You have selected {0} category'.format(selectedCategory.name))
  print('')
  shouldStartAgain = runQuiz(data, selectedCategory)
  if shouldStartAgain == 'y':
    main()
  else:
    print('You are done for today, sleep well!')

if __name__ == '__main__':
  main()

Note:

Although the current solution doesn't meet all the requirements but it is complete in itself as its fully functional code. Its in early stage of development so I would like to take proper design decisions as soon as possible.

TODO:

Find a way to persist the questions with updated score. Make the questions with lower score come first.

Questions:

  1. Is there any better way to deal with terminal?
  2. Keeping in mind that I would like to use this system as web service too, what design decisions would be helpful?
  3. It may be too broad, but what good programming practices should I follow, for example when and where to choose objects, enums etc?
\$\endgroup\$
9
\$\begingroup\$

Expanding a bit on what the first answer mentioned: There are no Quiz, Question etc. classes.

Your current code has this data:

data = [{
  'question': 'What does the fox say?',
  'hints': ['not the sound of cat', 'not the sound of dog'],
  'answer': 'Its a mystery',
  'category': Category.PUZZLE,
  'score': Score.BAD
}, {
  'question': 'What is the worst case run time of binary search of n elements?',
  'hints': [
    'Think about the best case',
    'Would need to search exhaustively until one element'],
  'answer': 'Log(n) base 2',
  'category': Category.CS,
  'score': Score.BAD
}];

So it is a list of objects of this:

Question - String
Hints - array of String
Answer - String
Category - enum value
Score - enum value

This looks like the start of a Question class. But wait, how/why does a Question have a Score? What does it mean that the score for a question is "Bad"? That depends on how you answer that question, doesn't it? I would remove that from this data, that value belongs elsewhere. You could possibly have some "points" integer value in the data to indicate how many points a question is worth though.

You say that you are not looking to make your program give the points, but the user to honestly give the points. This is good, and reminds me about Anki Flashcards (although Anki doesn't have hints AFAIK).

TODO: Find a way to persist the questions with updated score. Make the questions with lower score come first.

Ah, this is where the score comes in again. It's important to distinguish between a Question and the user's answer to a question. Maybe your system should support multiple users that should be able to answer different things? Then you need to connect each question that a user has answered to a score.

So, besides the Question class that I've already mentioned, you could have some more things:

  • UserAnswer (contains reference to Question and to a Score), a list of UserAnswers is basically a user's progress, so you could load/save multiple of these for each "Lesson" a user has ongoing/completed.
  • Quiz (contains a list of question, might not be necessary though as you have a category so you could filter the list of all questions based on a category)
  • Category can be a string instead of an enum, as I don't see a reason to programmatically hard-code a limit of possible categories

Also, I would strongly recommend to keep your data as a file or in a database and not inside your code.

\$\endgroup\$
  • \$\begingroup\$ Very insightful answer, yes you are right I want to make something like Anki but at first I didn't think about multiple users, but yes it makes sense if I will expose it to some bot. \$\endgroup\$ – CodeYogi Oct 30 '17 at 19:51
8
\$\begingroup\$

Some ideas:

  • This isn't object oriented code - the only classes are enums. There are no Quiz, Question etc. classes. You might want to remove the "object-oriented" tag or ask something specific about object orientation with respect to this code.
  • You only use Score.BAD.
  • You should use a loop rather than recursively calling main. Otherwise your most dedicated customers are going to run out of stack :)
  • Your categories are numbered 0 through 2, but you ask for a number 1-3 to index them. So that's a bug, since the user won't know which numbers are valid or why they get the wrong category for their input.
  • The "random" category isn't.
  • data is the worst variable name. It seems like each of the entries there is a question with associated metadata. (They're even hashes, and as such easily translatable to objects.) Why not name it questions?
  • Run the code through pycodestyle to make it more idiomatic. For example, 4 space indentation is a PEP8 recommendation.
  • To make the code easily usable in a web framework you should move all logic out of main, and leave it a simple function call. You'll also want a more generic "get an answer from a user" method which can be extended to work with HTTP and a "show current state" method which can output to a shell (that is, one line at a time) or a browser (that is, a whole web page or a change set).
\$\endgroup\$

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.