4
\$\begingroup\$

Using names.txt (right click and 'Save Link/Target As...'), a 46K text file containing over five-thousand first names, begin by sorting it into alphabetical order. Then working out the alphabetical value for each name, multiply this value by its alphabetical position in the list to obtain a name score.

For example, when the list is sorted into alphabetical order, COLIN, which is worth \$3 + 15 + 12 + 9 + 14 = 53\$, is the 938th name in the list. So, COLIN would obtain a score of \$938 × 53 = 49714\$.

What is the total of all the name scores in the file?

import string


def calculate_score(filename):
    """Reads names and returns total scores."""
    letter_scores = {letter: ord(letter) - 64 for letter in string.ascii_uppercase}
    with open(filename) as names:
        total = 0
        names = sorted(names.read().replace('"', '').split(','))
        for index, name in enumerate(names):
            temp = 0
            for letter in name:
                temp += letter_scores[letter]
            total += temp * (index + 1)
        return total


if __name__ == '__main__':
    print(calculate_score('p022_names.txt'))
\$\endgroup\$
2
\$\begingroup\$

The code looks quite good in general. Without a deeper look at the problem, there are twothree things that can easily be improved:

Building the look-up table

You use

letter_scores = {letter: ord(letter) - 64 for letter in string.ascii_uppercase}

which makes use of the ASCII encoding to determine the value of each letter. The following could be considered a little bit more straightforward:

letter_scores = {letter: index for index, letter in enumerate(string.ascii_uppercase, 1)}

This uses enumerate with a starting value of 1 to assign the position in the alphabet.

Iterating over the sorted names

When iterating over the sorted names, you already use enumerate to get index, but since you only use index+1, you can reuse the trick from above to simplify this even further (as @RootTwo rightfully pointed out in his comment).

Computing the score

To compute the score you use a for loop with an intermediate variable:

temp = 0
for letter in name:
    temp += letter_scores[letter]
total += temp * (index + 1)

This can be rewritten using a generator expression and sum:

total += sum(letter_scores[letter] for letter in name) * (index + 1)

It basically allows you to get rid of the additional counter variable.


Going nuts

You can also reapply that generator expression idea to the first loop and end with something like:

def calculate_score(filename):
    """Reads names and returns total scores."""
    letter_scores = {letter: index for index, letter in enumerate(string.ascii_uppercase, 1)}
    with open(filename) as names:
        names = sorted(names.read().replace('"', '').split(','))
        return sum(
            sum(letter_scores[letter] for letter in name) * index
            for index, name in enumerate(names, 1)
        )
\$\endgroup\$
  • \$\begingroup\$ Change the for loop to use enumerate(names, 1). Then just use index instead of (index + 1) when updating total. \$\endgroup\$ – RootTwo Jul 19 at 21:35
  • \$\begingroup\$ @RootTwo: I included your feedback and also added a nested generator expression solution. \$\endgroup\$ – AlexV Jul 19 at 21:48

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.