7
\$\begingroup\$

The program takes weighted strengths of the human body (arms/legs/core/mind) based on the required attributes for a sport, then tells you the resulting sport. The program is obviously really ugly. Is there a way I could use dictionaries and a lambda function to find a max instead? This latter part is what I don't know how to do.

print("Welcome to What's your ideal sport! Answer the following questions on a scale from 1-10.")

leg_strength = int(input("How good is your leg strength / speed?"))
arm_strength = int(input("How good is your arm strength?"))
core_strength = int(input("How strong are your abs?"))
mental_strength = int(input("How intelligent are you?"))
reaction_time = int(input("How fast are your reactions?"))

# Sports are: Soccer, Football, Tennis, Swimming, Basketball, Track, Gymnastics, and Chess.

sports = []

soccer = (arm_strength*5) + (reaction_time*2) + (mental_strength) + (arm_strength) + (core_strength)
sports.append(soccer)
chess = mental_strength * 10
sports.append(chess)
football = (arm_strength*4) + (core_strength*3) + (leg_strength*3)
sports.append(football)
tennis = (reaction_time*3) + (arm_strength*4) + (core_strength*2) + (mental_strength)
sports.append(tennis)
swimming = reaction_time + (core_strength*3) + (arm_strength*3) + (leg_strength*3)
sports.append(swimming)
basketball = (leg_strength*2) + (arm_strength*5) + (mental_strength*2) + core_strength
sports.append(basketball)
track = (leg_strength*6) + (arm_strength*2) + (reaction_time) + (mental_strength)
sports.append(track)
gymnastics = (leg_strength*3) + (arm_strength*3) + (core_strength*3) + mental_strength
sports.append(gymnastics)

print(sports)

best = max(sports)
sports.index(best)

indices = []

for sport in sports:
    if sport == best:
        indices.append(sports.index(sport))

result = []

for index in indices:
    if index == 0:
        result.append("Soccer")
    elif index == 1:
        result.append("Chess")
    elif index == 2:
        result.append("Football")
    elif index == 3:
        result.append("Tennis")
    elif index == 4:
        result.append("Swimming")
    elif index == 5:
        result.append("Basketball")
    elif index == 6:
        result.append("Track")
    elif index == 7:
        result.append("Gymnastics")

    print("Your best olympic sport(s) are: {0}.".format(result))
\$\endgroup\$
2
  • 1
    \$\begingroup\$ “disregarding the actual weightings obviously” doesn’t work – to actually ask for a recommendation, you need a real-world scenario. \$\endgroup\$
    – Ry-
    Aug 1, 2017 at 0:38
  • \$\begingroup\$ well what I mean is assume that the weightings I have are accurate to the sport; in case someone thought there was a better 'algorithm' than a simple weighted comparison. \$\endgroup\$ Aug 1, 2017 at 1:19

2 Answers 2

6
\$\begingroup\$

Perhaps you could represent the weightings of each trait as a tuple of multipliers, which are applied to the input values.

Here you have a list of weightings, to which it's trivial to add entries.

from operator import itemgetter, mul
from itertools import groupby, starmap

print("Welcome to What's your ideal sport! "
      "Answer the following questions on a scale from 1-10.")

traits = ('leg', 'arm', 'core', 'mental', 'reaction')

questions = ["How good is your leg strength / speed? ",
             "How good is your arm strength? ",
             "How strong are your abs? ",
             "How intelligent are you? ",
             "How fast are your reactions? "]

responses = [int(input(q)) for q in questions]

print("weights: ")
for t, a in zip(traits, responses):
    print("%s strength = %d" % (t, a))

weightings = [
    # sport, (legs, arms, core, mental, reactions)
    ('soccer',     (5, 2, 1, 1, 1)),
    ('chess',      (1, 1, 1, 10, 1)),
    ('football',   (3, 4, 3, 1, 1)),
    ('tennis',     (1, 4, 2, 1, 3)),
    ('swimming',   (3, 3, 3, 1, 1)),
    ('basketball', (2, 5, 1, 2, 1)),
    ('track',      (6, 2, 1, 1, 1)),
    ('gymnastics', (3, 3, 3, 1, 1))
]


def calcscore(weights, answers):
    ''' calculate the score for a given set of weights and answers, by
        multiplying each answer by the matching weight and adding them
        all together.
    '''
    return sum(weight * answer for (weight, answer) in zip(weights, answers))
    # more esoteric implementation.
    #return sum(starmap(mul, zip(weights, answers)))

