Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cohort 22 Adagrams Guevara Alejandra #40

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 95 additions & 4 deletions adagrams/game.py

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like there was only a single commit for the whole project. Try to commit more frequently on future projects. After finishing up each wave is a great time to commit your work so far.

Original file line number Diff line number Diff line change
@@ -1,11 +1,102 @@
import random
LETTER_POOL = {
'A': 9,
'B': 2,
'C': 2,
'D': 4,
'E': 12,
'F': 2,
'G': 3,
'H': 2,
'I': 9,
'J': 1,
'K': 1,
'L': 4,
'M': 2,
'N': 6,
'O': 8,
'P': 2,
'Q': 1,
'R': 6,
'S': 4,
'T': 6,
'U': 4,
'V': 2,
'W': 2,
'X': 1,
'Y': 2,
'Z': 1
}

def draw_letters():
pass
letter_list = []
pool_letter_count = []

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of this variable is a little confusing for me. With count in the name, it sounds like there should be numbers stored in the list rather than the letters. Maybe a name like all_letter_tiles would help the reader underdstand.


for letter, count in LETTER_POOL.items():
for _ in range(count):
pool_letter_count.append(letter)
Comment on lines +35 to +37

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code does a nice job of building up a list of all the available tiles. We could make this a little clearer by moving the logic to a helper function and giving it a good descriptive name, maybe build_tile_pile.


while len(letter_list) < 10:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line effectively has the meaning "run until we've picked 10 tiles." Some approaches to picking a tile aren't guaranteed to pick a tile each time through the loop, so phrasing the loop that way can be necessary. In your case, your loop block will always pick a tile each iteration, and so another way we think of this loop is, run the code 10 times. We could write that as

    for _ in range(10):

similarly to how you did the pile building a little earlier.

We could also consider giving a name to 10, like HAND_SIZE so that it's clear what this number represents here. For a small project like this, we probably know what all the numbers represent, but for larger systems, having unnamed numbers showing up all over the place makes the code more difficult to understand.

letter_position = random.randint(0, len(pool_letter_count) -1)
letter = pool_letter_count[letter_position]
letter_list.append(letter)
pool_letter_count.remove(letter)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works to remove one copy of the letter from the list (there could be more than one copy of the letter), but it's not necessarily the position that was picked. This doesn't affect the outcome in this case, but if we know the exact position, we can use pop rather than remove.

        pool_letter_count.pop(letter_position)

As we start to discuss code complexity (which is about how efficient it is, not how difficult to understand it is), either of these approaches is equivalent. Removing a value from a list by its value or by its position has the same performance.

But in this case, since the order of the letters in the pile isn't really important, we could remove the letter a little more efficiently by first swapping the letter to remove to the end of the list, and then popping from the end.

        last_pos = len(pool_letter_count) - 1
        pool_letter_count[last_pos], pool_letter_count[letter_position] = pool_letter_count[letter_position], pool_letter_count[last_pos]
        pool_letter_count.pop()

Though the code is longer, it's actually more efficient.


return letter_list



def uses_available_letters(word, letter_bank):
pass
letter_bank_list = letter_bank.copy()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 This is important so that we don't modify the passed in letter_bank as we remove letters.

word = word.upper()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 This is important because we have tests that show this verification should be case-insensitive.


for letter in word:
if letter in letter_bank_list:
letter_bank_list.remove(letter)
else:
return False
Comment on lines +54 to +57

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I were writing this code, I would reverse the sense of the comparison, like this

        if letter not in letter_bank_list:
            return False

        letter_bank_list.remove(letter)

The reason I would do this is that it lets us avoid indenting the "important" part of the code. Because of how Python uses indentation to control the flow of the code, we prefer to avoind indentation when we can.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The performance of both checking whether something is in a list, and the remove function depends on the length of the list we are looking at. And since we are doing these operations in a loop, it compounds the cost. We'll hear more about this as we work on Big O material, but we often want to minimize performance costs in code. One way to acheive that here would be to iterate over the hand once to build a dictionary holding a count of how many times each letter appears. As we process each letter in the word, we still need to look it up in the dictionary and check the count, but these are more efficient in a dictionary than in a list.

return True


