Skip to content

Commit

Permalink
Merge pull request pybites#657 from ronaldokun/PCC02
Browse files Browse the repository at this point in the history
Pcc02
  • Loading branch information
schattencheg authored Nov 7, 2019
2 parents 49fb6a6 + 97e1b6f commit 51f88c6
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 13 deletions.
1 change: 1 addition & 0 deletions 01/ronaldokun/data.py
1 change: 1 addition & 0 deletions 01/ronaldokun/dictionary.txt
28 changes: 28 additions & 0 deletions 01/ronaldokun/test_wordvalue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import unittest

from data import DICTIONARY, LETTER_SCORES
from wordvalue import load_words, calc_word_value, max_word_value

TEST_WORDS = ('bob', 'julian', 'pybites', 'quit', 'barbeque')

class TestWordValue(unittest.TestCase):

def test_load_words(self):
words = load_words()
self.assertEqual(len(words), 235886)
self.assertEqual(words[0], 'A')
self.assertEqual(words[-1], 'Zyzzogeton')
self.assertNotIn(' ', ''.join(words))

def test_calc_word_value(self):
self.assertEqual(calc_word_value('bob'), 7)
self.assertEqual(calc_word_value('JuliaN'), 13)
self.assertEqual(calc_word_value('PyBites'), 14)
self.assertEqual(calc_word_value('benzalphenylhydrazone'), 56)

def test_max_word_value(self):
self.assertEqual(max_word_value(TEST_WORDS), 'barbeque')
self.assertEqual(max_word_value(), 'benzalphenylhydrazone')

if __name__ == "__main__":
unittest.main()
54 changes: 41 additions & 13 deletions 02/data.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,58 @@
from collections import namedtuple

Letter = namedtuple('Letter', 'name amount value')
Letter = namedtuple("Letter", "name amount value")


def _load_words():
with open('dictionary.txt') as f:
with open("dictionary.txt") as f:
return set([word.strip().lower() for word in f.read().split()])


DICTIONARY = _load_words()
assert len(DICTIONARY) == 234371


# generated with https://github.com/pybites/blog_code/blob/master/BeautifulSoup/scrabble_distribution.py
distribution = [Letter(name='A', amount='9', value='1'), Letter(name='B', amount='2', value='3'), Letter(name='C', amount='2', value='3'), Letter(name='D', amount='4', value='2'), Letter(name='E', amount='12', value='1'), Letter(name='F', amount='2', value='4'), Letter(name='G', amount='3', value='2'), Letter(name='H', amount='2', value='4'), Letter(name='I', amount='9', value='1'), Letter(name='J', amount='1', value='8'), Letter(name='K', amount='1', value='5'), Letter(name='L', amount='4', value='1'), Letter(name='M', amount='2', value='3'), Letter(name='N', amount='6', value='1'), Letter(name='O', amount='8', value='1'), Letter(name='P', amount='2', value='3'), Letter(name='Q', amount='1', value='10'), Letter(name='R', amount='6', value='1'), Letter(name='S', amount='4', value='1'), Letter(name='T', amount='6', value='1'), Letter(name='U', amount='4', value='1'), Letter(name='V', amount='2', value='4'), Letter(name='W', amount='2', value='4'), Letter(name='X', amount='1', value='8'), Letter(name='Y', amount='2', value='4'), Letter(name='Z', amount='1', value='10')]

POUCH = list(''.join(
list(letter.name * int(letter.amount)
for letter in distribution))
)
distribution = [
Letter(name="A", amount="9", value="1"),
Letter(name="B", amount="2", value="3"),
Letter(name="C", amount="2", value="3"),
Letter(name="D", amount="4", value="2"),
Letter(name="E", amount="12", value="1"),
Letter(name="F", amount="2", value="4"),
Letter(name="G", amount="3", value="2"),
Letter(name="H", amount="2", value="4"),
Letter(name="I", amount="9", value="1"),
Letter(name="J", amount="1", value="8"),
Letter(name="K", amount="1", value="5"),
Letter(name="L", amount="4", value="1"),
Letter(name="M", amount="2", value="3"),
Letter(name="N", amount="6", value="1"),
Letter(name="O", amount="8", value="1"),
Letter(name="P", amount="2", value="3"),
Letter(name="Q", amount="1", value="10"),
Letter(name="R", amount="6", value="1"),
Letter(name="S", amount="4", value="1"),
Letter(name="T", amount="6", value="1"),
Letter(name="U", amount="4", value="1"),
Letter(name="V", amount="2", value="4"),
Letter(name="W", amount="2", value="4"),
Letter(name="X", amount="1", value="8"),
Letter(name="Y", amount="2", value="4"),
Letter(name="Z", amount="1", value="10"),
]