# results prior to sorting
result = [(name, calcscore(traits, responses)) for (name, traits) in weightings]

# sorted results prior ordered by score descending
sresult = sorted(result, key=itemgetter(1), reverse=True)

# group and take first group. It should never be empty.
grouper, group = next(groupby(sresult, key=itemgetter(1)))

print('results:')
print("with score %d" % grouper)
print("we have the sports")
for name, score in group:
    print('%s - %d' % (name, score))

Some of the values in the weightings should probably be zero rather than one. To represent an absence of this trait from the calculation. It will be straightforward for the op to modify these themselves. I am demonstrating the concept.

\$\endgroup\$
5
\$\begingroup\$

Currently, the code is too fragile and does not scale - it is not straightforward to add a new sport or more skills/attributes - you would have to actually modify the main program logic to add a new sport.

What if we define SPORTS - a mapping of sports names to formulas where each formula is defined as a lambda function. We can either use a simple lightweight class or a namedtuple to handle attributes:

from collections import namedtuple
from operator import itemgetter


SPORTS = {
    "soccer": lambda attrs: attrs.arm_strength * 5 + attrs.reaction_time * 2 + attrs.mental_strength + attrs.arm_strength + attrs.core_strength,
    "chess": lambda attrs: attrs.mental_strength * 10,
    "football": lambda attrs: attrs.arm_strength * 4 + attrs.core_strength * 3 + attrs.leg_strength * 3,
    "tennis": lambda attrs: attrs.reaction_time * 3 + attrs.arm_strength * 4 + attrs.core_strength * 2 + attrs.mental_strength,
    "swimming": lambda attrs: attrs.reaction_time + attrs.core_strength * 3 + attrs.arm_strength * 3 + attrs.leg_strength * 3,
    "basketball": lambda attrs: attrs.leg_strength * 2 + attrs.arm_strength * 5 + attrs.mental_strength * 2 + attrs.core_strength,
    "track": lambda attrs: attrs.leg_strength * 6 + attrs.arm_strength * 2 + attrs.reaction_time + attrs.mental_strength,
    "gymnastics": lambda attrs: attrs.leg_strength * 3 + attrs.arm_strength * 3 + attrs.core_strength * 3 + attrs.mental_strength
}

Attributes = namedtuple('Attributes', ['leg_strength', 'arm_strength', 'core_strength', 'mental_strength', 'reaction_time'])


if __name__ == '__main__':
    print("Welcome to What's your ideal sport! Answer the following questions on a scale from 1-10.")

    leg_strength = int(input("How good is your leg strength / speed?"))
    arm_strength = int(input("How good is your arm strength?"))
    core_strength = int(input("How strong are your abs?"))
    mental_strength = int(input("How intelligent are you?"))
    reaction_time = int(input("How fast are your reactions?"))

    attrs = Attributes(leg_strength, arm_strength, core_strength, mental_strength, reaction_time)
    skills = {sport: formula(attrs) for sport, formula in SPORTS.items()}

    # determine best skills
    _, max_value = max(skills.items(), key=itemgetter(1))
    olympic_sports = [sport for sport, value in skills.items() if value == max_value]
    print("Your best olympic sport(s) are: {0}.".format(", ".join(olympic_sports)))

Now, all it takes to create a new sport is to add an item to the SPORTS dictionary - without touching the main logic of the program.

\$\endgroup\$
4
  • 2
    \$\begingroup\$ While the fragility and scalability comments are certainly correct, the suggested implementation is not. It is equally fragile. Soccer's attrs.arm_strength * 5 + ... + attrs.arm_strength is a good indication. \$\endgroup\$
    – vnp
    Aug 1, 2017 at 3:41
  • \$\begingroup\$ @vnp could you please elaborate? (btw, the formula's themselves are copied from the initial code - except the extra parenthesis and spaces around operators) Thanks! \$\endgroup\$
    – alecxe
    Aug 1, 2017 at 3:44
  • 2
    \$\begingroup\$ Copied formulae is exactly my point. Here lies the fragility. I'd suggest a set of attribute-weight dictionaries for each sport. \$\endgroup\$
    – vnp
    Aug 1, 2017 at 3:51
  • \$\begingroup\$ @vnp ah, good point, I see what you mean! Yeah, this part was not improved - my "fragility" note was about the index-based best sport definition. Really like the idea to use weights for attributes! Thanks. \$\endgroup\$
    – alecxe
    Aug 1, 2017 at 3:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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