Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b1901b6
wave 1
jaekerrclayton Mar 28, 2022
a56bd5e
"Renamed variable letters to letter_bank."
hopeolaide Mar 28, 2022
6fc4d00
updated dictionary iteration from .items to .keys()
jaekerrclayton Mar 28, 2022
cc2f561
wave 2, doesn't pass last test(something to do with case, still)
jaekerrclayton Mar 29, 2022
242cebc
"Docstrings updated for reference."
hopeolaide Mar 29, 2022
d1ce816
"Troubleshooting"
hopeolaide Mar 29, 2022
9402c15
Back up file while troubleshooting.
hopeolaide Mar 29, 2022
81acaa6
"Current functions and docstrings up to date."
hopeolaide Mar 29, 2022
acc29cb
Deleted back_up.py file. No longer needed since game.py has been rest…
hopeolaide Mar 29, 2022
d501ef9
Wave 2 complete.
hopeolaide Mar 29, 2022
5e99672
Reformatted function. Commented out uses of Counter.
hopeolaide Mar 29, 2022
e2c490d
"Wave 3 nearly complete. 1 test remaining. "
hopeolaide Mar 29, 2022
ee58643
Refactored Wave 2.
hopeolaide Mar 29, 2022
2cb1aa5
update of wave 3 & some crazy notes for wave 4
jaekerrclayton Mar 29, 2022
ba451df
merges resolved, update to wave 3 & some wave 4 crazy code
jaekerrclayton Mar 29, 2022
1a79f9e
wave 4 notes, slightly less unhinged
jaekerrclayton Mar 29, 2022
0c0d243
wave 4, for loops & length
jaekerrclayton Mar 29, 2022
86d94ef
updated with pseudocode.
hopeolaide Mar 29, 2022
efe2427
1 test passed in Wave 4.
hopeolaide Mar 29, 2022
d3b140f
probably no changes?
jaekerrclayton Mar 29, 2022
f40dfdf
Merge branch 'master' of https://github.com/hopeolaide/adagrams-py
jaekerrclayton Mar 29, 2022
6463d4c
idk, tried something out -- we were counting the frequency of the wro…
jaekerrclayton Mar 30, 2022
59de25e
added a return after len 10 & more stuff is passing git add . but sti…
jaekerrclayton Mar 30, 2022
b018f52
"Revising Wave 4. Notes and code in progress."
hopeolaide Mar 31, 2022
c3964e5
Some updates. Still in progress.
hopeolaide Mar 31, 2022
b482dba
"Updates to first part of Wave 4 / in progress."
hopeolaide Mar 31, 2022
60de52c
"Wave 4: 6 out of 7 tests passed."
hopeolaide Mar 31, 2022
d630b7e
'Tidying up Wave 4. 1 test left.'
hopeolaide Mar 31, 2022
41457d8
"Wave 4 refactored with ternary operator."
hopeolaide Apr 1, 2022
d1a1fe9
"Wave 4 complete."
hopeolaide Apr 1, 2022
8781459
"game.py tidied and reafactored."
hopeolaide Apr 1, 2022
a56d56f
"Waves 1 to 4 refactored and tidied"
hopeolaide Apr 1, 2022
6e2fad6
Extra file for alternative code. Add here.
hopeolaide Apr 1, 2022
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
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,16 @@ $ pip install -r requirements.txt
Summary of one-time project setup:

One person:
- [ ] Fork the project respository
- [ ] Invite team members to the respository
- [x ] Fork the project respository
- [ x] Invite team members to the respository

All team members:
- [ ] `cd` into your `projects` folder
- [ ] Clone the project onto your machine
- [ ] `cd` into the `adagrams-py` folder
- [ ] Create the virtual environment `venv`
- [ ] Activate the virtual environment `venv`
- [ ] Install the dependencies with `pip`
- [x ] `cd` into your `projects` folder
- [ x] Clone the project onto your machine
- [ x] `cd` into the `adagrams-py` folder
- [ x] Create the virtual environment `venv`
- [ x] Activate the virtual environment `venv`
- [ x] Install the dependencies with `pip`

