-
Notifications
You must be signed in to change notification settings - Fork 2
/
crossword.py
133 lines (116 loc) · 4.5 KB
/
crossword.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
class Variable():
ACROSS = "across"
DOWN = "down"
def __init__(self, i, j, direction, length):
"""Create a new variable with starting point, direction, and length."""
self.i = i
self.j = j
self.direction = direction
self.length = length
self.cells = []
for k in range(self.length):
self.cells.append(
(self.i + (k if self.direction == Variable.DOWN else 0),
self.j + (k if self.direction == Variable.ACROSS else 0))
)
def __hash__(self):
return hash((self.i, self.j, self.direction, self.length))
def __eq__(self, other):
return (
(self.i == other.i) and
(self.j == other.j) and
(self.direction == other.direction) and
(self.length == other.length)
)
def __str__(self):
return f"({self.i}, {self.j}) {self.direction} : {self.length}"
def __repr__(self):
direction = repr(self.direction)
return f"Variable({self.i}, {self.j}, {direction}, {self.length})"
class Crossword():
def __init__(self, structure_file, words_file):
# Determine structure of crossword
with open(structure_file) as f:
contents = f.read().splitlines()
self.height = len(contents)
self.width = max(len(line) for line in contents)
self.structure = []
for i in range(self.height):
row = []
for j in range(self.width):
if j >= len(contents[i]):
row.append(False)
elif contents[i][j] == "_":
row.append(True)
else:
row.append(False)
self.structure.append(row)
# Save vocabulary list
with open(words_file) as f:
self.words = set(f.read().upper().splitlines())
# Determine variable set
self.variables = set()
for i in range(self.height):
for j in range(self.width):
# Vertical words
starts_word = (
self.structure[i][j]
and (i == 0 or not self.structure[i - 1][j])
)
if starts_word:
length = 1
for k in range(i + 1, self.height):
if self.structure[k][j]:
length += 1
else:
break
if length > 1:
self.variables.add(Variable(
i=i, j=j,
direction=Variable.DOWN,
length=length
))
# Horizontal words
starts_word = (
self.structure[i][j]
and (j == 0 or not self.structure[i][j - 1])
)
if starts_word:
length = 1
for k in range(j + 1, self.width):
if self.structure[i][k]:
length += 1
else:
break
if length > 1:
self.variables.add(Variable(
i=i, j=j,
direction=Variable.ACROSS,
length=length
))
# Compute overlaps for each word
# For any pair of variables v1, v2, their overlap is either:
# None, if the two variables do not overlap; or
# (i, j), where v1's ith character overlaps v2's jth character
self.overlaps = dict()
for v1 in self.variables:
for v2 in self.variables:
if v1 == v2:
continue
cells1 = v1.cells
cells2 = v2.cells
intersection = set(cells1).intersection(cells2)
if not intersection:
self.overlaps[v1, v2] = None
else:
intersection = intersection.pop()
self.overlaps[v1, v2] = (
cells1.index(intersection),
cells2.index(intersection)
)
def neighbors(self, var):
"""Given a variable, return set of overlapping variables."""
return set(
v for v in self.variables
if v != var and self.overlaps[v, var]
)