Skip to content

Commit f265d19

Browse files
committed
Fixed docstrings, added a couple of human_eval puzzles
1 parent 6ccde8f commit f265d19

File tree

10 files changed

+669
-223
lines changed

10 files changed

+669
-223
lines changed

generators/chess.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,10 @@ def gen_random(self):
180180
self.add(dict(m=m, n=n, target=target)) # solved by someone
181181

182182

183-
class UNSOLVED_UncrossedKnightsPath(UncrossedKnightsPath):
183+
class UNSOLVED_UncrossedKnightsPath(PuzzleGenerator):
184184
"""Uncrossed Knights Path (open problem, unsolved)
185185
186-
The goal of these problems is to *beat* the nxn_records from
186+
Similar to above, but the goal of these problems is to *beat* the nxn_records from
187187
[http://ukt.alex-black.ru/](http://ukt.alex-black.ru/)
188188
(accessed 2020-11-29).
189189
@@ -193,11 +193,37 @@ class UNSOLVED_UncrossedKnightsPath(UncrossedKnightsPath):
193193
unsolved_nxn_records = {10: 61, 11: 76, 12: 94, 13: 113, 14: 135, 15: 158,
194194
16: 183, 17: 211, 18: 238, 19: 268, 20: 302, 21: 337, 22: 375, 23: 414}
195195

196+
@staticmethod
197+
def sat(path: List[List[int]], m=10, n=10, target=62):
198+
"""Find a long (open) tour of knight moves on an m x n chess-board whose edges don't cross."""
199+
def legal_move(m):
200+
(a, b), (i, j) = m
201+
return {abs(i - a), abs(j - b)} == {1, 2}
202+
203+
def legal_quad(m1, m2): # non-overlapping test: parallel or bounding box has (width - 1) * (height - 1) >= 5
204+
(i1, j1), (i2, j2) = m1
205+
(a1, b1), (a2, b2) = m2
206+
return (len({(i1, j1), (i2, j2), (a1, b1), (a2, b2)}) < 4 # adjacent edges in path, ignore
207+
or (i1 - i2) * (b1 - b2) == (j1 - j2) * (a1 - a2) # parallel
208+
or (max(a1, a2, i1, i2) - min(a1, a2, i1, i2)) * (max(b1, b2, j1, j2) - min(b1, b2, j1, j2)) >= 5
209+
# far
210+
)
211+
212+
assert all(i in range(m) and j in range(n) for i, j in path), "move off board"
213+
assert len({(i, j) for i, j in path}) == len(path), "visited same square twice"
214+
215+
moves = list(zip(path, path[1:]))
216+
assert all(legal_move(m) for m in moves), "illegal move"
217+
assert all(legal_quad(m1, m2) for m1 in moves for m2 in moves), "intersecting move pair"
218+
219+
return len(path) >= target
220+
221+
196222
def gen(self, target_num_instances):
197-
for count, n in enumerate(self.nxn_records):
223+
for n in self.unsolved_nxn_records:
198224
if len(self.instances) >= target_num_instances:
199225
return
200-
self.add(dict(m=n, n=n, target=self.nxn_records[n] + 1)) # Note the +1 means breaking the record!
226+
self.add(dict(m=n, n=n, target=self.unsolved_nxn_records[n] + 1)) # Note the +1 means breaking the record!
201227

202228

203229
if __name__ == "__main__":

generators/codeforces.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,7 @@ class CombinationLockObfuscated(CombinationLock):
10481048

10491049
@staticmethod
10501050
def sat(states: List[str], start="012", combo="329", target_len=6):
1051+
"""Figure out what this does only from the code"""
10511052
return all(sum((int(a[i]) - int(b[i])) ** 2 % 10 for i in range(len(start))) == 1
10521053
for a, b in zip([start] + states, states[:target_len] + [combo]))
10531054

generators/human_eval.py

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2556,7 +2556,6 @@ def gen_random(self):
25562556
self.add(dict(n=n))
25572557

