@@ -83,11 +83,87 @@ def gen_random(self):
83
83
self .add (dict (source = source , target = target ))
84
84
85
85
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
+
86
162
@register
87
163
class Quine (Problem ):
88
164
"""[Quine](https://en.wikipedia.org/wiki/Quine_%28computing%29)
89
165
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.
91
167
"""
92
168
93
169
@staticmethod
@@ -98,11 +174,33 @@ def sat(quine: str):
98
174
def sol ():
99
175
return "(lambda x: f'({x})({chr(34)}{x}{chr(34)})')(\" lambda x: f'({x})({chr(34)}{x}{chr(34)})'\" )"
100
176
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
+
101
199
102
200
@register
103
201
class BooleanPythagoreanTriples (Problem ):
104
202
"""[Boolean Pythagorean Triples Problem](https://en.wikipedia.org/wiki/Boolean_Pythagorean_triples_problem)
105
-
203
+
106
204
Color the first n integers with one of two colors so that there is no monochromatic Pythagorean triple.
107
205
"""
108
206
@@ -320,7 +418,6 @@ def sol():
320
418
if len (todos ) == 0 :
321
419
return [[pi [k :k + 3 ] for k in range (0 , 15 , 3 )] for pi , _inv in days ]
322
420
323
-
324
421
# return [[[0, 5, 10], [1, 6, 11], [2, 7, 12], [3, 8, 13], [4, 9, 14]], # wikipedia solution
325
422
# [[0, 1, 4], [2, 3, 6], [7, 8, 11], [9, 10, 13], [12, 14, 5]],
326
423
# [[1, 2, 5], [3, 4, 7], [8, 9, 12], [10, 11, 14], [13, 0, 6]],
@@ -333,7 +430,7 @@ def sol():
333
430
@register
334
431
class MonkeyAndCoconuts (Problem ):
335
432
"""[The Monkey and the Coconuts](https://en.wikipedia.org/wiki/The_monkey_and_the_coconuts)
336
-
433
+
337
434
Find the number of coconuts to solve the following riddle quoted from
338
435
[Wikipedia article](https://en.wikipedia.org/wiki/The_monkey_and_the_coconuts):
339
436
There is a pile of coconuts, owned by five men.
@@ -366,16 +463,6 @@ def sol():
366
463
m += 5
367
464
368
465
369
- #
370
- #
371
- # assert monkey_and_coconuts_prob(monkey_and_coconuts_sol())
372
- #
373
- #
374
- # ########################################################################################################################
375
- #
376
- # # monty hall problem?
377
- #
378
- #
379
466
# ########################################################################################################################
380
467
# # https://en.wikipedia.org/wiki/Mountain_climbing_problem (Mathematical Problems category)
381
468
# # 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):
528
615
self .add (dict (side = side , num_points = num_points ), test = test )
529
616
530
617
531
- #
532
- # assert no_three_in_a_line_prob(no_three_in_a_line_sol())
533
- #
534
- #
535
618
# ########################################################################################################################
536
619
# # https://en.wikipedia.org/wiki/Orchard-planting_problem (Mathematical Problems category)
537
620
# # 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):
619
702
"""
620
703
621
704
@staticmethod
622
- def sat (n : int , lace = "rrbbbbrrbrbrbbrr " ):
705
+ def sat (n : int , lace = "bbbbrrbrbrbbrrrr " ):
623
706
sub = lace [n : n + len (lace ) // 2 ]
624
707
return n >= 0 and lace .count ("r" ) == 2 * sub .count ("r" ) and lace .count ("b" ) == 2 * sub .count ("b" )
625
708
@@ -769,9 +852,6 @@ def sol():
769
852
return [i for i in range (- 10 ** 5 , 10 ** 5 ) if sorted ([int (s ) for s in str (i * i )]) == list (range (10 ))]
770
853
771
854
772
-
773
-
774
-
775
855
# MAYBE: add a version of TowersOfHanoiArbitrary where one has to find the fewest moves (maybe with more than 3 pegs)
776
856
777
857
@register
@@ -1041,7 +1121,115 @@ def gen_random(self):
1041
1121
words = ["" .join (alpha [int (d )] for d in str (i )) for i in nums ]
1042
1122
self .add (dict (words = words )) # , test=False)
1043
1123
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
+
1044
1231
1045
1232
if __name__ == "__main__" :
1233
+ # SlidingPuzzle.sol(**SlidingPuzzle.get_example())
1046
1234
for problem in get_problems (globals ()):
1047
1235
problem .test ()
0 commit comments