mirror of
https://github.com/InternLM/InternBootcamp.git
synced 2026-04-19 12:58:04 +00:00
207 lines
No EOL
8.3 KiB
Python
Executable file
207 lines
No EOL
8.3 KiB
Python
Executable file
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) |