-1

I've created a function in Python to review a series of questions in a dictionary and remove records from the dataset depending on the answers to the questionnaire. I've created the questionnaire so the user can only respond yes or no. I have a third branch to handle the case where the user responds with something other than yes or no, but I want the function to repeat the existing question. Currently, it goes back and restarts from the first question. What can I add to fix this functionality?

For reference, here is the function I created:

def questionnaire(df, questions = questions):
    for i in range(0, len(questions)): #len(questions)-1):
        print(list(questions.keys())[i])
        
        ans = str(input())
        if ans.lower() == 'yes':
            if list(questions.values())[i][1] == "Yes":
                df = df[df.Section.isin(list(questions.values())[i][0]) == False]
        elif ans.lower() == 'no':
            if list(questions.values())[i][1] == "No":
                df = df[df.Section.isin(list(questions.values())[i][0]) == False]
        else:
            print("Please type yes or no.")
            questionnaire(df, questions = questions)
        
    return df
324
  • 702
  • 8
  • 28
  • Does this answer your question? [Asking the user for input until they give a valid response](https://stackoverflow.com/questions/23294658/asking-the-user-for-input-until-they-give-a-valid-response) – mkrieger1 Sep 27 '22 at 14:59

2 Answers2

1

This should work for your problem:

def questionnaire(df, questions=questions):
    for i in range(0, len(questions)):  #len(questions)-1):
        print(list(questions.keys())[i])
        ans = None
        while ans not in ['yes', 'no']:
            print("Please type yes or no.")
            ans = str(input()).lower()
        if ans == 'yes':
            if list(questions.values())[i][1] == "Yes":
                df = df[df.Section.isin(list(questions.values())[i][0]) ==
                        False]
        else:
            if list(questions.values())[i][1] == "No":
                df = df[df.Section.isin(list(questions.values())[i][0]) ==
                        False]
    return df

So you can transform it to this code:

def questionnaire(df, questions=questions):
    for i in range(0, len(questions)):  #len(questions)-1):
        print(list(questions.keys())[i])
        ans = None
        while ans not in ['yes', 'no']:
            print("Please type yes or no.")
            ans = str(input()).lower()
        question = list(questions.values())[i]
        if question[1] == ans.title():
            df = df[df.Section.isin(question[0]) == False]
    return df
ErnestBidouille
  • 1,071
  • 4
  • 16
0

Issue: Recursion, restarting the entire questionnaire again

You have so many lines in that loop over questions that you might not see the recursion:

def questionnaire(df, questions = questions):
    for i in range(0, len(questions)): #len(questions)-1):
        # your code omitted for clarity
        else:
            print("Please type yes or no.")
            questionnaire(df, questions = questions)  # RECURSION! Starting the questionnaire over again

Let's clean the scene with some refactoring first. Then we can try to fix the issue. Instead of recursion we need a loop.

Refactor: Extract functions to have the loop short and clean

First let us add some comments that explain the abstractions (what your code is intended to do):

def questionnaire(df, questions = questions):
    # ask all questions, one at a time
    for i in range(0, len(questions)):  # for each question
        print(list(questions.keys())[i])  # ask current question
        
        ans = str(input())  # get user input as answer
        if ans.lower() == 'yes':
            # verify if given answer (title-cased YES) was expected
            if list(questions.values())[i][1] == "Yes":
                df = df[df.Section.isin(list(questions.values())[i][0]) == False]
        elif ans.lower() == 'no':
            # verify if given answer (title-cased NO) was expected
            if list(questions.values())[i][1] == "No":
                df = df[df.Section.isin(list(questions.values())[i][0]) == False]
        else:
            # if given answer is invalid (not in set of expected answers), ask again 
            print("Please type yes or no.")
            questionnaire(df, questions = questions)
        
    return df

Using the comments we can see similarities and refactor this. Also taking some improvements:

  • assume questions is a Python dict of key question mapped to value answer_options. Instead of iterating with for-i and then indexing you can use for-each like for question, answer_tuple in questions_dict.items() then replace list(questions.keys())[i] to question and list(questions.values())[i] to answer_tuple.
  • instead print(question); ans = input() you can directly prompt input(question)
def ask(question):
   # ask current question
   return str(input(question))  # get user input as answer


def store_answer(df, expected)
    return df[df.Section.isin(expected) == False]


def collect_answer(df, question, answer_tuple):
        ans = ask(question)
        while ans.lower() not in  {'yes', 'no'}:
            print("Invalid answer. Please type either yes or no.")
            ans = ask(question)

        # valid answer to store
        if ans.title() == answer_tuple[1]:  # if correct answer given
            return store_answer(df, answer_tuple[0])
        return df

def questionnaire(df, questions_dict = questions):
    # ask all questions, one at a time
    for question, answer_tuple in questions_dict.items():  # for each dict entry (question with answer_tuple)
        df = collect_answer(df, question, answer_tuple)
    return df

Fixed by Pattern: Loop until valid input

Instead of the recursion with questionnaire(df, questions) we just ask for the current question again in a loop until valid input was given:

    ans = ask(question)
    while ans.lower() not in {'yes', 'no'}:
        print("Invalid answer. Please type either yes or no.")
        ans = ask(question)
    # valid answer to store

If no valid answer was given, then it only asks the current question again ... as long as the answer is not in the set of valid options {'yes', 'no'}. As soon as this while loop exits, we know the answer is valid and can continue.

hc_dev
  • 8,389
  • 1
  • 26
  • 38