From dac1f21e4718992b6ddf16bd2e38f7cab3231c8c Mon Sep 17 00:00:00 2001 From: CaedenPH Date: Sat, 29 Jul 2023 18:42:07 +0300 Subject: [PATCH 1/5] feat(other): Create word_search algorithm --- other/word_search.py | 395 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 395 insertions(+) create mode 100644 other/word_search.py diff --git a/other/word_search.py b/other/word_search.py new file mode 100644 index 000000000000..560bc34361bc --- /dev/null +++ b/other/word_search.py @@ -0,0 +1,395 @@ +""" +Creates a random wordsearch with eight different directions +that are best described as compass locations. + +@ +""" + + +from random import choice, randint, shuffle + +# The words to display on the word search - +# can be made dynamic by randonly selecting a certain number of +# words from a predefined word file, while ensuring the character +# count fits within the matrix size (n x m) +WORDS = ["cat", "dog", "snake", "fish"] + +WIDTH = 10 +HEIGHT = 10 + + +class WordSearch: + """ + >>> ws = WordSearch(WORDS, WIDTH, HEIGHT) + >>> ws.board # doctest: +ELLIPSIS + [[None, ..., None], ..., [None, ..., None]] + >>> ws.generate_board() + """ + + def __init__(self, words: list[str], width: int, height: int) -> None: + self.words = words + self.width = width + self.height = height + + # Board matrix holding each letter + self.board: list[list[str | None]] = [[None] * width for _ in range(height)] + + def insert_north(self, word: str, rows: list[int], cols: list[int]) -> None: + """ + >>> ws = WordSearch(WORDS, 3, 3) + >>> ws.insert_north("cat", [2], [2]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, None, 't'], + [None, None, 'a'], + [None, None, 'c']] + >>> ws.insert_north("at", [0, 1, 2], [2, 1]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, 't', 't'], + [None, 'a', 'a'], + [None, None, 'c']] + """ + word_length = len(word) + # Attempt to insert the word into each row and when successful, exit + for row in rows: + # Check if there is space above the row to fit in the word + if word_length > row + 1: + continue + + # Attempt to insert the word into each column + for col in cols: + # Only check to be made here is if there are existing letters + # above the column that will be overwritten + letters_above = [self.board[row - i][col] for i in range(word_length)] + if all(letter is None for letter in letters_above): + # Successful, insert the word north + for i in range(word_length): + self.board[row - i][col] = word[i] + return + + def insert_north_east(self, word: str, rows: list[int], cols: list[int]) -> None: + """ + >>> ws = WordSearch(WORDS, 3, 3) + >>> ws.insert_north_east("cat", [2], [0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, None, 't'], + [None, 'a', None], + ['c', None, None]] + >>> ws.insert_north_east("at", [0, 1], [2, 1, 0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, 't', 't'], + ['a', 'a', None], + ['c', None, None]] + """ + word_length = len(word) + # Attempt to insert the word into each row and when successful, exit + for row in rows: + # Check if there is space for the word above the row + if word_length > row + 1: + continue + + # Attempt to insert the word into each column + for col in cols: + # Check if there is space to the right of the word as well as above + if word_length + col > self.width: + continue + + # Check if there are existing letters + # to the right of the column that will be overwritten + letters_diagonal_left = [ + self.board[row - i][col + i] for i in range(word_length) + ] + if all(letter is None for letter in letters_diagonal_left): + # Successful, insert the word north + for i in range(word_length): + self.board[row - i][col + i] = word[i] + return + + def insert_east(self, word: str, rows: list[int], cols: list[int]) -> None: + """ + >>> ws = WordSearch(WORDS, 3, 3) + >>> ws.insert_east("cat", [1], [0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, None, None], + ['c', 'a', 't'], + [None, None, None]] + >>> ws.insert_east("at", [1, 0], [2, 1, 0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, 'a', 't'], + ['c', 'a', 't'], + [None, None, None]] + """ + word_length = len(word) + # Attempt to insert the word into each row and when successful, exit + for row in rows: + # Attempt to insert the word into each column + for col in cols: + # Check if there is space to the right of the word + if word_length + col > self.width: + continue + + # Check if there are existing letters + # to the right of the column that will be overwritten + letters_left = [self.board[row][col + i] for i in range(word_length)] + if all(letter is None for letter in letters_left): + # Successful, insert the word north + for i in range(word_length): + self.board[row][col + i] = word[i] + return + + def insert_south_east(self, word: str, rows: list[int], cols: list[int]) -> None: + """ + >>> ws = WordSearch(WORDS, 3, 3) + >>> ws.insert_south_east("cat", [0], [0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [['c', None, None], + [None, 'a', None], + [None, None, 't']] + >>> ws.insert_south_east("at", [1, 0], [2, 1, 0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [['c', None, None], + ['a', 'a', None], + [None, 't', 't']] + """ + word_length = len(word) + # Attempt to insert the word into each row and when successful, exit + for row in rows: + # Check if there is space for the word above the row + if word_length + row > self.height: + continue + + # Attempt to insert the word into each column + for col in cols: + # Check if there is space to the right of the word as well as above + if word_length + col > self.width: + continue + + # Check if there are existing letters + # to the right of the column that will be overwritten + letters_diagonal_left = [ + self.board[row + i][col + i] for i in range(word_length) + ] + if all(letter is None for letter in letters_diagonal_left): + # Successful, insert the word north + for i in range(word_length): + self.board[row + i][col + i] = word[i] + return + + def insert_south(self, word: str, rows: list[int], cols: list[int]) -> None: + """ + >>> ws = WordSearch(WORDS, 3, 3) + >>> ws.insert_south("cat", [0], [0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [['c', None, None], + ['a', None, None], + ['t', None, None]] + >>> ws.insert_south("at", [2, 1, 0], [0, 1, 2]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [['c', None, None], + ['a', 'a', None], + ['t', 't', None]] + """ + word_length = len(word) + # Attempt to insert the word into each row and when successful, exit + for row in rows: + # Check if there is space below the row to fit in the word + if word_length + row > self.height: + continue + + # Attempt to insert the word into each column + for col in cols: + # Only check to be made here is if there are existing letters + # below the column that will be overwritten + letters_below = [self.board[row + i][col] for i in range(word_length)] + if all(letter is None for letter in letters_below): + # Successful, insert the word south + for i in range(word_length): + self.board[row + i][col] = word[i] + return + + def insert_south_west(self, word: str, rows: list[int], cols: list[int]) -> None: + """ + >>> ws = WordSearch(WORDS, 3, 3) + >>> ws.insert_south_west("cat", [0], [2]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, None, 'c'], + [None, 'a', None], + ['t', None, None]] + >>> ws.insert_south_west("at", [1, 2], [2, 1, 0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, None, 'c'], + [None, 'a', 'a'], + ['t', 't', None]] + """ + word_length = len(word) + # Attempt to insert the word into each row and when successful, exit + for row in rows: + # Check if there is space for the word above the row + if word_length + row > self.height: + continue + + # Attempt to insert the word into each column + for col in cols: + # Check if there is space to the right of the word as well as above + if word_length > col + 1: + continue + + # Check if there are existing letters + # to the right of the column that will be overwritten + letters_diagonal_left = [ + self.board[row + i][col - i] for i in range(word_length) + ] + if all(letter is None for letter in letters_diagonal_left): + # Successful, insert the word north + for i in range(word_length): + self.board[row + i][col - i] = word[i] + return + + def insert_west(self, word: str, rows: list[int], cols: list[int]) -> None: + """ + >>> ws = WordSearch(WORDS, 3, 3) + >>> ws.insert_west("cat", [1], [2]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [[None, None, None], + ['t', 'a', 'c'], + [None, None, None]] + >>> ws.insert_west("at", [1, 0], [1, 2, 0]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [['t', 'a', None], + ['t', 'a', 'c'], + [None, None, None]] + """ + word_length = len(word) + # Attempt to insert the word into each row and when successful, exit + for row in rows: + # Attempt to insert the word into each column + for col in cols: + # Check if there is space to the left of the word + if word_length > col + 1: + continue + + # Check if there are existing letters + # to the left of the column that will be overwritten + letters_left = [self.board[row][col - i] for i in range(word_length)] + if all(letter is None for letter in letters_left): + # Successful, insert the word north + for i in range(word_length): + self.board[row][col - i] = word[i] + return + + def insert_north_west(self, word: str, rows: list[int], cols: list[int]) -> None: + """ + >>> ws = WordSearch(WORDS, 3, 3) + >>> ws.insert_north_west("cat", [2], [2]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [['t', None, None], + [None, 'a', None], + [None, None, 'c']] + >>> ws.insert_north_west("at", [1, 2], [0, 1]) + >>> ws.board # doctest: +NORMALIZE_WHITESPACE + [['t', None, None], + ['t', 'a', None], + [None, 'a', 'c']] + """ + word_length = len(word) + # Attempt to insert the word into each row and when successful, exit + for row in rows: + # Check if there is space for the word above the row + if word_length > row + 1: + continue + + # Attempt to insert the word into each column + for col in cols: + # Check if there is space to the right of the word as well as above + if word_length > col + 1: + continue + + # Check if there are existing letters + # to the right of the column that will be overwritten + letters_diagonal_left = [ + self.board[row - i][col - i] for i in range(word_length) + ] + if all(letter is None for letter in letters_diagonal_left): + # Successful, insert the word north + for i in range(word_length): + self.board[row - i][col - i] = word[i] + return + + def generate_board(self) -> None: + """ + Generates a board with a random direction for each word. + + >>> wt = WordSearch(WORDS, WIDTH, HEIGHT) + >>> wt.generate_board() + >>> len(list(filter(lambda word: word is not None, sum(wt.board, start=[]))) + ... ) == sum(map(lambda word: len(word), WORDS)) + True + """ + directions = ( + self.insert_north, + self.insert_north_east, + self.insert_east, + self.insert_south_east, + self.insert_south, + self.insert_south_west, + self.insert_west, + ) + for word in self.words: + # Shuffle the row order and column order that is used when brute forcing + # the insertion of the word + rows, cols = list(range(self.height)), list(range(self.width)) + shuffle(rows) + shuffle(cols) + + # Insert the word via the direction + choice(directions)(word, rows, cols) + + +def visualise_word_search( + board: list[list[str | None]] | None = None, *, add_fake_chars: bool = True +) -> None: + """ + Graphically displays the word search in the terminal. + + >>> ws = WordSearch(WORDS, 5, 5) + >>> ws.insert_north("cat", [4], [4]) + >>> visualise_word_search( + ... ws.board, add_fake_chars=False) # doctest: +NORMALIZE_WHITESPACE + # # # # # + # # # # # + # # # # t + # # # # a + # # # # c + >>> ws.insert_north_east("snake", [4], [4, 3, 2, 1, 0]) + >>> visualise_word_search( + ... ws.board, add_fake_chars=False) # doctest: +NORMALIZE_WHITESPACE + # # # # e + # # # k # + # # a # t + # n # # a + s # # # c + """ + if board is None: + word_search = WordSearch(WORDS, WIDTH, HEIGHT) + word_search.generate_board() + board = word_search.board + + result = "" + for row in range(len(board)): + for col in range(len(board[0])): + character = "#" + if (letter := board[row][col]) is not None: + character = letter + # Empty char, so add a fake char + elif add_fake_chars: + character = chr(randint(97, 122)) + result += f"{character} " + result += "\n" + print(result, end="") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + visualise_word_search() From eb2907203b7529c20563ce4f42d20e7bc47e709b Mon Sep 17 00:00:00 2001 From: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Date: Sat, 29 Jul 2023 17:23:44 +0000 Subject: [PATCH 2/5] updating DIRECTORY.md --- DIRECTORY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index 77938f45011b..46259f11b1b5 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -738,6 +738,7 @@ * [Scoring Algorithm](other/scoring_algorithm.py) * [Sdes](other/sdes.py) * [Tower Of Hanoi](other/tower_of_hanoi.py) + * [Word Search](other/word_search.py) ## Physics * [Archimedes Principle](physics/archimedes_principle.py) From c331682391815abf0940c45ed75e2a457f6a2210 Mon Sep 17 00:00:00 2001 From: CaedenPH Date: Sat, 29 Jul 2023 18:25:15 +0100 Subject: [PATCH 3/5] doc(word_search): Link to wikipedia article --- other/word_search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/other/word_search.py b/other/word_search.py index 560bc34361bc..da4f8be12285 100644 --- a/other/word_search.py +++ b/other/word_search.py @@ -2,7 +2,7 @@ Creates a random wordsearch with eight different directions that are best described as compass locations. -@ +@ https://en.wikipedia.org/wiki/Word_search """ From 767aa408c09ffed21682c051ba5f5b0ce32f099b Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Tue, 5 Sep 2023 10:32:51 +0100 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Tianyi Zheng --- other/word_search.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/other/word_search.py b/other/word_search.py index da4f8be12285..fdad706b1c86 100644 --- a/other/word_search.py +++ b/other/word_search.py @@ -66,7 +66,7 @@ def insert_north(self, word: str, rows: list[int], cols: list[int]) -> None: self.board[row - i][col] = word[i] return - def insert_north_east(self, word: str, rows: list[int], cols: list[int]) -> None: + def insert_northeast(self, word: str, rows: list[int], cols: list[int]) -> None: """ >>> ws = WordSearch(WORDS, 3, 3) >>> ws.insert_north_east("cat", [2], [0]) @@ -99,7 +99,7 @@ def insert_north_east(self, word: str, rows: list[int], cols: list[int]) -> None self.board[row - i][col + i] for i in range(word_length) ] if all(letter is None for letter in letters_diagonal_left): - # Successful, insert the word north + # Successful, insert the word northeast for i in range(word_length): self.board[row - i][col + i] = word[i] return @@ -131,12 +131,12 @@ def insert_east(self, word: str, rows: list[int], cols: list[int]) -> None: # to the right of the column that will be overwritten letters_left = [self.board[row][col + i] for i in range(word_length)] if all(letter is None for letter in letters_left): - # Successful, insert the word north + # Successful, insert the word east for i in range(word_length): self.board[row][col + i] = word[i] return - def insert_south_east(self, word: str, rows: list[int], cols: list[int]) -> None: + def insert_southeast(self, word: str, rows: list[int], cols: list[int]) -> None: """ >>> ws = WordSearch(WORDS, 3, 3) >>> ws.insert_south_east("cat", [0], [0]) @@ -153,13 +153,13 @@ def insert_south_east(self, word: str, rows: list[int], cols: list[int]) -> None word_length = len(word) # Attempt to insert the word into each row and when successful, exit for row in rows: - # Check if there is space for the word above the row + # Check if there is space for the word below the row if word_length + row > self.height: continue # Attempt to insert the word into each column for col in cols: - # Check if there is space to the right of the word as well as above + # Check if there is space to the right of the word as well as below if word_length + col > self.width: continue @@ -169,7 +169,7 @@ def insert_south_east(self, word: str, rows: list[int], cols: list[int]) -> None self.board[row + i][col + i] for i in range(word_length) ] if all(letter is None for letter in letters_diagonal_left): - # Successful, insert the word north + # Successful, insert the word southeast for i in range(word_length): self.board[row + i][col + i] = word[i] return @@ -206,7 +206,7 @@ def insert_south(self, word: str, rows: list[int], cols: list[int]) -> None: self.board[row + i][col] = word[i] return - def insert_south_west(self, word: str, rows: list[int], cols: list[int]) -> None: + def insert_southwest(self, word: str, rows: list[int], cols: list[int]) -> None: """ >>> ws = WordSearch(WORDS, 3, 3) >>> ws.insert_south_west("cat", [0], [2]) @@ -223,13 +223,13 @@ def insert_south_west(self, word: str, rows: list[int], cols: list[int]) -> None word_length = len(word) # Attempt to insert the word into each row and when successful, exit for row in rows: - # Check if there is space for the word above the row + # Check if there is space for the word below the row if word_length + row > self.height: continue # Attempt to insert the word into each column for col in cols: - # Check if there is space to the right of the word as well as above + # Check if there is space to the left of the word as well as below if word_length > col + 1: continue @@ -239,7 +239,7 @@ def insert_south_west(self, word: str, rows: list[int], cols: list[int]) -> None self.board[row + i][col - i] for i in range(word_length) ] if all(letter is None for letter in letters_diagonal_left): - # Successful, insert the word north + # Successful, insert the word southwest for i in range(word_length): self.board[row + i][col - i] = word[i] return @@ -271,12 +271,12 @@ def insert_west(self, word: str, rows: list[int], cols: list[int]) -> None: # to the left of the column that will be overwritten letters_left = [self.board[row][col - i] for i in range(word_length)] if all(letter is None for letter in letters_left): - # Successful, insert the word north + # Successful, insert the word west for i in range(word_length): self.board[row][col - i] = word[i] return - def insert_north_west(self, word: str, rows: list[int], cols: list[int]) -> None: + def insert_northwest(self, word: str, rows: list[int], cols: list[int]) -> None: """ >>> ws = WordSearch(WORDS, 3, 3) >>> ws.insert_north_west("cat", [2], [2]) @@ -299,7 +299,7 @@ def insert_north_west(self, word: str, rows: list[int], cols: list[int]) -> None # Attempt to insert the word into each column for col in cols: - # Check if there is space to the right of the word as well as above + # Check if there is space to the left of the word as well as above if word_length > col + 1: continue @@ -309,7 +309,7 @@ def insert_north_west(self, word: str, rows: list[int], cols: list[int]) -> None self.board[row - i][col - i] for i in range(word_length) ] if all(letter is None for letter in letters_diagonal_left): - # Successful, insert the word north + # Successful, insert the word northwest for i in range(word_length): self.board[row - i][col - i] = word[i] return From d3b8d646d31094280e44835859b63bd62a97b26c Mon Sep 17 00:00:00 2001 From: Tianyi Zheng Date: Sat, 30 Sep 2023 00:53:42 -0400 Subject: [PATCH 5/5] Update word_search.py --- other/word_search.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/other/word_search.py b/other/word_search.py index fdad706b1c86..a4796e220c7c 100644 --- a/other/word_search.py +++ b/other/word_search.py @@ -69,12 +69,12 @@ def insert_north(self, word: str, rows: list[int], cols: list[int]) -> None: def insert_northeast(self, word: str, rows: list[int], cols: list[int]) -> None: """ >>> ws = WordSearch(WORDS, 3, 3) - >>> ws.insert_north_east("cat", [2], [0]) + >>> ws.insert_northeast("cat", [2], [0]) >>> ws.board # doctest: +NORMALIZE_WHITESPACE [[None, None, 't'], [None, 'a', None], ['c', None, None]] - >>> ws.insert_north_east("at", [0, 1], [2, 1, 0]) + >>> ws.insert_northeast("at", [0, 1], [2, 1, 0]) >>> ws.board # doctest: +NORMALIZE_WHITESPACE [[None, 't', 't'], ['a', 'a', None], @@ -139,12 +139,12 @@ def insert_east(self, word: str, rows: list[int], cols: list[int]) -> None: def insert_southeast(self, word: str, rows: list[int], cols: list[int]) -> None: """ >>> ws = WordSearch(WORDS, 3, 3) - >>> ws.insert_south_east("cat", [0], [0]) + >>> ws.insert_southeast("cat", [0], [0]) >>> ws.board # doctest: +NORMALIZE_WHITESPACE [['c', None, None], [None, 'a', None], [None, None, 't']] - >>> ws.insert_south_east("at", [1, 0], [2, 1, 0]) + >>> ws.insert_southeast("at", [1, 0], [2, 1, 0]) >>> ws.board # doctest: +NORMALIZE_WHITESPACE [['c', None, None], ['a', 'a', None], @@ -209,12 +209,12 @@ def insert_south(self, word: str, rows: list[int], cols: list[int]) -> None: def insert_southwest(self, word: str, rows: list[int], cols: list[int]) -> None: """ >>> ws = WordSearch(WORDS, 3, 3) - >>> ws.insert_south_west("cat", [0], [2]) + >>> ws.insert_southwest("cat", [0], [2]) >>> ws.board # doctest: +NORMALIZE_WHITESPACE [[None, None, 'c'], [None, 'a', None], ['t', None, None]] - >>> ws.insert_south_west("at", [1, 2], [2, 1, 0]) + >>> ws.insert_southwest("at", [1, 2], [2, 1, 0]) >>> ws.board # doctest: +NORMALIZE_WHITESPACE [[None, None, 'c'], [None, 'a', 'a'], @@ -279,12 +279,12 @@ def insert_west(self, word: str, rows: list[int], cols: list[int]) -> None: def insert_northwest(self, word: str, rows: list[int], cols: list[int]) -> None: """ >>> ws = WordSearch(WORDS, 3, 3) - >>> ws.insert_north_west("cat", [2], [2]) + >>> ws.insert_northwest("cat", [2], [2]) >>> ws.board # doctest: +NORMALIZE_WHITESPACE [['t', None, None], [None, 'a', None], [None, None, 'c']] - >>> ws.insert_north_west("at", [1, 2], [0, 1]) + >>> ws.insert_northwest("at", [1, 2], [0, 1]) >>> ws.board # doctest: +NORMALIZE_WHITESPACE [['t', None, None], ['t', 'a', None], @@ -326,12 +326,13 @@ def generate_board(self) -> None: """ directions = ( self.insert_north, - self.insert_north_east, + self.insert_northeast, self.insert_east, - self.insert_south_east, + self.insert_southeast, self.insert_south, - self.insert_south_west, + self.insert_southwest, self.insert_west, + self.insert_northwest, ) for word in self.words: # Shuffle the row order and column order that is used when brute forcing @@ -359,7 +360,7 @@ def visualise_word_search( # # # # t # # # # a # # # # c - >>> ws.insert_north_east("snake", [4], [4, 3, 2, 1, 0]) + >>> ws.insert_northeast("snake", [4], [4, 3, 2, 1, 0]) >>> visualise_word_search( ... ws.board, add_fake_chars=False) # doctest: +NORMALIZE_WHITESPACE # # # # e