## Project Development Workflow

Expand Down
159 changes: 155 additions & 4 deletions adagrams/game.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,162 @@

import random
import copy

LETTER_POOL = {

Choose a reason for hiding this comment

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

👍 I do like having these giant data structures not cluttering up a function. Naming the variable like a constant helps us communicate that we should be careful not to do anything that would alter the data.

'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
}
SCORE_CHART = {

Choose a reason for hiding this comment

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

Nice job restructuring this data from how it was presented in the project description. Organizing the score chart this way will help us take advantage of a piece of data that we have (a letter in a word) to get its score very efficiently with a dictionary lookup.

'A': 1,
'B': 3,
'C': 3,
'D': 2,
'E': 1,
'F': 4,
'G': 2,
'H': 4,
'I': 1,
'J': 8,
'K': 5,
'L': 1,
'M': 3,
'N': 1,
'O': 1,
'P': 3,
'Q': 10,
'R': 1,
'S': 1,
'T': 1,
'U': 1,
'V': 4,
'W': 4,
'X': 8,
'Y': 4,
'Z': 10

}
def draw_letters():
pass
user_pool = copy.deepcopy(LETTER_POOL)

Choose a reason for hiding this comment

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

LETTER_POOL is a dictionary where the keys are immutable strings (they must be immutable to be keys) and the values are ins, which are also immutable. While your approach does require making a copy, since you are decrementing the counts, since everything in the dict is immutable, a shallow copy would be sufficient.

letter_bank = []
while len(letter_bank)< 10:
draw = random.choice(list(user_pool))

Choose a reason for hiding this comment

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

This approach of choosing a letter, and then only adding it if there are any of that letter left, gives a slightly different distribution of letters than if we were to, for example, create a single list where each letter appeared in the list the number of tiles there were for that letter. Consider our first pick. With your approach, we'd have a 1 out of 26 (3.8%) chance of picking a Z, while if we built a tile list, we would have a 1 out of 98 (1%) chance of picking Z. You still are correctly preventing the picking of impossible hands, but it would be a little harder to make actual words using letter picked this way than from a single tile list, where more common letters appear more frequently, improving their likelihood of being picked.

for letter in user_pool.keys():
if user_pool[letter] == 0:
continue
if letter == draw:
letter_bank.append(draw)
user_pool[letter] -= 1
Comment on lines +67 to +72

Choose a reason for hiding this comment

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

Since we just picked draw (a letter which is a key in the dictionary) above, we don't need to iterate to find it's available count. We can use the drawn letter itself to look this up.

        if user_pool[draw] > 0: 
            letter_bank.append(draw) 
            user_pool[draw] -= 1 


return letter_bank

from collections import Counter

Choose a reason for hiding this comment

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

Since you didn't use Counter, be sure to comment out/remove this import.


def uses_available_letters(word, letter_bank):
pass
input_word = word.upper()
print(f"This is the value of {input_word=}")

Choose a reason for hiding this comment

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

Please remove debugging print statements before submitting.

#We initially explored using the Counter approach but didnt go with it.
# We left it here (commented out) to be able to discuss in our 1:1s with instructors
# word_counter = Counter(input_word)
# # letter_bank_counter = Counter(letter_bank)

for letter in input_word:
if letter not in letter_bank:
return False
elif letter in letter_bank:

Choose a reason for hiding this comment

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

A value is either in a list, or it's not. So if the if fails to find a letter in the bank, we don't need to check whether it's there. We know that it is. So we can just use else: here.

But, we actually don't even need the if check at all. count will simply return 0 if something isn't in a list. And count and in are both O(n) operations, so this could be simplified slightly to

    for letter in input_word:
        word_letter_frequency = input_word.count(letter)
        letter_bank_frequency = letter_bank.count(letter)
        if word_letter_frequency > letter_bank_frequency:
            return False       
    return True