25582558

2559-
25602559
class HexPrimes(PuzzleGenerator):
25612560
"""Inspired by [HumanEval](https://github.com/openai/human-eval) \\#78"""
25622561

@@ -2587,7 +2586,7 @@ def sat(b: str, n=5324680297138495285):
25872586
assert inside[0] == "1" or len(inside) == 1
25882587
m = 0
25892588
for c in inside:
2590-
m = 2*m + int(c)
2589+
m = 2 * m + int(c)
25912590
return m == n
25922591

25932592
@staticmethod
@@ -2601,6 +2600,93 @@ def gen_random(self):
26012600
self.add(dict(n=n))
26022601

26032602

2603+
class NearbyDuplicates(PuzzleGenerator):
2604+
"""Inspired by [HumanEval](https://github.com/openai/human-eval) \\#80"""
2605+
2606+
@staticmethod
2607+
def sat(indices: List[int], s="I am an unhappy string!"):
2608+
"""A string is happy if every three consecutive characters are distinct. Find two indices making s unhappy."""
2609+
i, j = indices
2610+
return s[i] == s[j] and 0 <= i < j < i + 3
2611+
2612+
@staticmethod
2613+
def sol(s):
2614+
for i in range(len(s) - 2):
2615+
if s[i] == s[i + 1]:
2616+
return [i, i + 1]
2617+
if s[i] == s[i + 2]:
2618+
return [i, i + 2]
2619+
2620+
def gen_random(self):
2621+
a = self.random.string(min_len=1)
2622+
s = a + self.random.choice(["", self.random.char()]) + a[-1] + self.random.string(min_len=1)
2623+
self.add(dict(s=s))
2624+
2625+
2626+
class Grader(PuzzleGenerator):
2627+
"""Inspired by [HumanEval](https://github.com/openai/human-eval) \\#81"""
2628+
2629+
@staticmethod
2630+
def sat(grades: List[str], gpas=[2.8, 3.1, 4.0, 2.2, 3.1, 2.5, 0.9]):
2631+
"""
2632+
Convert GPAs to letter grades according to the following table:
2633+
4.0: A+
2634+
3.7: A
2635+
3.4: A-
2636+
3.0: B+
2637+
2.7: B
2638+
2.4: B-
2639+
2.0: C+
2640+
1.7: C
2641+
1.4: C-
2642+
below: F
2643+
2644+
Sample input: [4.0, 3.5, 3.8]
2645+
Sample output: ['A+', 'A-', 'A']
2646+
"""
2647+
assert len(grades) == len(gpas)
2648+
letters = ['A+', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'F']
2649+
scores = [4.0, 3.7, 3.4, 3.0, 2.7, 2.4, 2.0, 1.7, 1.4, 0.0]
2650+
for grade, gpa in zip(grades, gpas):
2651+
i = letters.index(grade)
2652+
assert gpa >= scores[i]
2653+
assert i == 0 or gpa <= scores[i - 1]
2654+
return True
2655+
2656+
@staticmethod
2657+
def sol(gpas):
2658+
letters = ['A+', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'F']
2659+
scores = [4.0, 3.7, 3.4, 3.0, 2.7, 2.4, 2.0, 1.7, 1.4, 0.0]
2660+
ans = []
2661+
for gpa in gpas:
2662+
i = 0
2663+
while gpa < scores[i]:
2664+
i += 1
2665+
ans.append(letters[i])
2666+
return ans
2667+
2668+
def gen_random(self):
2669+
gpas = [self.random.random() * 4.0 for _ in range(self.random.randrange(10))]
2670+
self.add(dict(gpas=gpas))
2671+
2672+
2673+
class FactorString(PuzzleGenerator):
2674+
"""Inspired by [HumanEval](https://github.com/openai/human-eval) \\#82"""
2675+
2676+
@staticmethod
2677+
def sat(factor: str, s="catscatcatscatcatscat"):
2678+
"""Find a string which when repeated more than once gives s"""
2679+
return len(factor) < len(s) and s == factor * (len(s) // len(factor))
2680+
2681+
@staticmethod
2682+
def sol(s):
2683+
n = len(s)
2684+
return next(s[:i] for i in range(1, len(s)) if s == s[:i] * (n // i))
2685+
2686+
def gen_random(self):
2687+
s = self.random.pseudo_word() * self.random.randrange(2, 10)
2688+
self.add(dict(s=s))
2689+
26042690

26052691
if __name__ == "__main__":
26062692
PuzzleGenerator.debug_problems()

generators/number_theory.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@
1111
class FermatsLastTheorem(PuzzleGenerator):
1212
"""[Fermat's last theorem](https://en.wikipedia.org/w/index.php?title=Fermat%27s_Last_Theorem)
1313
14-
Find integers a,b,c > 0, n > 2, such such that `a ** n + b ** n == c ** n`
1514
Supposedly unsolvable, but how confident are really in the super-complicated proof?
1615
1716
See [Wiles, Andrew. "Modular elliptic curves and Fermat's last theorem." Annals of mathematics 141.3 (1995): 443-551.](https://www.jstor.org/stable/2118559)"""
1817

1918
@staticmethod
2019
def sat(nums: List[int]):
20+
"""Find integers a,b,c > 0, n > 2, such such that a^n + b^n == c^n"""
2121
a, b, c, n = nums
2222
return (a ** n + b ** n == c ** n) and min(a, b, c) > 0 and n > 2
2323

@@ -28,12 +28,11 @@ class GCD(PuzzleGenerator):
2828
"""[Greatest Common Divisor](https://en.wikipedia.org/w/index.php?title=Greatest_common_divisor&oldid=990943381)
2929
(GCD)
3030
31-
Find the greatest common divisor of two integers.
32-
3331
See also the [Euclidean algorithm](https://en.wikipedia.org/wiki/Euclidean_algorithm)"""
3432

3533
@staticmethod
3634
def sat(n: int, a=15482, b=23223, lower_bound=5):
35+
"""Find a large common divisor of two integers."""
3736
return a % n == 0 and b % n == 0 and n >= lower_bound
3837

3938
@staticmethod
@@ -66,12 +65,11 @@ class GCD_multi(PuzzleGenerator):
6665
"""[Greatest Common Divisor](https://en.wikipedia.org/w/index.php?title=Greatest_common_divisor&oldid=990943381)
6766
(GCD)
6867
69-
Find the greatest common divisor of a *list* of integers.
70-
7168
See also the [Euclidean algorithm](https://en.wikipedia.org/wiki/Euclidean_algorithm)"""
7269

7370
@staticmethod
7471
def sat(n: int, nums=[77410, 23223, 54187], lower_bound=2):
72+
"""Find a large common divisor of the list of integers."""
7573
return all(i % n == 0 for i in nums) and n >= lower_bound
7674

7775
@staticmethod
@@ -96,12 +94,11 @@ class LCM(PuzzleGenerator):
9694
"""[Least Common Multiple](https://en.wikipedia.org/wiki/Least_common_multiple)
9795
(LCM)
9896
99-
Find the least common multiple of two integers.
100-
10197
See also the [Euclidean algorithm](https://en.wikipedia.org/wiki/Euclidean_algorithm)"""
10298

10399
@staticmethod
104100
def sat(n: int, a=15, b=27, upper_bound=150):
101+
"""Find a small common multiple of two integers."""
105102
return n % a == 0 and n % b == 0 and 0 < n <= upper_bound
106103

107104
@staticmethod
@@ -123,12 +120,11 @@ class LCM_multi(PuzzleGenerator):
123120
"""[Least Common Multiple](https://en.wikipedia.org/wiki/Least_common_multiple)
124121
(LCM)
125122
126-
Find the least common multiple of a list of integers.
127-
128123
See also the [Euclidean algorithm](https://en.wikipedia.org/wiki/Euclidean_algorithm)"""
129124

130125
@staticmethod
131126
def sat(n: int, nums=[15, 27, 102], upper_bound=5000):
127+
"""Find a small common multiple of a list of integers."""
132128
return all(n % i == 0 for i in nums) and n <= upper_bound
133129

134130
@staticmethod
@@ -152,15 +148,14 @@ def gen_random(self):
152148
class SmallExponentBigSolution(PuzzleGenerator):
153149
"""Small exponent, big solution
154150
155-
Solve for n: b^n = target (mod n)
156-
157151
Problems have small b and target but solution is typically a large n.
158152
Some of them are really hard, for example, for `b=2, target=3`, the smallest solution is `n=4700063497`
159153
160154
See [Richard K. Guy "The strong law of small numbers", (problem 13)](https://doi.org/10.2307/2322249)"""
161155

162156
@staticmethod
163157
def sat(n: int, b=2, target=5):
158+
"""Solve for n: b^n = target (mod n)"""
164159
return (b ** n) % n == target
165160

166161
@staticmethod
@@ -205,6 +200,7 @@ class ThreeCubes(PuzzleGenerator):
205200

206201
@staticmethod
207202
def sat(nums: List[int], target=10):
203+
"""Given n, find integers a, b, c such that a^3 + b^3 + c^3 = n."""
208204
assert target % 9 not in [4, 5], "Hint"
209205
return len(nums) == 3 and sum([i ** 3 for i in nums]) == target
210206

generators/trivial_inverse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -661,9 +661,9 @@ def gen_random(self):
661661

662662

663663
class IntNegSquareRoot(PuzzleGenerator):
664-
"""Find a negative integer that when squared equals perfect-square a."""
665664
@staticmethod
666665
def sat(n: int, a=10000200001):
666+
"""Find a negative integer that when squared equals perfect-square a."""
667667
return a == n * n and n < 0
668668

669669
@staticmethod

make_dataset.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ def save_readme(gen_modules, filename):
5757
n = len(puzzles)
5858
link = f"[{sec_name}](#{sec_name.lower().replace(' ', '-')})"
5959
n_instances = sum(p["n_instances"] for p in puzzles)
60-
tot_instances += len(puzzles)
6160
tot_instances += n_instances
6261
table += f"- [{sec_name} ({len(puzzles):,} problems, {n_instances:,} instances)](#{sec_name.lower().replace(' ', '-')})\n"
6362
for i, puzzle in enumerate(puzzles):
@@ -138,10 +137,15 @@ def main(args):
138137
"examples": examples
139138
}
140139

140+
for p in puzzles:
141+
if p["sat"] == utils.remove_docstring(p["sat"]):
142+
print(p["sat"])
143+
assert False, f"Puzzle {p['name']} in {p['module']} doesn't have a valid docstring"
144+
141145
utils.save_json(puzzles, args.json, make_dirs_if_necessary=True, indent=2)
142146
save_readme(summaries, args.readme)
143147
utils.info(f"Elapsed time: {(time.perf_counter() - start_time) / 60:.2f} minutes")
144-
148+
utils.info(f"Saved {len(puzzles)} to {args.json} and {args.readme}")
145149

146150
if __name__ == "__main__":
147151
args = parser.parse_args()

puzzle_generator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -362,8 +362,8 @@ def add_test(self, sol_src, run_test_type=False):
362362
"""Assert that the solution satisfies the given instance and add the solution to the instance.
363363
Do a round-trip json encoding/decoding to mimic the actual test and deter strange attacks.
364364
Ideally this could be done by running a protected process (like in evaluating programming
365-
contest submissions) but that is much slower so we will only add that later if the AI
366-
starts misbehaving."""
365+
contest submissions) but that is much slower. Since this is a test we authored presumably it has
366+
no evil code."""
367367

368368
if sol_src in self.sol_srcs: # already added this solution
369369
return

0 commit comments

Comments
 (0)