@@ -6,29 +6,28 @@ def is_valid(
66) -> bool :
77 """
88 Check if a word can be placed at the given position.
9+ A cell is valid if it is empty or already contains the correct letter
10+ (enabling crossing/intersection between words).
911
10- >>> puzzle = [
11- ... ['', '', '', ''],
12- ... ['', '', '', ''],
13- ... ['', '', '', ''],
14- ... ['', '', '', '']
15- ... ]
12+ >>> puzzle = [['', '', '', ''], ['', '', '', ''],
13+ ... ['', '', '', ''], ['', '', '', '']]
1614 >>> is_valid(puzzle, 'word', 0, 0, True)
1715 True
18- >>> puzzle = [
19- ... ['', '', '', ''],
20- ... ['', '', '', ''],
21- ... ['', '', '', ''],
22- ... ['', '', '', '']
23- ... ]
2416 >>> is_valid(puzzle, 'word', 0, 0, False)
2517 True
18+ >>> puzzle2 = [['w', '', ''], ['o', '', ''], ['r', '', ''], ['d', '', '']]
19+ >>> is_valid(puzzle2, 'word', 0, 0, True)
20+ True
21+ >>> is_valid(puzzle2, 'cat', 0, 0, True)
22+ False
2623 """
27- for i in range (len (word )):
28- if vertical :
29- if row + i >= len (puzzle ) or puzzle [row + i ][col ] != "" :
30- return False
31- elif col + i >= len (puzzle [0 ]) or puzzle [row ][col + i ] != "" :
24+ rows , cols = len (puzzle ), len (puzzle [0 ])
25+ for i , ch in enumerate (word ):
26+ r , c = (row + i , col ) if vertical else (row , col + i )
27+ if r >= rows or c >= cols :
28+ return False
29+ cell = puzzle [r ][c ]
30+ if cell != "" and cell != ch :
3231 return False
3332 return True
3433
@@ -37,95 +36,83 @@ def place_word(
3736 puzzle : list [list [str ]], word : str , row : int , col : int , vertical : bool
3837) -> None :
3938 """
40- Place a word at the given position.
41-
42- >>> puzzle = [
43- ... ['', '', '', ''],
44- ... ['', '', '', ''],
45- ... ['', '', '', ''],
46- ... ['', '', '', '']
47- ... ]
39+ Place a word at the given position in the puzzle.
40+
41+ >>> puzzle = [['', '', '', ''], ['', '', '', ''],
42+ ... ['', '', '', ''], ['', '', '', '']]
4843 >>> place_word(puzzle, 'word', 0, 0, True)
4944 >>> puzzle
5045 [['w', '', '', ''], ['o', '', '', ''], ['r', '', '', ''], ['d', '', '', '']]
5146 """
52- for i , char in enumerate (word ):
47+ for i , ch in enumerate (word ):
5348 if vertical :
54- puzzle [row + i ][col ] = char
49+ puzzle [row + i ][col ] = ch
5550 else :
56- puzzle [row ][col + i ] = char
51+ puzzle [row ][col + i ] = ch
5752
5853
5954def remove_word (
60- puzzle : list [list [str ]], word : str , row : int , col : int , vertical : bool
55+ puzzle : list [list [str ]], word : str , row : int , col : int , vertical : bool ,
56+ snapshot : list [list [str ]],
6157) -> None :
6258 """
63- Remove a word from the given position.
64-
65- >>> puzzle = [
66- ... ['w', '', '', ''],
67- ... ['o', '', '', ''],
68- ... ['r', '', '', ''],
69- ... ['d', '', '', '']
70- ... ]
71- >>> remove_word(puzzle, 'word', 0, 0, True)
59+ Remove a word from the puzzle, restoring only cells that were empty
60+ before placement. Cells shared with crossing words are preserved.
61+
62+ >>> puzzle = [['w', 'o', 'r', 'd'], ['', '', '', ''],
63+ ... ['', '', '', ''], ['', '', '', '']]
64+ >>> snap = [['', 'o', 'r', 'd'], ['', '', '', ''],
65+ ... ['', '', '', ''], ['', '', '', '']]
66+ >>> remove_word(puzzle, 'word', 0, 0, False, snap)
7267 >>> puzzle
73- [['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']]
68+ [['', 'o ', 'r ', 'd '], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']]
7469 """
7570 for i in range (len (word )):
76- if vertical :
77- puzzle [row + i ][col ] = ""
78- else :
79- puzzle [row ][col + i ] = ""
71+ r , c = (row + i , col ) if vertical else (row , col + i )
72+ if snapshot [r ][c ] == "" :
73+ puzzle [r ][c ] = ""
8074
8175
8276def solve_crossword (puzzle : list [list [str ]], words : list [str ]) -> bool :
8377 """
8478 Solve the crossword puzzle using backtracking.
79+ Words are tried longest-first to prune the search space early.
80+ Intersections between words (shared letters) are supported.
8581
86- >>> puzzle = [
87- ... ['', '', '', ''],
88- ... ['', '', '', ''],
89- ... ['', '', '', ''],
90- ... ['', '', '', '']
91- ... ]
92-
93- >>> words = ['word', 'four', 'more', 'last']
94- >>> solve_crossword(puzzle, words)
82+ >>> puzzle = [['', '', '', ''], ['', '', '', ''],
83+ ... ['', '', '', ''], ['', '', '', '']]
84+ >>> solve_crossword(puzzle, ['word', 'four', 'more', 'last'])
9585 True
96- >>> puzzle = [
97- ... ['', '', '', ''],
98- ... ['', '', '', ''],
99- ... ['', '', '', ''],
100- ... ['', '', '', '']
101- ... ]
102- >>> words = ['word', 'four', 'more', 'paragraphs']
103- >>> solve_crossword(puzzle, words)
86+ >>> puzzle2 = [['', '', '', ''], ['', '', '', ''],
87+ ... ['', '', '', ''], ['', '', '', '']]
88+ >>> solve_crossword(puzzle2, ['word', 'four', 'more', 'paragraphs'])
10489 False
10590 """
91+ if not words :
92+ return True
93+
94+ remaining = sorted (words , key = len , reverse = True )
95+ word , rest = remaining [0 ], remaining [1 :]
96+
10697 for row in range (len (puzzle )):
10798 for col in range (len (puzzle [0 ])):
108- if puzzle [row ][col ] == "" :
109- for word in words :
110- for vertical in [True , False ]:
111- if is_valid (puzzle , word , row , col , vertical ):
112- place_word (puzzle , word , row , col , vertical )
113- words .remove (word )
114- if solve_crossword (puzzle , words ):
115- return True
116- words .append (word )
117- remove_word (puzzle , word , row , col , vertical )
118- return False
119- return True
99+ for vertical in (True , False ):
100+ if is_valid (puzzle , word , row , col , vertical ):
101+ snapshot = [r [:] for r in puzzle ]
102+ place_word (puzzle , word , row , col , vertical )
103+ if solve_crossword (puzzle , rest ):
104+ return True
105+ remove_word (puzzle , word , row , col , vertical , snapshot )
106+
107+ return False
120108
121109
122110if __name__ == "__main__" :
123111 PUZZLE = [["" ] * 3 for _ in range (3 )]
124112 WORDS = ["cat" , "dog" , "car" ]
125-
126113 if solve_crossword (PUZZLE , WORDS ):
127114 print ("Solution found:" )
128115 for row in PUZZLE :
129- print (" " .join (row ))
116+ print (" " .join (cell or "." for cell in row ))
130117 else :
131- print ("No solution found: " )
118+ print ("No solution found. " )
0 commit comments