word_letter_frequency = input_word.count(letter)
letter_bank_frequency = letter_bank.count(letter)
if word_letter_frequency > letter_bank_frequency:
return False
Comment on lines +86 to +93

Choose a reason for hiding this comment

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

This approach makes good sense. For each letter in the word, count the number of times it appears in the word, count the number of times it appears in the bank, and if there are more occurrences in the word than the bank, we know this word isn't valid given the bank. However, iterating over the word is O(n), as is counting the occurrences of a letter in a word. So this overall approach is O(n^2). Additionally, we are potentially doing double work if a word contains multiple copies of a single letter. The word "letter" itself has 2 ls and 2 es, so each would be counted twice.

What if we could count ALL the letters in the word in a single pass, and ALL the letters in the bank in a single pass, and then make sure that for each letter in the word, its word count must be lower than its bank count. None of these loops are nested, so the overall approach would be O(n).

So how might we do that counting and comparing? You were thinking about using Counter, and while that could help here, we can also do this counting manually. See what you can come up with!

return True

# Remnant from initial Counter approach:
# for (k, v), (k2, v2) in zip(word_counter.items(), letter_bank_counter.items()):
# zip(word_counter.items(), letter_bank_counter.items())
# if k == k2 and v > v2:
# return False
#
# return True

def score_word(word):

Choose a reason for hiding this comment

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

👍
Great approach. By storing the score data indexed by letter, you are able to achieve a very efficient linear scoring algorithm here!

pass
"""
Wave 3: score_word
Returns the score of a given word as defined by the Adagrams game.

The number of points of each letter is summed up to represent the total score of word
Each letter's point value is described in the table below
If the length of the word is 7, 8, 9, or 10, then the word gets an additional 8 points

"""
input_word = word.upper()
word_score = 0
word_length = len(input_word)

if input_word == "":
return 0

for letter in input_word:
word_score += SCORE_CHART[letter]

if word_length >= 7:
word_score += 8

return word_score


def get_highest_word_score(word_list):
pass
best_word_list = []
best_score_list =[]

# Translating word list and scores into a dictionary and finding highest word score in dictionary:
for word in word_list:

Choose a reason for hiding this comment

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

We don't need this loop. This will end up re-building the whole word dictionary and finding the max score from those values as many times as there are words in the word_list. Remove this line and unindent the body.

word_score_dict = {word: score_word(word) for word in word_list} #Dictionary comprehension
highest_word_score = max(word_score_dict.values())

# Max only returns the first match so we need to check which (if any) other values == highest score
# Once clear append the keys for those highest_scores values to the best_word_list.
for word in word_score_dict:
if word_score_dict[word] == highest_word_score:
best_word_list.append(word)
best_score_list.append(word_score_dict[word])

Choose a reason for hiding this comment

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

Do we need to track this? After all, every word here should have a score equal to highest_word_score.


#Conditional check to see if length is more than 1 then apply tie-breaker logic
# (see helper function further below)
best_word = best_word_list[0] if len(best_word_list) == 1 else tie_breaker(best_word_list)

Choose a reason for hiding this comment

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

👍 Tie breaker helper function! And nice use of a ternary expression.


return (best_word, highest_word_score)

#Tie-breaker logic
def tie_breaker(best_word_list):

Choose a reason for hiding this comment

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

👍

As soon as we find a word with ten letters, it wins. Otherwise we take the shortest.

for word in best_word_list:
if len(word) == 10:
return word

return min(best_word_list, key = len)

Choose a reason for hiding this comment

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

This will find the "minimum" word by comparing the results of calling len on each word in the list. Make sure you feel comfortable with how this works (like the idea of passing functions to other functions) before using this. Do you feel like you could write a similar function yourself?





153 changes: 153 additions & 0 deletions adagrams/old_code_review.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
"""WAVE 4 Old Code Review """

