Skip to content

Commit 854a295

Browse files
committed
Added trivial inverse problems (and renamed file)
Added sliding puzzle, longest monotonic subsequence, reverse quine
1 parent 2937be6 commit 854a295

File tree

2 files changed

+211
-23
lines changed

2 files changed

+211
-23
lines changed

templates/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from . import algebra
22
from . import basic
33
from . import chess
4+
from . import classic_puzzles
45
from . import codeforces
56
from . import game_theory
67
from . import graphs
78
from . import ICPC
89
from . import IMO
9-
from . import puzzles
1010
from . import trivial_inverse

templates/puzzles.py renamed to templates/classic_puzzles.py

Lines changed: 210 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,87 @@ def gen_random(self):
8383
self.add(dict(source=source, target=target))
8484

8585

86+
@register
87+
class LongestMonotonicSubstring(Problem):
88+
"""Find the indices of the longest substring with characters in sorted order."""
89+
90+
@staticmethod
91+
def sat(x: List[int], length=13, s="Dynamic programming solves this puzzle!!!"):
92+
return all(s[x[i]] <= s[x[i + 1]] and x[i + 1] > x[i] >= 0 for i in range(length - 1))
93+
94+
@staticmethod
95+
def sol(length, s): # O(N^2) method. Todo: add binary search solution which is O(n log n)
96+
if s == "":
97+
return []
98+
n = len(s)
99+
dyn = [] # list of (seq length, seq end, prev index)
100+
for i in range(n):
101+
try:
102+
dyn.append(max((length + 1, i, e) for length, e, _ in dyn if s[e] <= s[i]))
103+
except ValueError:
104+
dyn.append((1, i, -1)) # sequence ends at i
105+
_length, i, _ = max(dyn)
106+
backwards = [i]
107+
while dyn[i][2] != -1:
108+
i = dyn[i][2]
109+
backwards.append(i)
110+
return backwards[::-1]
111+
112+
def gen_random(self):
113+
n = self.random.randrange(self.random.choice([10, 100, 1000])) # a length between 1-10 or 1-100 or 1-1000
114+
m = self.random.randrange(n + 1)
115+
rand_chars = [chr(self.random.randrange(32, 124)) for _ in range(n)]
116+
li = sorted(rand_chars[:m])
117+
for i in range(m, n):
118+
li.insert(self.random.randrange(i + 1), rand_chars[i])
119+
s = "".join(li)
120+
length = len(self.sol(-1, s))
121+
self.add(dict(length=length, s=s))
122+
123+
124+
@register
125+
class LongestMonotonicSubstringTricky(Problem):
126+
"""Find the indices of the longest substring with characters in sorted order, with a twist!"""
127+
128+
@staticmethod
129+
def sat(x: List[int], length=20, s="Dynamic programming solves this puzzle!!!"):
130+
return all(s[x[i]] <= s[x[i + 1]] and x[i + 1] > x[i] for i in range(length - 1))
131+
132+
@staticmethod
133+
def sol(length, s): # O(N^2) method. Todo: add binary search solution which is O(n log n)
134+
if s == "":
135+
return []
136+
n = len(s)
137+
dyn = [] # list of (seq length, seq end, prev index)
138+
for i in range(-n, n):
139+
try:
140+
dyn.append(max((length + 1, i, e) for length, e, _ in dyn if s[e] <= s[i]))
141+
except ValueError:
142+
dyn.append((1, i, None)) # sequence ends at i
143+
_length, i, _ = max(dyn)
144+
backwards = [i]
145+
while dyn[n + i][2] is not None:
146+
i = dyn[n + i][2]
147+
backwards.append(i)
148+
return backwards[::-1]
149+
150+
def gen_random(self):
151+
n = self.random.randrange(self.random.choice([10, 100, 1000])) # a length between 1-10 or 1-100 or 1-1000
152+
m = self.random.randrange(n + 1)
153+
rand_chars = [chr(self.random.randrange(32, 124)) for _ in range(n)]
154+
li = sorted(rand_chars[:m])
155+
for i in range(m, n):
156+
li.insert(self.random.randrange(i + 1), rand_chars[i])
157+
s = "".join(li)
158+
length = len(self.sol(-1, s))
159+
self.add(dict(length=length, s=s))
160+
161+
86162
@register
87163
class Quine(Problem):
88164
"""[Quine](https://en.wikipedia.org/wiki/Quine_%28computing%29)
89165
90-
Find an expression whose evaluation is the same as itself.
166+
Find a string that when evaluated as a Python expression is that string itself.
91167
"""
92168

