12
\$\begingroup\$

This is a "Help me make a choice" script where the user can input two options and then, based on the (3) reasons why they are good options, and the reasons weight the script will tell you which option you should choose.

This is my first Python script based on an idea I had and I'd like to know how I could make it better. For example, should I store the reasons (ie. option1a) in a list, dictionary or keep them in variables? Should I do the same to the weight (ie. option1aw) of each option?

The code works and does what I wanted it to do, but I don't know if I did in the best way possible.

import random

print("First, tell me what your options are.")

option1 = input("Option 1: ")
option2 = input("Ok. Now tell me the other option: ")

option1a = input("\nGood. Now tell me a reason why \'{}\' is a good choice: ".format(option1))

option1b = input("Ok. Now tell me another reason why \'{}\' is a good choice: ".format(option1))

option1c = input("Ok. Now tell me another reason why \'{}\' is a good choice: ".format(option1))

option2a = input("\nThats all for \'{}\'. Now tell me why \'{}\' is a good choice: ".format(option1, option2))

option2b = input("Now tell me another reason why \'{}\' is a good choice: ".format(option2))

option2c = input("Now tell me another reason why \'{}\' is a good choice: ".format(option2))

option1aw = int(input("\nNow let's evaluate the options. Regarding \'{}\', "
                      "from 1 to 5, tell me how important is \'{}\'? ".format(option1, option1a)))

option1bw = int(input("Still regarding \'{}\', from 1 to 5, tell me how important is \'{}\'? ".format(option1, option1b)))

option1cw = int(input("How about \'{}\', from 1 to 5, tell me how important it is: ".format(option1c)))

option2aw = int(input("\nNow let's evaluate the rest of the options. Regarding \'{}\',"
                      " from 1 to 5, tell me how important is \'{}\'? ".format(option2, option2a)))

option2bw = int(input("Still regarding \'{}\', from 1 to 5, tell me how important is \'{}\'? ".format(option2, option2b)))

option2cw = int(input("How about \'{}\', from 1 to 5, tell me how important it is: ".format(option2c)))

prompt = input("\nWe'll calculate now. Click enter when you are ready to see the results. > ")


option1result = option1aw + option1bw + option1cw

option2result = option2aw + option2bw + option2cw


options = [option1, option2]

def coinflip():
    print("Your best choice is:")
    print(options[random.randint(0, len(options)-1)])

if option1result > option2result:
    print("\n\'{}\' is your best choice based on the reasons you gave me. ".format(option1))

elif option1result == option2result:
    print("\nHonestly, both are good options. Do you want to flip a coin? Press enter. ")
    input()
    coinflip()

else:
    print("\n\'{}\' is your best choice based on the reasons you gave me. ".format(option2))


quit()
\$\endgroup\$
15
\$\begingroup\$
  1. I think your program would be easier to use and create if you rearrange when you ask your questions. (This is mostly to show that I have consciously changed how your program works)
  2. You are correct it would be easier to use your data if you enter it as a dictionary and some lists. I personally would use the following layout:

    option_1 = {
        'option': option1,
        'reasons': [option1a, option1b, option1c],
        'weights': [option1aw, option1bw, option1cw],
    }
    

    This allows getting the relevant by indexing the objects.
    For example to get the entered option you can do:

    option_1['option']
    

    To get the first reason you can do:

    option_1['option'][0]
    

    It should be noted that lists in Python, and most programming languages, are indexed starting at 0, which is why to get the first value we see the 0 above.

  3. Reduce your workload by using functions, these allow you to define a set of instructions to run which you can then reuse by calling the function.

    Take the following function to get an option:
    Note: I have changed the questions in this code snippet.

    def get_option():
        return {
            'option': input('Enter an option: '),
            'reasons': [
                input(f'Option 1: Why is this a good option? '),
                input(f'Option 2: Why is this a good option? '),
                input(f'Option 3: Why is this a good option? '),
            ],
            'weights': [
                int(input(f'How important is Option 1? (from 1-5) ')),
                int(input(f'How important is Option 2? (from 1-5) ')),
                int(input(f'How important is Option 3? (from 1-5) ')),
            ]
        }
    
  4. Allowing a user to enter two options is now simple. You make a list with both of them.

    options = [
        get_option(),
        get_option(),
    ]
    
  5. Before we go any further I'd like to show you my favorite feature of Python - list comprehensions. These allow you to perform a task on a list in a single line. Take the above code snippet to generate two options, we can rewrite that using standard list generation methods exposed in Python and other languages, which would look like:

    options = []  # Build an empty list
    for _ in range(2):  # Loop twice
        options.append(get_option())  # Add an option to options on each loop
    

    However this pattern is rather messy and it would be more Pythonic to use a comprehension here.

    options = [
        get_option()
        for _ in range(2)
    ]
    

    You should be able to notice we can also use this to simplify our get_option code.

    def get_option():
        return {
            'option': input('Enter an option: '),
            'reasons': [
                input(f'Reason {i+1}: Why is this a good option? ')
                for i in range(3)
            ],
            'weights': [
                int(input(f'How important is Reason {i+1}? (from 1-5) '))
                for i in range(3)
            ]
        }
    
  6. From here we can change your option1result and option2result to:

    results = [sum(option['weights']) for option in options]
    
  7. You can use random.choice to chose from a list.

  8. It is best practice to use an if __name__ == '__main__': guard to prevent your code from running unless it's the main program.
  9. Don't use quit, if you remove it the program will exit successfully.
