chiark / gitweb /
29caaae9144f218af521616c0b7bdcb63dd3f1e4
[matchsticks-search.git] / tabulate.py
1 #!/usr/bin/env python
2
3 import sys, os, re, fractions
4 from fractions import Fraction
5
6 import bounds
7
8 header_re = re.compile(r'^(\d+) into (\d+).*:.* ([\d/]+)')
9 dissect_re = re.compile(r'\s+(\d+)\s+x\s+\((([\d/]+ \+ )*[\d/]+)\)')
10
11 dissections = {}
12
13 def find_min_frag(dissection):
14     ret = None
15     for side in dissection:
16         for count, pieces in side:
17             for piece in pieces:
18                 if piece > 0:
19                     if ret is None or ret > piece:
20                         ret = piece
21     assert ret is not None
22     return ret
23
24 def verify(n, m, dissection):
25     collections = [{}, {}]
26     total = 0
27     for count, pieces in dissection[0]:
28         assert sum(pieces) == n
29         total += count
30         for piece in pieces:
31             collections[0].setdefault(piece, 0)
32             collections[0][piece] += count
33     assert total == m
34     total = 0
35     for count, pieces in dissection[1]:
36         assert sum(pieces) == m
37         total += count
38         for piece in pieces:
39             collections[1].setdefault(piece, 0)
40             collections[1][piece] += count
41     assert total == n
42     assert collections[0] == collections[1]
43
44 def add_m_dissector(n,m):
45     if n <= 2*m:
46         return None
47     prev = dissections[(n-m, m)]
48     ret = [prev[0][:], prev[1][:]]
49     for i in range(len(ret[0])):
50         ret[0][i] = (ret[0][i][0], ret[0][i][1] + [m])
51     ret[1].append((m, (m,)))
52     return ret
53
54 def gcd_dissector(n,m):
55     gcd = fractions.gcd(n,m)
56     return [[(m, (gcd,) * (n/gcd))],
57             [(n, (gcd,) * (m/gcd))]]
58
59 def invent_dissection(n,m):
60     # Try to come up with a dissection by assorted simple methods.
61     best = 0
62     for dissector, this_reason in [
63         (gcd_dissector, "trivial gcd solution"),
64         (add_m_dissector, "extend solution for (%d,%d)" % (n-m, m)),
65         ]:
66         dissection = dissector(n, m)
67         if dissection is None: continue
68         verify(n, m, dissection)
69         this_best = find_min_frag(dissection)
70         if best < this_best:
71             best, best_reason = this_best, this_reason
72     assert best > 0
73     return dissection, this_reason
74
75 def read(n,m):
76     best = 0
77
78     # Look for appropriate file(s).
79     for filename, nature in [("data/main.%d.%d" % (n,m), "e"),
80                              ("data/partition.%d.%d" % (n,m), "p"),
81                              ("data/manual.%d.%d" % (n,m), "m")]:
82         if os.path.exists(filename):
83             with open(filename) as f:
84                 header = f.readline()
85                 match = header_re.match(header)
86                 assert match is not None
87                 assert int(match.group(1)) == n
88                 assert int(match.group(2)) == m
89                 this_best = Fraction(match.group(3))
90
91                 # Now read the actual dissection.
92                 dissection = [[],[]]
93                 for line in f.readlines():
94                     match = dissect_re.match(line)
95                     if match is not None:
96                         count = int(match.group(1))
97                         pieces = map(Fraction, match.group(2).split(" + "))
98                         length = sum(pieces)
99                         if length == n:
100                             dissection[0].append((count, pieces))
101                         elif length == m:
102                             dissection[1].append((count, pieces))
103                         else:
104                             assert False, "wrong length"
105                 verify(n, m, dissection)
106                 assert find_min_frag(dissection) == this_best
107                 dissections[(n,m)] = dissection
108
109                 if this_best > best:
110                     best = this_best
111                     best_nature = nature
112
113     if best == 0:
114         # We don't have a stored solution at all, so make up something
115         # plausible if we can.
116         dissection, best_nature = invent_dissection(n, m)
117         best = find_min_frag(dissection)
118         dissections[(n,m)] = dissection
119
120     bound, bound_type = bounds.upper_bound(n, m)
121
122     if best == bound:
123         tdclass = "known"
124         show_bound = ""
125     elif best_nature == 'e':
126         tdclass = "believed"
127         show_bound = ""
128     elif best_nature == 'p':
129         tdclass = "probable"
130         show_bound = ""
131     else:
132         tdclass = "range"
133         show_bound = " &ndash; %s"
134     sys.stdout.write("<td class=\"%s\">" % tdclass)
135     sys.stdout.write(str(best))
136     if show_bound != "":
137         sys.stdout.write(show_bound % str(bound))
138     sys.stdout.write("</td>\n")
139
140 def main(args):
141     limit = 24 # FIXME: configurable
142     sys.stdout.write("""\
143 <html>
144 <head>
145 <title>Known bounds for stick-dissecting problem</title>
146 <style type="text/css">
147 table, td, th {
148     border: 1px solid black;
149     border-collapse: collapse;
150 }
151 td.known {
152     background-color: #00ff00;
153 }
154 td.believed {
155     background-color: #aaff00;
156 }
157 td.probable {
158     background-color: #ffff00;
159 }
160 td.range {
161     background-color: #ff8888;
162 }
163 </style>
164 </head>
165 <body>
166 <table>
167 """)
168     sys.stdout.write("<tr>\n")
169     sys.stdout.write("<th>n \\ m</th>\n")
170     for m in range(2,limit-1):
171         sys.stdout.write("<th>%d</th>\n" % m)
172     sys.stdout.write("</tr>\n")
173     for n in range(2,limit):
174         sys.stdout.write("<tr>\n")
175         sys.stdout.write("<th>%d</th>\n" % n)
176         for m in range(2,n):
177             read(n, m)
178         sys.stdout.write("</tr>\n")
179     sys.stdout.write("</table>\n")
180     sys.stdout.write("</body></html>\n")
181
182 if __name__ == "__main__":
183     main(sys.argv[1:])