93169
@staticmethod
@@ -98,11 +174,33 @@ def sat(quine: str):
98174
def sol():
99175
return "(lambda x: f'({x})({chr(34)}{x}{chr(34)})')(\"lambda x: f'({x})({chr(34)}{x}{chr(34)})'\")"
100176

177+
@staticmethod
178+
def sol2(): # thanks for this simple solution, GPT-3!
179+
return 'quine'
180+
181+
182+
@register
183+
class RevQuine(Problem):
184+
"""Reverse [Quine](https://en.wikipedia.org/wiki/Quine_%28computing%29)
185+
186+
Find a string that, when reversed and evaluated gives you back that same string. The solution we found
187+
is from GPT3.
188+
"""
189+
190+
@staticmethod
191+
def sat(rev_quine: str):
192+
return eval(rev_quine[::-1]) == rev_quine
193+
194+
@staticmethod
195+
def sol():
196+
return "rev_quine"[::-1] # thanks GPT-3!
197+
198+
101199

102200
@register
103201
class BooleanPythagoreanTriples(Problem):
104202
"""[Boolean Pythagorean Triples Problem](https://en.wikipedia.org/wiki/Boolean_Pythagorean_triples_problem)
105-
203+
106204
Color the first n integers with one of two colors so that there is no monochromatic Pythagorean triple.
107205
"""
108206

@@ -320,7 +418,6 @@ def sol():
320418
if len(todos) == 0:
321419
return [[pi[k:k + 3] for k in range(0, 15, 3)] for pi, _inv in days]
322420

323-
324421
# return [[[0, 5, 10], [1, 6, 11], [2, 7, 12], [3, 8, 13], [4, 9, 14]], # wikipedia solution
325422
# [[0, 1, 4], [2, 3, 6], [7, 8, 11], [9, 10, 13], [12, 14, 5]],
326423
# [[1, 2, 5], [3, 4, 7], [8, 9, 12], [10, 11, 14], [13, 0, 6]],
@@ -333,7 +430,7 @@ def sol():
333430
@register
334431
class MonkeyAndCoconuts(Problem):
335432
"""[The Monkey and the Coconuts](https://en.wikipedia.org/wiki/The_monkey_and_the_coconuts)
336-
433+
337434
Find the number of coconuts to solve the following riddle quoted from
338435
[Wikipedia article](https://en.wikipedia.org/wiki/The_monkey_and_the_coconuts):
339436
There is a pile of coconuts, owned by five men.
@@ -366,16 +463,6 @@ def sol():
366463
m += 5
367464

368465

369-
#
370-
#
371-
# assert monkey_and_coconuts_prob(monkey_and_coconuts_sol())
372-
#
373-
#
374-
# ########################################################################################################################
375-
#
376-
# # monty hall problem?
377-
#
378-
#
379466
# ########################################################################################################################
380467
# # https://en.wikipedia.org/wiki/Mountain_climbing_problem (Mathematical Problems category)
381468
# # two mountain climbers starting at opposite ends of a mountain range must climb up and down to keep their heights
@@ -528,10 +615,6 @@ def gen(self, target_num_problems):
528615
self.add(dict(side=side, num_points=num_points), test=test)
529616

530617

531-
#
532-
# assert no_three_in_a_line_prob(no_three_in_a_line_sol())
533-
#
534-
#
535618
# ########################################################################################################################
536619
# # https://en.wikipedia.org/wiki/Orchard-planting_problem (Mathematical Problems category)
537620
# # Find n points in the plane that maximize the number of three-in-a-line (opposite of above no_three_in_a_line_prob)
@@ -619,7 +702,7 @@ class NecklaceSplit(Problem):
619702
"""
620703