import random


def get_option():
    return {
        'option': input('Enter an option: '),
        'reasons': [
            input(f'Reason {i+1}: Why is this a good option? ')
            for i in range(3)
        ],
        'weights': [
            int(input(f'How important is Reason {i+1}? (from 1-5) '))
            for i in range(3)
        ]
    }


if __name__ == '__main__':
    print('First, tell me what your options are.')
    options = [
        get_option()
        for _ in range(2)
    ]
    prompt = input('\nWe will calculate now.\n')

    results = [sum(option['weights']) for option in options]
    if results[0] == results[1]:
        input('There are multiple best options.\nThe best will be determined by coinflip.\n')
        best_option = random.choice(options)
    else:
        best_option = options[0] if results[0] > results[1] else results[1]

    print('Your best choice is:')
    print(options[0]['option'])

Additional improvements:

  1. Your code only currently works for two options. To get the top options for any amount of options is fairly simple.

    You want a dictionary that holds all the options with a certain total weight. After this you just want to take the max weight, which will give you all the options with that weight.

    Once you have have the best options the code is pretty much the same, if there are multiple options then you just use random.choice on them to narrow them down to one.

  2. You should allow your user to enter how many options and reasons they want. This now is just a simple question you can ask before entering either loop.

  3. The way you get user input is error prone, if I enter a as my weight then your code blows up.
    You should also take into account that you've told your user that only 1-5 are valid entries, but happily allow -1, and 6.
  4. Not displaying all the best options seems like a poor oversight. Since we know all the best options we can just display them by looping.

Ignoring 3 this can get:

import random


def get_option():
    option = input('Enter an option: ')
    reasons = int(input('How many reasons do you have for this option? '))
    return {
        'option': option,
        'reasons': [
            input(f'Reason {i+1}: Why is this a good option? ')
            for i in range(reasons)
        ],
        'weights': [
            int(input(f'How important is Reason {i+1}? (from 1-5) '))
            for i in range(reasons)
        ]
    }


def get_top_options(options):
    rankings = {}
    for option in options:
        rankings.setdefault(sum(option['weights']), []).append(option)
    return rankings[max(rankings)]


if __name__ == '__main__':
    amount_options = int(input('How many options are there? '))
    options = [
        get_option()
        for _ in range(amount_options)
    ]
    prompt = input('\nWe will calculate now.\n')

    best_options = get_top_options(options)
    if len(best_options) == 1:
        print('Your best choice is:')
    else:
        print('Your best choices are:')
    for option in best_options:
        print(option['option'])
\$\endgroup\$
  • \$\begingroup\$ Thank you for taking the time to reply so extensively. It was basically a Python lesson!! I'll study this later today. \$\endgroup\$ – Leo Rapini Aug 21 at 18:40
  • 1
    \$\begingroup\$ @LeoRapini No problem, thank you for the reputation. I have also included a couple more improvements. Happy learning :) \$\endgroup\$ – Peilonrayz Aug 21 at 20:16
6
\$\begingroup\$

Data instead of code

You have a lot of repeated calls to input. This should really just be a tuple of strings that all refer to a choices dict; something like:

choices = {}

prompts = (
   ('option1': 'Option 1:'), 
   # ...
   ('option1a': 'Good. Now tell me a reason why {option1} is a good choice: '),
   # ...
)

for name, prompt in prompts:
    choices[name] = input(prompt.format(**choices))

Global code

Move most of your global statements into functions, with a top-level main function.

Quit

...at the end is redundant.

The illusion of choice

Don't ask the user whether they want to flip a coin, only to do it anyway. Either given them an actual choice, or just say that it's going to happen.

\$\endgroup\$
  • \$\begingroup\$ Thank you for your reply @Reinderien. I love the Illusion of choice in the end!! \$\endgroup\$ – Leo Rapini Aug 21 at 18:38

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.