patternpythonModerate
"The Minion Game" challenge
Viewed 0 times
miniongamethechallenge
Problem
This is an implementation of the minion game, which takes in string input and declares a winner between two pre-determined players.
The logic is player 1 gets all sequenced words not starting with a vowel and player 2 gets all sequenced words starting with vowel in the input string.
Objective: performance optimisation. The code passed all the test cases, but I am very unsure where to tune the performance.
For a 1000 letter word, here are the time stats:
Sample input / output:
The logic is player 1 gets all sequenced words not starting with a vowel and player 2 gets all sequenced words starting with vowel in the input string.
Objective: performance optimisation. The code passed all the test cases, but I am very unsure where to tune the performance.
For a 1000 letter word, here are the time stats:
- real 0m10.142s
- user 0m6.386s
- sys 0m0.743s
import string
import sys
import itertools
def minion(str):
person_a_name = 'Stuart'
person_b_name = 'Kevin'
letter_list = [a for a in str]
l = len(letter_list)
vowel = ['A','E','I','O','U']
consonants = ['Q','W','R','T','Y','P','S','D','F','G','H','J','K','L','Z','X','C','V','B','N','M']
all_word = []
person_a_words = []
person_b_words = []
all_word = [letter_list[start:end+1] for start in xrange(l) for end in xrange(start, l)]
for array in all_word:
if array[0] in vowel:
person_b_words.append(array)
for array in all_word:
if array[0] in consonants:
person_a_words.append(array)
if len(person_a_words) == len(person_b_words):
print 'Draw'
if len(person_a_words) > len(person_b_words):
print person_a_name, len(person_a_words)
if len(person_b_words) > len(person_a_words):
print person_b_name, len(person_b_words)
def main():
str = raw_input()
minion(str.upper())
if __name__ == '__main__':
main()Sample input / output:
banana
Stuart 12
guavaisanotherfruit
Kevin 98
Solution
The big performance hit probably comes in this block of code, where you don a bunch of array manipulations and looping:
Appending to an array is a (relatively) expensive operation, as is looping over a list. I can see a number of optimisations here. [Edit: I wrote these in the order I thought of them; the big performance gain comes on the last item. I'm leaving the remaining items because they're still instructive, even if not directly applicable here.]
-
Only loop over all_word once. You can check for starting with a consonant and vowel in the same iteration of the loop:
We've just cut out an iteration over all_word. If all_word is large, that will be a significant saving.
-
Don't store the words in a list, just the count. All you care about is the relative number of words in each list; the words themselves don't matter. It's much easier to increment an integer than mutate a list, so consider the following:
and then you can compare the two integers at the end. That's bound to be a performance saving.
-
Don't construct all_word as a list; use a generator. If you replace the square brackets with parens:
then this becomes a generator comprehension instead of a list comprehension. This means it only creates the elements as they're needed by the for loop; it doesn't create them all in memory before continuing.
Using generators instead of lists is a really good way to reduce occupancy and speed up programs.
-
Do you even need to use all_word? For each value of start, the first letter of the resulting words will be the same, and this gives you
You could just add the number of distinct words to each person's score directly:
That is substantially faster: I just ran this with a 1.5m character string, and it finished in ~1.5s.
Other non-performance related comments:
-
You can get the individual letters of a string by calling
although in this case, you don't need to coerce to a list first – you can iterate over the characters of a string directly.
-
Your function is doing some work (finding out whether there are more vowel sub-words or consonant subwords) and printing to screen (the result). It would be better to separate this into two functions: one that does the work, the other does the result.
That makes it easier to reuse the work of vowels vs. consonants.
-
To aid readability, I'd keep the same order of variables when you do the comparison at the end. i.e.
```
if vowel_count > consonant_count:
print("Vowels victorious!")
elif vowel_count < consonant_count:
print("Consonants champion!")
else:
print("D
all_word = [letter_list[start:end+1] for start in xrange(l)
for end in xrange(start, l)]
for array in all_word:
if array[0] in vowel:
person_b_words.append(array)
for array in all_word:
if array[0] in consonants:
person_a_words.append(array)
if len(person_a_words) == len(person_b_words):
print 'Draw'
if len(person_a_words) > len(person_b_words):
print person_a_name, len(person_a_words)
if len(person_b_words) > len(person_a_words):
print person_b_name, len(person_b_words)Appending to an array is a (relatively) expensive operation, as is looping over a list. I can see a number of optimisations here. [Edit: I wrote these in the order I thought of them; the big performance gain comes on the last item. I'm leaving the remaining items because they're still instructive, even if not directly applicable here.]
-
Only loop over all_word once. You can check for starting with a consonant and vowel in the same iteration of the loop:
for array in all_word:
if array[0] in vowel:
person_b_words.append(array)
elif array[0] in consonants:
person_a_words.append(array)We've just cut out an iteration over all_word. If all_word is large, that will be a significant saving.
-
Don't store the words in a list, just the count. All you care about is the relative number of words in each list; the words themselves don't matter. It's much easier to increment an integer than mutate a list, so consider the following:
person_a_words = 0
person_b_words = 0
for array in all_word:
if array[0] in vowel:
person_b_words += 1
elif array[0] in consonants:
person_a_words += 1and then you can compare the two integers at the end. That's bound to be a performance saving.
-
Don't construct all_word as a list; use a generator. If you replace the square brackets with parens:
all_word = (letter_list[start:end+1] for start in xrange(l)
for end in xrange(start, l))then this becomes a generator comprehension instead of a list comprehension. This means it only creates the elements as they're needed by the for loop; it doesn't create them all in memory before continuing.
Using generators instead of lists is a really good way to reduce occupancy and speed up programs.
-
Do you even need to use all_word? For each value of start, the first letter of the resulting words will be the same, and this gives you
(l - start) different words. You don't actually need to create the words; you just care about their initial letter, and how many distinct words they create.You could just add the number of distinct words to each person's score directly:
person_a_words = 0
person_b_words = 0
for idx, letter in enumerate(letter_list):
if letter in vowel:
person_b_words += len(letter_list) - idx
else:
person_a_words += len(letter_list) - idxThat is substantially faster: I just ran this with a 1.5m character string, and it finished in ~1.5s.
Other non-performance related comments:
- Don't use
stras a variable name; overriding builtins is bad practice.
- You've imported the string, sys and itertools modules, but you never use any of them. Why?
- PEP 8 requires a space after commas in a list; you should add this in
vowelandconsonants.
-
You can get the individual letters of a string by calling
list() on it. These calls are equivalent:letter_list = [a for a in my_string]
letter_list = list(my_string)although in this case, you don't need to coerce to a list first – you can iterate over the characters of a string directly.
- There's no need to assign the length of
letter_listto a variable, especially not one with as undescriptive a name asl. It just makes your code harder to read.
- The name of your function isn't particularly helpful. Ideally it should give me some idea of what the function does. There should also be a docstring to explain the result.
- I would rename the
person_aandperson_bvariables to beconsonantandvowel, respectively – that will make the code easier to read. A and B don't really mean anything (and as evidence, I got them the wrong way round when I first wrote that sentence).
-
Your function is doing some work (finding out whether there are more vowel sub-words or consonant subwords) and printing to screen (the result). It would be better to separate this into two functions: one that does the work, the other does the result.
That makes it easier to reuse the work of vowels vs. consonants.
-
To aid readability, I'd keep the same order of variables when you do the comparison at the end. i.e.
```
if vowel_count > consonant_count:
print("Vowels victorious!")
elif vowel_count < consonant_count:
print("Consonants champion!")
else:
print("D
Code Snippets
all_word = [letter_list[start:end+1] for start in xrange(l)
for end in xrange(start, l)]
for array in all_word:
if array[0] in vowel:
person_b_words.append(array)
for array in all_word:
if array[0] in consonants:
person_a_words.append(array)
if len(person_a_words) == len(person_b_words):
print 'Draw'
if len(person_a_words) > len(person_b_words):
print person_a_name, len(person_a_words)
if len(person_b_words) > len(person_a_words):
print person_b_name, len(person_b_words)for array in all_word:
if array[0] in vowel:
person_b_words.append(array)
elif array[0] in consonants:
person_a_words.append(array)person_a_words = 0
person_b_words = 0
for array in all_word:
if array[0] in vowel:
person_b_words += 1
elif array[0] in consonants:
person_a_words += 1all_word = (letter_list[start:end+1] for start in xrange(l)
for end in xrange(start, l))person_a_words = 0
person_b_words = 0
for idx, letter in enumerate(letter_list):
if letter in vowel:
person_b_words += len(letter_list) - idx
else:
person_a_words += len(letter_list) - idxContext
StackExchange Code Review Q#106238, answer score: 13
Revisions (0)
No revisions yet.