621704
@staticmethod
622-
def sat(n: int, lace="rrbbbbrrbrbrbbrr"):
705+
def sat(n: int, lace="bbbbrrbrbrbbrrrr"):
623706
sub = lace[n: n + len(lace) // 2]
624707
return n >= 0 and lace.count("r") == 2 * sub.count("r") and lace.count("b") == 2 * sub.count("b")
625708

@@ -769,9 +852,6 @@ def sol():
769852
return [i for i in range(-10 ** 5, 10 ** 5) if sorted([int(s) for s in str(i * i)]) == list(range(10))]
770853

771854

772-
773-
774-
775855
# MAYBE: add a version of TowersOfHanoiArbitrary where one has to find the fewest moves (maybe with more than 3 pegs)
776856

777857
@register
@@ -1041,7 +1121,115 @@ def gen_random(self):
10411121
words = ["".join(alpha[int(d)] for d in str(i)) for i in nums]
10421122
self.add(dict(words=words)) # , test=False)
10431123

1124+
@register
1125+
class SlidingPuzzle(Problem):
1126+
"""[Sliding puzzle](https://en.wikipedia.org/wiki/15_puzzle)
1127+
1128+
Classic example of A* search. NP-hard but the puzzles can all be solved with A* and an efficient representation
1129+
1130+
3-, 8-, and 15-sliding puzzles
1131+
"""
1132+
1133+
@staticmethod
1134+
def sat(moves: List[int], start=[[5, 0, 2, 3], [1, 9, 6, 7], [4, 14, 8, 11], [12, 13, 10, 15]]):
1135+
locs = {i: [x, y] for y, row in enumerate(start) for x, i in enumerate(row)} # locations, 0 stands for blank
1136+
for i in moves:
1137+
assert abs(locs[0][0] - locs[i][0]) + abs(locs[0][1] - locs[i][1]) == 1
1138+
locs[0], locs[i] = locs[i], locs[0]
1139+
return all(locs[i] == [i % len(start[0]), i // len(start)] for i in locs)
1140+
1141+
@staticmethod
1142+
def sol(start):
1143+
from collections import defaultdict
1144+
import math
1145+
d = len(start)
1146+
N = d * d
1147+
assert all(len(row) == d for row in start)
1148+
1149+
def get_state(
1150+
li): # state is an integer with 4 bits for each slot and the last 4 bits indicate where the blank is
1151+
ans = 0
1152+
for i in li[::-1] + [li.index(0)]:
1153+
ans = (ans << 4) + i
1154+
return ans
1155+
1156+
start = get_state([i for row in start for i in row])
1157+
target = get_state(list(range(N)))
1158+
1159+
def h(state): # manhattan distance
1160+
ans = 0
1161+
for i in range(N):
1162+
state = (state >> 4)
1163+
n = state & 15
1164+
if n != 0:
1165+
ans += abs(i % d - n % d) + abs(i // d - n // d)
1166+
return ans
1167+
1168+
g = defaultdict(lambda: math.inf)
1169+
g[start] = 0 # shortest p ath lengths
1170+
f = {start: h(start)} # f[s] = g[s] + h(s)
1171+
backtrack = {}
1172+
1173+
todo = {start}
1174+
import heapq
1175+
heap = [(f[start], start)]
1176+
1177+
neighbors = [[i for i in [b - 1, b + 1, b + d, b - d] if i in range(N) and (b // d == i // d or b % d == i % d)]
1178+
for b in range(N)]
1179+
1180+
def next_state(s, blank, i):
1181+
assert blank == (s & 15)
1182+
v = (s >> (4 * i + 4)) & 15
1183+
return s + (i - blank) + (v << (4 * blank + 4)) - (v << (4 * i + 4))
1184+
1185+
while todo:
1186+
(dist, s) = heapq.heappop(heap)
1187+
if f[s] < dist:
1188+
continue
1189+
if s == target:
1190+
# compute path
1191+
ans = []
1192+
while s != start:
1193+
s, i = backtrack[s]
1194+
ans.append((s >> (4 * i + 4)) & 15)
1195+
return ans[::-1]
1196+
1197+
todo.remove(s)
1198+
1199+
blank = s & 15
1200+
score = g[s] + 1
1201+
for i in neighbors[blank]:
1202+
s2 = next_state(s, blank, i)
1203+
1204+
if score < g[s2]:
1205+
# paths[s2] = paths[s] + [s[i]]
1206+
g[s2] = score
1207+
backtrack[s2] = (s, i)
1208+
score2 = score + h(s2)
1209+
f[s2] = score2
1210+
todo.add(s2)
1211+
heapq.heappush(heap, (score2, s2))
1212+
1213+
1214+
def gen_random(self):
1215+
d = self.random.randint(2, 4)
1216+
N = d * d
1217+
state = list(range(N))
1218+
num_moves = self.random.randrange(100)
1219+
for _ in range(num_moves):
1220+
blank = state.index(0)
1221+
delta = self.random.choice([-1, 1, d, -d])
1222+
1223+
i = blank + delta
1224+
if i not in range(N) or delta == 1 and i % d == 0 or delta == -1 and blank % d == 0:
1225+
continue
1226+
1227+
state[i], state[blank] = state[blank], state[i]
1228+
start = [list(state[i:i + d]) for i in range(0, N, d)]
1229+
self.add(dict(start=start))
1230+
10441231

10451232
if __name__ == "__main__":
1233+
# SlidingPuzzle.sol(**SlidingPuzzle.get_example())
10461234
for problem in get_problems(globals()):
10471235
problem.test()

0 commit comments

Comments
 (0)