POUCH = list("".join(list(letter.name * int(letter.amount) for letter in distribution)))
assert len(POUCH) == 98 # no wildcards in this simple game


LETTER_SCORES = dict(zip(
LETTER_SCORES = dict(
zip(
[letter.name for letter in distribution],
[int(letter.value) for letter in distribution]
))
[int(letter.value) for letter in distribution],
)
)

assert LETTER_SCORES['A'] == 1
assert LETTER_SCORES['Q'] == 10
assert LETTER_SCORES["A"] == 1
assert LETTER_SCORES["Q"] == 10
assert sum(LETTER_SCORES.values()) == 87
99 changes: 99 additions & 0 deletions 02/ronaldokun/game.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#!python3
# Code Challenge 02 - Word Values Part II - a simple game
# http://pybit.es/codechallenge02.html
"""
Author: Ronaldo S.A.Batista
Email: [email protected]
Github: @ronaldokun
"""
from itertools import permutations
from random import choices

from data import DICTIONARY, LETTER_SCORES, POUCH

NUM_LETTERS = 7

def draw_letters():
"""Pick NUM_LETTERS letters randomly. Hint: use stdlib random"""
return choices(POUCH, k=NUM_LETTERS)
# re-use from challenge 01
def calc_word_value(word):
"""Calc a given word value based on Scrabble LETTER_SCORES mapping"""
return sum(LETTER_SCORES.get(char.upper(), 0) for char in word)


# re-use from challenge 01
def max_word_value(words):
"""Calc the max value of a collection of words"""
return max(words, key=calc_word_value)


def _validation(word: str, draw: list) -> bool:
"""Check if word can be formed from letters in draw
:param word: word to be checked
:param draw: set of letters to form a word
:return: True if word can be formed from draw, False otherwise
"""
copy = draw[:]
for c in word:
try:
copy.remove(c)
except ValueError as e:
raise ValueError(f"The word {word} cannot be formed from the {draw}") from e
if not in_dict(word):
raise ValueError(f"The word {word} is not a valid one")

return True


def in_dict(word: str) -> bool:
""" Check if word in in DICTIONARY
:param word: string to check
:return: True if word is found, False otherwise
"""
return "".join(word).lower() in DICTIONARY

def input_word(draw):
"""Ask player for a word and validate against draw.
Use _validation(word, draw) helper."""
word = input("Insert a valid word: ").upper()
if _validation(word, draw):
return word

# Below 2 functions pass through the same 'draw' argument (smell?).
# Maybe you want to abstract this into a class?
# get_possible_dict_words and _get_permutations_draw would be instance methods.
# 'draw' would be set in the class constructor (__init__).
def get_possible_dict_words(draw):
"""Get all possible words from draw which are valid dictionary words.
Use the _get_permutations_draw helper and DICTIONARY constant"""
return list(filter(in_dict, _get_permutations_draw(draw)))


def _get_permutations_draw(draw):
"""Helper for get_possible_dict_words to get all permutations of draw letters.
Hint: use itertools.permutations"""
return (w for k in range(1, NUM_LETTERS+1) for w in permutations(draw, k))


def main():

draw = draw_letters()
print(f"Letters drawn: {draw}")

word = input_word(draw)

word_score = calc_word_value(word)
print(f"Word Chosen: {word} (Value: {word_score})")

possible_words = get_possible_dict_words(draw)

max_word = max_word_value(possible_words)
val_max_word = calc_word_value(max_word)

print(f"The Optimal Word for this Draw is {''.join(max_word)} (Value: {val_max_word})")
print(f"Your Score (player_score / optimal_score) : {word_score / val_max_word * 100: .2f}")


if __name__ == "__main__":
main()

0 comments on commit 51f88c6

Please sign in to comment.