8
\$\begingroup\$

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_:
        return file_.read().replace("\"", "").split(",")


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)))
\$\endgroup\$

1 Answer 1

6
\$\begingroup\$

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:

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

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)

\$\endgroup\$

Your Answer

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

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