问题
I am trying to create a word game that involves finding words in a matrix of 5x5 characters, like so:
[['a', 'a', 'u', 'r', 'a'],
['m', 'v', 'g', 'n', 'x'],
['a', 'q', 'h', 'y', 'o'],
['p', 'r', 'h', 'l', 'h'],
['v', 'h', 'y', 'o', 'j']]
which I have represented as a list of lists. "xylon" should be found but not "nylon" since this reuses the 'n'. I found a similar problem here but I do not know C.
My current solution involves creating a dictionary for each letter in the word, consisting of a list of tuples of its location on the board like so: {'letter':[(row,column),(row,column)]}. Then, for each letter in the word, I check if each of its locations on the board are compatible with the locations of the previous letter. If it is, I add that location to a new path dictionary.
This fails though for repeating letters and other cases like "nylon" which does return True. It is already rather bulky and confusing and I should probably just start over. Is there a more succinct solution I can employ?
Edits:
To clarify: A word is "in" the grid if there exists a path connecting each letter in the word in the grid. Up, down, left, right, and diagonals are allowed. 'x' is adjacent to 'y', which is adjacent to 'l', and so on. The path needs to have no particular shape as long as each letter is adjacent and no specific letter on the board is used twice. A word can have repeating letters, so "pama" would be allowed because there are multiple 'a's that can be used.
@MSW is correct, the game is boggle, though I am finding this out for the first time!
回答1:
If you want to check for word membership, your starting point of a dictionary mapping letters to positions is a good one:
letter_positions = {}
for (y, row) in enumerate(board):
for (x, letter) in enumerate(row):
letter_positions.setdefault(letter, []).append((x, y))
From there, your function should keep track of which letters have already been used to make sure it doesn't duplicate them:
def move_valid(position, last_position):
if last_position is None:
return True
return (
abs(position[0] - last_position[0]) <= 1 and
abs(position[1] - last_position[1]) <= 1
)
def find_word(word, used=None):
if word == "":
return []
if used is None:
used = []
letter, rest = word[:1], word[1:]
for position in letter_positions.get(letter) or []:
if position in used:
continue
if not move_valid(position, used and used[-1]):
continue
path = find_word(rest, used + [position])
if path is not None:
return [position] + path
return None
And for example:
>>> find_word("xylon")
[(4, 1), (3, 2), (3, 3), (4, 2), (3, 1)]
>>> find_word("bad")
None
Now, note that the runtime here will be O(not great)
because of the position in used
(used
is a list and will require an O(N)
search for each letter position) and the used + [position]
and [position] + path
(each of which will result in an allocation + copy). In practice this will be ~O(word length ^ 2)
, but could be improved to ~O(word length)
with some more sensible data structures.
来源:https://stackoverflow.com/questions/31686957/python-find-a-word-in-a-matrix-of-characters