def score_word(word):
pass
score_chart = {
"A": 1, "E": 1, "I": 1, "O": 1, "U": 1, "L":1, "N": 1, "R": 1, "S":1, "T":1,
"D": 2, "G": 2,
"B": 3, "C": 3, "M": 3, "P": 3,
"F": 4, "H": 4, "V": 4, "W": 4, "Y": 4,
"K":5 ,
"J": 8, "X": 8,
"Q": 10, "Z": 10,
}
Comment on lines +62 to +70

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Nice job storing the scores so that we can look them up quickly using the letter.

Consider moving this out of the function (similar to how you have LETTER_POOL earlier in the file, and name it in all caps to indicate this is a CONSTANT value.

Also, consider ordering the letters alphabetically. The layout here is very close to how it was shown in the README, but as a person, if I wanted to quickly find one of the letters (maybe I need to update a score), it would be helpful if the code was arranged alphabetically. It would also help us double check that we've got all the letters accounted for. Keep in mind that the primary consumer of code is not the computer, it's other coders. So laying out the data and logic in a way that's easy for others to follow is important.

score = 0
word = word.upper()

for letter in word:
letter_point_value = score_chart[letter]
score += letter_point_value
Comment on lines +74 to +76

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Great job calculating the base word score by summing the scores of the individual letters.

if len(word) >= 7:
score += 8
Comment on lines +77 to +78

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Nice handling of adding the bonus.

We can also write this to make it more clear why we're doing this. Consider giving names to the "magic numbers" (hard coded literal values that appear in code) that are used here. We could use MIN_BONUS_LEN for 7, and LENGTH_BONUS for 8. We could even move this to a helper function called something like add_bonus_points.

return score


def get_highest_word_score(word_list):
pass
highest_score = 0
highest_score_word = ""

for word in word_list:
current_score = score_word(word)
if current_score > highest_score:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 A higher score word always replaces the best word so far.

highest_score = current_score
highest_score_word = word
elif current_score == highest_score:
if len(highest_score_word) == len(word) and len(highest_score_word) == 10:
continue
elif len(word) < len(highest_score_word) and len(highest_score_word) < 10:
highest_score_word = word
elif len(highest_score_word) < 10 and len(word) == 10:
highest_score_word = word
Comment on lines +92 to +97

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This first condition isn't necessary for the logic. Conditions like that can be helpful while initially planning our logic, but if we can remove them later, it's a good idea. One way to avoid bugs is to have less code for there to be bugs in. To be clear, there's no bug in the submitted code, but any other coder that comes along and reads the code will have to try to understand why it's here. We can save them that effort by removing it.

            if len(highest_score_word) < 10 and len(word) == 10:
                highest_score_word = word
            elif len(word) < len(highest_score_word) and len(highest_score_word) < 10:
                highest_score_word = word 

Be sure to take some tie to think about why removing that condition was safe to do.

It would even be possible to combine both of the checks into a single check (combined with or), but that would make the condition much more difficult to understand. But there are potentially other ways to combine the checks that might still be understandable, so try to think about that a bit.

Another different approach is to break up the parts of the logic into different chunks rather than trying to do it all in one loop. We can think of another approach like:

  1. Find the max score (any winning word must have this score)
  2. Make a list of all the words that have the max score
  3. If we found only a single word, that's the winner
  4. Otherwise, look at each word
  5. If we find a 10 letter word, that's the winner (nothing would be able to beat it)
  6. Otherwise, find the shortest word, that's the winner

The tie-breaking logic becomes a little easier thinking of the problem this way!


return highest_score_word, highest_score



3 changes: 2 additions & 1 deletion tests/test_wave_02.py

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to avoid introducing unintentional changes to files. If we see a change show up in a file we didn't intend, we can revert a change so that it doesn't get included in a commit.

git restore tests/test_wave_02.py

Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,5 @@ def test_uses_available_letters_ignores_case():
assert uses_available_letters("bEd", letters)
assert uses_available_letters("fad", letters)
assert uses_available_letters("a", letters)
assert not uses_available_letters("aA", letters)
assert not uses_available_letters("aA", letters)

2 changes: 1 addition & 1 deletion tests/test_wave_04.py

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to avoid introducing unintentional changes to files. If we see a change show up in a file we didn't intend, we can revert a change so that it doesn't get included in a commit.

git restore tests/test_wave_04.py

Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def test_get_highest_word_score_accurate_unsorted_list():

def test_get_highest_word_tie_prefers_shorter_word():
# Arrange
words = ["MMMM", "WWW"]
words = ["MMMM", "WWW"]

# Act
best_word = get_highest_word_score(words)
Expand Down