Project Euler 22: Names scores using dictionary

I solved Project Euler 22 using Python 3. The problem reads as follows:

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?

As you can see, the problem has much to do with handling lists of items and not so much with mathematics, unlike most other Euler problems. I chose to break the list of names, with the index/rank of each name as the key.

Are there ways to improve its performance that you can see? I'm also interested in any other possible improvements in style, naming, PEP8, etc.

'''
Project Euler 22: Names scores
https://projecteuler.net/problem=22
'''
import string
from typing import List, Dict

def get_names_from_file(file_path: str) -> List:
'''
Parses a text file containing names in this format:
"FOO","BAR","BAZ"
and returns a list of strings, e.g.,
["FOO","BAR","BAZ"]
'''
with open(file_path, "r") as file_:

def get_sorted_indexed_dict(items: List, is_0_indexed: bool = False) -> Dict:
'''
Takes a list of strings and returns an alphabetically sorted dict
with the item's 1-indexed (by default) position as key, e.g.,
list arg: [ "FOO", "BAR", "BAZ" ]
returns: { 1 : "BAR", 2 : "BAZ", 3 : "FOO" }
The return dict can be 0-indexed by adding a 2nd argument as True.
'''
ix_start = 1
if is_0_indexed:
ix_start = 0
items = sorted(items)
numbered_dict = {}
for item in items:
numbered_dict[items.index(item) + ix_start] = item
return numbered_dict

def get_alpha_values() -> Dict:
'''
Assigns ASCII chars A-Z, inclusive, to a
number value 1-26, respectively.
'''
alpha_values = {}
index = 1
letters = list(string.ascii_uppercase)
for letter in letters:
alpha_values[letter] = index
index += 1
return alpha_values

def get_word_alpha_value(word: str, alpha_values: Dict) -> int:
'''
Calculates the value of each letter in the word
and returns the sum of the letter values.
'''
word_value = 0
for letter in list(word):
word_value += alpha_values[letter]
return word_value

def get_word_ranked_value(rank: int, word: str, alpha_values: Dict) -> int:
'''
Calculates the ranked value according to problem 22, i.e.,
the value of each word's letters multiplied by its
alphabetical rank.
'''
return rank * get_word_alpha_value(word, alpha_values)

def get_pe22_solution(file_path: str) -> int:
'''
Returns the solution to Project Euler 22 based on the file path provided.
'''
alpha_values = get_alpha_values()
# sanity check some alpha values
assert alpha_values['A'] == 1
assert alpha_values['Z'] == 26
assert alpha_values['S'] == 19

names = get_names_from_file(file_path)
names = get_sorted_indexed_dict(names)
# sanity checks name order based on PE 22 problem description
assert names[1] == "AARON"
assert names[938] == "COLIN"
assert names[5163] == "ZULMA"

name_values = [
get_word_ranked_value(k, v, alpha_values)
for (k, v) in names.items()
]
return sum(name_values)

if __name__ == "__main__":
FILE = "names.txt"
print("The answer to PE 22 is: {}".format(get_pe22_solution(FILE)))


Nice PEP8 compliant code, Phrancis. Good job! I only have two comments to make regarding that: you usually have to use triple-double quoted strings when it comes to docstrings. From the docs (PEP257):

For consistency, always use """triple double quotes""" around docstrings. Use r"""raw triple double quotes""" if you use any backslashes in your docstrings. For Unicode docstrings, use u"""Unicode triple-quoted strings""" .

More, you missed one newline between your imports and the first function. It's not such a big deal but I thought it's worth mentioning it.

In this line:

...


You can use single quotes to get rid of the escape character \:

return file_.read().replace('"', '').split(',')


You can also omit the read file mode as that's the default mode when you read a file:

with open(file_path) as file_:
...


There's more than one place where you wrote:

something = list(string)


Instead of the situation where you sorted the list, you don't need list(string) because you can already iterate a string just fine.

Use enumerate() more often.

The enumerate() function adds a counter to an iterable. That said, you can rewrite your get_alpha_values() function like this:

def get_alpha_values() -> Dict:
alpha_values = {}
for index, letter in enumerate(string.ascii_uppercase, 1):
alpha_values[letter] = index
return alpha_values


Enumerate takes a second argument which lets you start from whatever position you want (in our case, that would be 1).

We can also make use of the get() method of the dicts and make your get_word_alpha_value() more straightforward:

def get_word_alpha_value(word: str, alpha_values: Dict) -> int:
return sum([alpha_values.get(letter, 0) for letter in word])


The get method of a dict (like for example characters) works just like indexing the dict, except that, if the key is missing, instead of raising a KeyError it returns the default value (if you call .get with just one argument, the key, the default value is None)