mirror of
https://github.com/InternLM/InternBootcamp.git
synced 2026-04-19 12:58:04 +00:00
init-commit
This commit is contained in:
commit
18a552597a
3461 changed files with 1150579 additions and 0 deletions
207
internbootcamp/libs/calcudoku/calcudoku_validor.py
Executable file
207
internbootcamp/libs/calcudoku/calcudoku_validor.py
Executable file
|
|
@ -0,0 +1,207 @@
|
|||
import re
|
||||
from collections import defaultdict
|
||||
from itertools import permutations
|
||||
|
||||
def validate_calcudoku_puzzle(puzzle_spec, solution):
|
||||
"""
|
||||
Given:
|
||||
puzzle_spec: list of strings, each string has n group labels or clues separated by whitespace.
|
||||
E.g., "J+12 F/4 5 2 E*3 E".
|
||||
solution: 2D list of ints (n×n), e.g. [[6,4,5,2,1,3], [5,1,4,3,2,6], ...]
|
||||
Returns: (bool, str)
|
||||
- bool indicating whether the solution is fully valid or not.
|
||||
- str is "" if valid, or an explanation of the first error found.
|
||||
|
||||
Steps to validate:
|
||||
1) Parse puzzle spec to find:
|
||||
- The puzzle size n
|
||||
- group_label -> list of cells
|
||||
- group_label -> (op, target), if any
|
||||
|
||||
2) Check solution dimension matches puzzle dimension.
|
||||
|
||||
3) Check each row, each column for distinct 1..n.
|
||||
|
||||
4) For each group, check if the group's constraint is satisfied:
|
||||
op "+" => sum of group cells == target
|
||||
op "*" => product of group cells == target
|
||||
op "-" => for some permutation p of group cells, p[0] - p[1] - ... = target
|
||||
op "/" => for some permutation p of group cells, p[0] / p[1] / ... = target (within an epsilon for float)
|
||||
"""
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Step 1: parse puzzle spec
|
||||
# ---------------------------------------------------------------------------
|
||||
n = len(puzzle_spec)
|
||||
# basic check: each row in puzzle_spec should have n tokens
|
||||
puzzle_grid = []
|
||||
for r, line in enumerate(puzzle_spec):
|
||||
tokens = line.strip().split()
|
||||
if len(tokens) != n:
|
||||
return False, f"Row {r} of puzzle_spec has {len(tokens)} cells, expected {n}"
|
||||
puzzle_grid.append(tokens)
|
||||
|
||||
# group_label -> dict with {"cells": [...], "op": None or str, "target": None or int}
|
||||
groups = defaultdict(lambda: {"cells": [], "op": None, "target": None})
|
||||
|
||||
# We'll read each cell's label or clue.
|
||||
# If it includes operation+target (like "J+12"), we store that in the group info.
|
||||
for r in range(n):
|
||||
for c in range(n):
|
||||
cell_str = puzzle_grid[r][c]
|
||||
# We assume the puzzle format:
|
||||
# either "X" (just a label)
|
||||
# or "X+12", "X-3", "X*60", "X/4", etc.
|
||||
# or possibly "XY+12" if label has multiple letters
|
||||
# We'll parse label, operation, and target.
|
||||
|
||||
match = re.match(r"^([A-Za-z]+)([+\-\*/])(\d+)$", cell_str)
|
||||
if match:
|
||||
g_label = match.group(1)
|
||||
op = match.group(2)
|
||||
tgt = int(match.group(3))
|
||||
groups[g_label]["op"] = op
|
||||
groups[g_label]["target"] = tgt
|
||||
groups[g_label]["cells"].append((r, c))
|
||||
else:
|
||||
# just a label
|
||||
g_label = cell_str
|
||||
groups[g_label]["cells"].append((r, c))
|
||||
|
||||
# gather group data in a simpler structure
|
||||
group_list = []
|
||||
for g_label, info in groups.items():
|
||||
op = info["op"]
|
||||
target = info["target"]
|
||||
cells = info["cells"]
|
||||
group_list.append((g_label, op, target, cells))
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2) Check solution dimension
|
||||
# ---------------------------------------------------------------------------
|
||||
if len(solution) != n:
|
||||
return False, f"Solution row count {len(solution)} != puzzle dimension {n}"
|
||||
for r in range(n):
|
||||
if len(solution[r]) != n:
|
||||
return False, f"Solution row {r} has {len(solution[r])} cols, expected {n}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3) Check row & column distinctness
|
||||
# Each row/col must contain numbers 1..n exactly once
|
||||
# ---------------------------------------------------------------------------
|
||||
# Check row distinctness
|
||||
for r in range(n):
|
||||
row_vals = solution[r]
|
||||
if len(set(row_vals)) != n:
|
||||
return False, f"Row {r} has repeated values: {row_vals}"
|
||||
# optionally check they are exactly 1..n
|
||||
for val in row_vals:
|
||||
if val < 1 or val > n:
|
||||
return False, f"Row {r} has out-of-range value {val}"
|
||||
|
||||
# Check column distinctness
|
||||
for c in range(n):
|
||||
col_vals = [solution[r][c] for r in range(n)]
|
||||
if len(set(col_vals)) != n:
|
||||
return False, f"Column {c} has repeated values: {col_vals}"
|
||||
for val in col_vals:
|
||||
if val < 1 or val > n:
|
||||
return False, f"Column {c} has out-of-range value {val}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4) Check group operation constraints
|
||||
# ---------------------------------------------------------------------------
|
||||
for g_label, op, target, cells in group_list:
|
||||
# Gather the solution values for these cells
|
||||
vals = [solution[r][c] for (r, c) in cells]
|
||||
|
||||
# If the group has no op/target (like single-cell group sometimes?),
|
||||
# then it's typically the puzzle format that it does have an op & target
|
||||
# on at least one cell. For a single-cell group, one possibility is "A+5" or similar.
|
||||
# If we truly have no op, we'll just skip – or treat it as passing automatically.
|
||||
if op is None or target is None:
|
||||
# If the group is single cell, we can interpret that as the target = that cell's value with op = '+'
|
||||
# or we can just skip. Here, let's skip. Or we can check that if group has 1 cell, the value is that cell.
|
||||
if len(cells) == 1:
|
||||
# It's presumably correct.
|
||||
# If you need a stricter logic, uncomment:
|
||||
# if vals[0] != target: return False, f"Single-cell group {g_label} mismatch"
|
||||
pass
|
||||
continue
|
||||
|
||||
if op == '+':
|
||||
if sum(vals) != target:
|
||||
return False, f"Group {g_label} sum {sum(vals)} != target {target}"
|
||||
elif op == '*':
|
||||
prod = 1
|
||||
for v in vals:
|
||||
prod *= v
|
||||
if prod != target:
|
||||
return False, f"Group {g_label} product {prod} != target {target}"
|
||||
elif op == '-':
|
||||
# We look for any permutation p of vals s.t. p[0] - p[1] - ... = target
|
||||
found = False
|
||||
for perm in permutations(vals):
|
||||
result = perm[0]
|
||||
for x in perm[1:]:
|
||||
result -= x
|
||||
if result == target:
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
return False, f"Group {g_label} cannot satisfy difference = {target} with values {vals}"
|
||||
elif op == '/':
|
||||
# We look for any permutation p of vals s.t. p[0] / p[1] / ... = target
|
||||
found = False
|
||||
for perm in permutations(vals):
|
||||
result = float(perm[0])
|
||||
ok = True
|
||||
for x in perm[1:]:
|
||||
# check dividing by zero not possible here if x>0
|
||||
# but just in case
|
||||
if x == 0:
|
||||
ok = False
|
||||
break
|
||||
result /= float(x)
|
||||
# check if close
|
||||
if ok and abs(result - target) < 1e-9:
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
return False, f"Group {g_label} cannot satisfy ratio = {target} with values {vals}"
|
||||
else:
|
||||
return False, f"Group {g_label} has unsupported op {op}"
|
||||
|
||||
# If we reach here, everything passed
|
||||
return True, ""
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
|
||||
# Example puzzle (the same puzzle_spec might be from a generator).
|
||||
puzzle_spec_example = [
|
||||
"P+4 O+1 T+5 G-2 E+3 R+5",
|
||||
"A-2 Q/3 Q G F+5 R",
|
||||
"A A H+2 N+8 N K+4",
|
||||
"L-1 B*48 B N C+6 S+1",
|
||||
"L J+10 B I*10 I I",
|
||||
"J J D+8 D D M+6"
|
||||
]
|
||||
# Suppose n=4 here. A= group 1, B= group 2, etc.
|
||||
# This puzzle_spec is purely an illustrative example, likely incomplete as a real puzzle.
|
||||
|
||||
# And an example solution that we want to check:
|
||||
solution_example = [
|
||||
[4, 1, 5, 6, 3, 2],
|
||||
[1 ,2, 6, 4, 5, 3],
|
||||
[3, 6, 2, 5, 1, 4],
|
||||
[5, 4, 3, 2, 6, 1],
|
||||
[6, 3, 4, 1, 2, 5],
|
||||
[2, 5, 1, 3, 4, 6],
|
||||
]
|
||||
|
||||
is_valid, reason = validate_calcudoku_puzzle(puzzle_spec_example, solution_example)
|
||||
if is_valid:
|
||||
print("Solution is valid!")
|
||||
else:
|
||||
print("Solution is invalid:", reason)
|
||||
Loading…
Add table
Add a link
Reference in a new issue