-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.py
188 lines (158 loc) · 6.52 KB
/
main.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
import argparse
import logging
import re
from anagrammer import Anagrammer
from clue import Clue
from lib import mystring
from solvers.anagramsolver import AnagramSolver
from solvers.doublesolver import DoubleSolver
from solvers.hiddensolver import HiddenSolver
from solvers.reversesolver import ReverseSolver
# corpuses
anagram_indicator_file = "data/anagram.txt"
hidden_indicator_file = "data/hidden.txt"
reverse_indicator_file = "data/reversal.txt"
# other data files
anagram_database_file = "data/anagrams.db"
# allow args to be accessed in other files
cmd_line_args = None
secondary_solver_cutoff = 40.0
class Parser(object):
""" A parser.
Attributes:
solvers
aux_solvers
"""
def __init__(self):
# Generate solvers.
anagrammer = Anagrammer(anagram_database_file)
anagram_solver = AnagramSolver(anagram_indicator_file, anagrammer)
double_solver = DoubleSolver()
hidden_solver = HiddenSolver(hidden_indicator_file, anagrammer)
reverse_solver = ReverseSolver(reverse_indicator_file, anagrammer)
self.solvers = [anagram_solver, hidden_solver, reverse_solver]
self.aux_solvers = [double_solver]
def parse_length(self, input_line):
# expect "... (len)"
# todo: implement double clues better
split = re.search("(.*)\((\d+),(\d+)\)", input_line)
if split:
logging.warning("Double clues aren't implemented yet.")
clue_words = split.group(1).strip()
answer_length = int(split.group(2)) + int(split.group(3))
return clue_words, answer_length
# allow lengths given outside of parenthesis
split = re.search("([^\d]*)(\d+)$", input_line)
if not split:
split = re.search("([^\d]*)\((\d+)\)$", input_line)
if not split:
logging.warning("No length specified")
return [], 0
clue_words = split.group(1).strip()
answer_length = split.group(2)
try:
answer_length = int(mystring.strip_punctuation(answer_length))
except ValueError:
logging.warning("No answer length given for clue")
return [], 0
return clue_words, answer_length
def parse(self, input_line, print_solns=True):
""" Parse a given line of input.
:param str input_line:
:param bool print_solns: Whether to print solns
:return: List of solution objects
:rtype: list[solution.Solution]
"""
logging.debug("parsing")
clue_words, answer_length = self.parse_length(input_line)
if not clue_words or answer_length == 0: # error
logging.debug("failed to parse out length")
return []
clue = Clue(clue_words, answer_length)
solns = []
for s in self.solvers:
logging.info("Running " + str(s))
new_solns = s.get_solutions(clue)
logging.info(str(s) + " got " + str(len(new_solns)) + " solutions")
solns += new_solns
solns = sorted(solns)
# run secondary, expensive solvers
if not solns or solns[-1].score < secondary_solver_cutoff:
logging.info("Running auxiliary solvers")
for s in self.aux_solvers:
logging.info("Running " + str(s))
new_solns = s.get_solutions(clue)
logging.info(str(s) + " got " + str(len(new_solns)) + " solns")
solns += new_solns
solns = sorted(solns)
# todo: get rid of duplicates?
# todo: scale relative anagramers
ret = sorted(solns)[-cmd_line_args.num_solns:]
if print_solns:
for soln in ret:
if cmd_line_args.no_solution_breakdown:
print soln.solution
else:
print str(soln) + "\n"
return ret
def main():
# Parse commandline arguments
arg_parser = argparse.ArgumentParser(description='Cryptic crossword solver.')
arg_parser.add_argument("-v", "--logging", type=str, default="DEBUG",
help='Verbosity logging ')
arg_parser.add_argument("--no-solution-breakdown", action="store_true",
help="Print only solution words with no "
"explanation.")
arg_parser.add_argument("--num-solns", type=int, default=10,
help="Number of solutions to show.")
arg_parser.add_argument("--input-file", type=str, default="",
help="Run on input list of clues, not interactive")
arg_parser.add_argument("--test", type=str, default="",
help="Run test file")
global cmd_line_args
cmd_line_args = arg_parser.parse_args()
# Set logging level
logging_level = getattr(logging, cmd_line_args.logging.upper())
if not isinstance(logging_level, int):
raise ValueError('Invalid log level: %s' % cmd_line_args.logging)
logging.basicConfig(level=logging_level)
logging.info("Setting verbosity to " + str(cmd_line_args.logging))
parser = Parser()
test_print = True
if cmd_line_args.test != "":
print "Running in test mode..."
correct = 0
correct_but_not_first = 0
total = 0
with open(cmd_line_args.test, "r") as f:
lines = f.readlines()
for i in range(0, len(lines), 2):
total += 1
clue = lines[i]
correct_soln = lines[i + 1].strip()
if test_print:
print "--------------"
print clue.strip()
print "expect:\t" + correct_soln + "\n"
solns = parser.parse(clue, print_solns=test_print)
if solns and solns[-1].solution == correct_soln:
correct += 1
else:
for s in solns[:-1]:
if s.solution == correct_soln:
correct_but_not_first += 1
print "Out of total: %d\t %d correct\t %d correct but not first" % \
(total, correct, correct_but_not_first)
elif cmd_line_args.input_file == "":
print "Running in interactive mode...\n" \
"Clues should be of the form '....(num)'"
while True:
line = raw_input()
parser.parse(line)
else:
print "Running in file input mode..."
with open(cmd_line_args.input_file, "r") as f:
for line in f.readlines():
print line
parser.parse(line)
main()