@@ -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
87163class 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
103201class 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
334431class 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
10451232if __name__ == "__main__" :
1233+ # SlidingPuzzle.sol(**SlidingPuzzle.get_example())
10461234 for problem in get_problems (globals ()):
10471235 problem .test ()
0 commit comments