4
\$\begingroup\$

This is a puzzle I found online that I have been trying to optimize and make the best version. I've tested it and it works great but I'm curious how it can be improved in any way.

Instructions:

"You forgot to give Professor Boolean's favorite rabbit specimen a name? You know how picky the professor is! Only particular names will do! Fix this immediately, before you're... eliminated!"

Luckily, your minion friend has already come up with a list of possible names, and we all know that the professor has always had a thing for names with lots of letters near the 'tail end' of the alphabet, so to speak. You realize that if you assign the value 1 to the letter A, 2 to B, and so on up to 26 for Z, and add up the values for all of the letters, the names with the highest total values will be the professor's favorites. For example, the name Annie has value 1 + 14 + 14 + 9 + 5 = 43, while the name Earz, though shorter, has value 5 + 1 + 18 + 26 = 50.

If two names have the same value, Professor Boolean prefers the lexicographically larger name. For example, if the names were AL (value 13) and CJ (value 13), he prefers CJ.

Write a function answer(names) which takes a list of names and returns the list sorted in descending order of how much the professor likes them.

There will be at least 1 and no more than 1000 names. Each name will consist only of lower case letters. The length of each name will be at least 1 and no more than 8.

Test cases:

Inputs:

(string list) names = ["annie", "bonnie", "liz"]

Output:

(string list) ["bonnie", "liz", "annie"]

Inputs:

(string list) names = ["abcdefg", "vi"]

Output:

(string list) ["vi", "abcdefg"]

Your code will run inside a Python 2.7.6 sandbox.

Standard libraries are supported except for bz2, crypt, fcntl, mmap, pwd, pyexpat, select, signal, termios, thread, time, unicodedata, zipimport, zlib.

My answer:

def answer(names):
    # Sort names by reverse alphebetical order
    names = sorted(names, reverse=True)
    # Evaluate the value of each name and build a list with name,value pairs
    name_with_values = sorted([((current_name,value(current_name))) for current_name in names], 
        key=lambda value: value[1], reverse=True)
    # Return the list only containing the names
    return [name[0] for name in name_with_values]

def value(name):
    # Evaluate and return value of the name
    return sum([ord(letter) - 96 for letter in name])

print answer(names = ["annie", "bonnie", "liz", "bpnnid"])

Feedback I'm interested in:

  • Is my logic easy to understand?
  • How can it be styled or named better?
  • Is it more complex or more computationally expensive than it needs to be?
  • How is the general readability/clarity?
\$\endgroup\$
  • \$\begingroup\$ Can you tell us where you found this puzzle? Is it a Google Foobar challenge? \$\endgroup\$ – Gareth Rees Jul 3 '15 at 12:28
  • \$\begingroup\$ Yeah, this is a Foobar challenge. \$\endgroup\$ – mxplusb Dec 9 '15 at 19:00
5
\$\begingroup\$

You are using the sorted function's key parameter in a very odd way. As you have a function that takes a string and returns a value to sort on, you could implement this much more neatly as:

def answer(names):
    return sorted(names, key=value, reverse=True)

See this Python wiki article for more on sorting. In use:

>>> sorted(["annie", "bonnie", "liz"], key=value, reverse=True)
['bonnie', 'liz', 'annie']
>>> sorted(["abcdefg", "vi"], key=value, reverse=True)
['vi', 'abcdefg']

Note that this doesn't meet the requirements for breaking ties, though; as value('al') == value('cj') and sorted is stable, they stay in the input order:

>>> sorted(('cj', 'al'), key=value, reverse=True)
['cj', 'al']  # ok!
>>> sorted(('al', 'cj'), key=value, reverse=True)
['al', 'cj']  # oh...

Your current pre-sort fixes this, and could be incorporated here too:

>>> sorted(sorted(('al', 'cj'), reverse=True), key=value, reverse=True)
['cj', 'al']

To get around this without overcomplicating the code, value can return a tuple (score, string), so the string is used to break ties:

def value(name):
    """Evaluate and return value of the name."""
    return sum([ord(letter) - 96 for letter in name]), name
                                                   # ^ note

Now 'cj' is always preferred:

>>> sorted(('al', 'cj'), key=value, reverse=True)
['cj', 'al']
>>> sorted(('cj', 'al'), key=value, reverse=True)
['cj', 'al']

Beyond that, note that you should use docstrings rather than just comments:

def value(name):
    """Evaluate and return value of the name."""
    return sum([ord(letter) - 96 for letter in name])

Otherwise it looks pretty good: the code is well laid out, and follows the style guide. If anything, I'd say you have too many comments; they shouldn't explain things the reader could learn from the code, but why the code is written that way (if this is obvious, no comment is needed).

\$\endgroup\$

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.