""" Everything below is from the previous version that got us to 4 passed tests. """

Choose a reason for hiding this comment

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

The main issue I see is not clearing out the previous max words when you find a new max score. But otherwise I think this approach all looks good!

# for word in word_list:
# # word_length= len(word)
# word_score = score_word(word)
# word_list_scores.append(word_score)
# highest_score = max(word_list_scores)
# if word_score == highest_score:
# best_word.append(word)
Comment on lines +9 to +10

Choose a reason for hiding this comment

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

I think one thing you'd want to do is keep track of what the last maximum score was. Then, if you find a new maximum score, you could clear out the words that were previously in the best word list.



"""
In the case of tie in scores, use these tie-breaking rules:
prefer the word with the fewest letters...
...unless one word has 10 letters.
"""
# if len(best_word) == 1:
# word = best_word[0]
# highest_score_tuple = (word, highest_score)
# return highest_score_tuple


"""
If the top score is tied between multiple words and one is 10 letters long,
choose the one with 10 letters over the one with fewer tiles
If the there are multiple words that are the same score and the same length,
pick the first one in the supplied list"""

# for word in best_word:

# if len(word) == 10:
# highest_score_tuple = (word, highest_score)
# return highest_score_tuple
# else:
# ### this isn't the right way to do it!
# ## but thought is that we have a
# shortest_word = min(word_list, key=lambda word: len(word))
# highest_score_tuple = (shortest_word, highest_score)

# return highest_score_tuple







"""
Super old Wave 4 code. Ignore below """



# for word_score in word_list_scores:
# word_score_frequency = word_list_scores.count(word_score)
# highest_score = max(word_list_scores)
# print(f"THIS IS THE WORD SCORE FREQUENCY{word_score_frequency=}")
# if word_score == highest_score:
# highest_score = word_score
# highest_score_tuple = (word, highest_score)
# if word_score_frequency > 1:
# if word_length == 10:

# highest_score_tuple = (word, highest_score)
# else:
# shortest_word = min(word_list, key=lambda word: len(word))
# highest_score_tuple = (shortest_word, highest_score)

# highest_score.append(word_score)
# if word_score > highest_word_score:



# score = score_word(word)
# score_list.append(score)
# highest_score = max(score_list)
# for score in score_list:
# if score == highest_score:
# highest_scores.append(score)
# print(f' THIS SHOULD BE A LIST OF TUPLES{highest_scores=}')




# # score_list.append(score)
# #[a word, 3 another,6,10,4,10]
# highest_score = highest_score.append(word, score)
# best_score = highest_score[0]
# for word_info in highest_score:
# if word_info[1] > best_score[1]:



# max_score = max(score_list)
# if score == max_score:
# best_score.append(word, score)
# if len(best_score) > 1:


# user_scores = (word, score)
# score_list.append(user_scores)

# max_score = max(score_list)
# for user in score_list:
# if user[1] > max_score:
# user[1] = max_score


# def get_highest_scoring_words(word_list):
# best_score = 0
# score_list = []
# best_words = []
# word_lengths = []
# shortest_words = []

# for word in word_list:
# word_lengths.append(len(word))
# smallest_length = min(word_lengths)
# if len(word) == smallest_length:
# shortest_words.append(word)


# for word in word_list:
# score = score_word(word)
# score_list.append(score)
# best_score = max(score_list)
# if score == best_score:
# best_words.append(word)

# return tuple(best_words, best_score, shortest_words[0])




# def get_highest_word_score(word_list):
# calculate_score = get_highest_scoring_words(word_list)
# score = calculate_score[1]
# winning_words = calculate_score[0]
# shortest_word = calculate_score[2]
# winning_word = ''

# if len(winning_words) == 1:
# winning_word = winning_words[0]

# for word in winning_words:
# if len(word) == 10:
# winning_word = word
# break
# else:
# winning_word = shortest_word

# return tuple(winning_word, score)