Files
2025-05-23 22:01:00 +08:00

444 lines
20 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from base.data import Data
from base.verifier import Verifier
import numpy as np
from typing import List, Dict
import re
class CalcudokoVerifier(Verifier):
"""
Calcudoko 游戏的验证器
"""
def extract_answer(self, test_solution: str):
"""
从模型的回答中提取数独解答
@param test_solution: 模型的完整回答
@return: 提取的答案 (元组形式的字符串)
"""
if not test_solution:
return ""
# 提取Python代码块中的内容
code_block_pattern = r"```python\s*([\s\S]*?)\s*```"
matches = re.findall(code_block_pattern, test_solution)
if matches:
# 取最后一个Python代码块
python_code = matches[-1].strip()
return python_code
# 如果没有找到Python代码块,尝试找到任何类似于元组的结构
tuple_pattern = r"\(\s*\(\s*\d+\s*,.*?\)\s*\)"
matches = re.findall(tuple_pattern, test_solution, re.DOTALL)
if matches:
return matches[-1].strip()
# 如果上面都没找到,直接返回原始答案
return test_solution
def parse_grid_from_answer(self, test_solution: str, data: Data) -> np.ndarray:
"""
从模型的回答中解析出网格数据
@param test_solution: 模型的完整回答
@param data: 包含问题、元数据等信息的Data对象
@return: numpy数组形式的网格
"""
try:
# 从data中获取grid_size
grid_size = data.metadata.get("grid_size", 4) # 默认为4
# 先提取答案
answer = self.extract_answer(test_solution)
# 查找[[...]]格式的答案
bracket_pattern = r'\[\[(.*?)\]\]'
bracket_match = re.search(bracket_pattern, test_solution)
if bracket_match:
answer = bracket_match.group(1)
print(f"从双括号中提取的答案: {answer}")
# 清理字符串,只保留必要字符
answer = ''.join(c for c in answer if c.isdigit() or c in '[],' or c.isspace())
# 标准化分隔符
answer = answer.replace(',', ',') # 中文逗号转英文逗号
# 去掉最外层的括号
answer = answer.strip('[]')
# 分割成行
rows = [row.strip() for row in answer.split(',')]
# 转换成数字网格
grid = []
for row in rows:
# 分割并转换成整数
numbers = [int(n) for n in row.split() if n.strip()]
if len(numbers) != grid_size:
# 尝试修复长度不正确的行
print(f"警告: 行长度不正确 {len(numbers)} != {grid_size}, 尝试修复...")
# 如果长度小于grid_size,尝试查找更多数字
if len(numbers) < grid_size:
# 在原始答案中查找类似于 "第x行:1 2 3 4 5" 的行描述
row_patterns = [
r'第\s*\d+\s*行\s*[::]\s*([\d\s]+)',
r'行\s*\d+\s*[::]\s*([\d\s]+)',
r'Row\s*\d+\s*[::]\s*([\d\s]+)',
r'- 第\d+行[::]?\s*([\d\s]+)',
r'- 行\d+[::]?\s*([\d\s]+)',
r'- Row\d+[::]?\s*([\d\s]+)'
]
found_rows = []
for pattern in row_patterns:
matches = re.findall(pattern, test_solution)
if matches:
found_rows.extend(matches)
print(f"找到的可能行: {found_rows}")
if len(found_rows) == grid_size:
# 如果找到的行数正好等于grid_size,使用这些行
print("使用找到的行描述作为网格数据")
grid = []
for found_row in found_rows:
numbers = [int(n) for n in found_row.split() if n.strip()]
if len(numbers) != grid_size:
print(f"警告: 找到的行长度仍不正确: {len(numbers)} != {grid_size}")
# 如果长度不正确,填充或截断
if len(numbers) < grid_size:
numbers.extend([1] * (grid_size - len(numbers)))
else:
numbers = numbers[:grid_size]
grid.append(numbers)
break # 成功处理,退出循环
# 填充或截断使长度正确
if len(numbers) < grid_size:
numbers.extend([1] * (grid_size - len(numbers)))
else:
numbers = numbers[:grid_size]
grid.append(numbers)
# 检查行数
if len(grid) != grid_size:
print(f"警告: 列数不正确: {len(grid)} != {grid_size}, 尝试修复...")
# 如果行数小于grid_size,添加默认行
if len(grid) < grid_size:
for _ in range(grid_size - len(grid)):
grid.append([1] * grid_size)
else:
# 如果行数大于grid_size,截断
grid = grid[:grid_size]
return np.array(grid)
except Exception as e:
print(f"解析答案时出错: {str(e)}")
import traceback
traceback.print_exc()
# 尝试直接搜索[[...]]格式
try:
bracket_pattern = r'\[\[(.*?)\]\]'
bracket_match = re.search(bracket_pattern, test_solution)
if bracket_match:
answer_str = bracket_match.group(1)
rows = answer_str.split(',')
grid = []
for row in rows:
numbers = [int(n) for n in row.split() if n.strip()]
if len(numbers) != grid_size:
if len(numbers) < grid_size:
numbers.extend([1] * (grid_size - len(numbers)))
else:
numbers = numbers[:grid_size]
grid.append(numbers)
if len(grid) != grid_size:
if len(grid) < grid_size:
for _ in range(grid_size - len(grid)):
grid.append([1] * grid_size)
else:
grid = grid[:grid_size]
print(f"已解析出最后的网格:\n{np.array(grid)}")
return np.array(grid)
except:
pass
# 最终失败,返回默认网格
print("无法解析答案,使用默认网格")
return np.array([[i % grid_size + 1 for i in range(grid_size)] for _ in range(grid_size)])
def verify(self, data: Data, test_answer: str) -> bool:
"""
验证模型的回答是否正确
@param data: 包含问题、元数据等信息的Data对象
@param test_answer: 模型给出的答案字符串
@return: 回答是否正确的布尔值
"""
try:
# 从元数据中获取必要信息
grid_size = data.metadata.get("grid_size", 4) # 默认为4
regions = data.metadata.get("regions", [])
print(f"验证: 网格大小 = {grid_size}x{grid_size}, 区域数量 = {len(regions)}")
print(f"验证: 模型答案='{test_answer[:100]}...'")
# 从答案中提取网格
try:
grid = self.parse_grid_from_answer(test_answer, data)
print(f"提取的网格:\n{grid}")
except Exception as e:
print(f"验证结果: 错误 - 无法提取网格: {str(e)}")
return False
# 验证数独规则
print("验证数独规则...")
# 检查每一行
for i, row in enumerate(grid):
if len(set(row)) != grid_size:
print(f"验证结果: 错误 - 第{i+1}行包含重复数字: {row}")
return False
if not all(1 <= n <= grid_size for n in row):
print(f"验证结果: 错误 - 第{i+1}行包含超出范围的数字: {row}")
return False
# 检查每一列
for i, col in enumerate(grid.T):
if len(set(col)) != grid_size:
print(f"验证结果: 错误 - 第{i+1}列包含重复数字: {col}")
return False
if not all(1 <= n <= grid_size for n in col):
print(f"验证结果: 错误 - 第{i+1}列包含超出范围的数字: {col}")
return False
# 验证区域规则
print("验证区域规则...")
for i, region in enumerate(regions):
print(f"检查区域 {i+1}: {region}")
# 获取区域中的单元格
cells = region["cells"]
numbers = []
# 提取数字
for cell in cells:
try:
if isinstance(cell, str):
match = re.search(r'\((\d+)\s*,\s*(\d+)\)', cell)
if match:
r,c = (int(match.group(1)), int(match.group(2)))
# r, c = cell # cell已经是元组了,直接解包
# 转换为0基索引
r, c = r-1, c-1
# 检查坐标是否有效
if r < 0 or r >= grid_size or c < 0 or c >= grid_size:
print(f"无效坐标: {r+1},{c+1}")
return False
numbers.append(grid[r][c])
except Exception as e:
print(f"提取数字时出错: {str(e)}")
return False
print(f"区域 {i+1} 中的数字: {numbers}")
# 检查数字是否有效(1到grid_size)
if not all(1 <= n <= grid_size for n in numbers):
print(f"区域中有无效数字: {numbers}")
return False
# 检查区域内数字是否唯一
if len(set(numbers)) != len(numbers):
print(f"区域中有重复数字: {numbers}")
return False
# 检查数字是否满足运算规则
target = region["target"]
operator = region["operator"]
try:
if operator == '+':
result = sum(numbers)
valid = (result == target)
print(f"加法验证: {numbers} = {result}, 目标 = {target}, 结果: {'通过' if valid else '失败'}")
if not valid:
return False
elif operator == '-':
if len(numbers) != 2:
print(f"减法操作数数量无效: {len(numbers)}")
return False
result1 = numbers[0] - numbers[1]
result2 = numbers[1] - numbers[0]
valid = (result1 == target or result2 == target)
print(f"减法验证: {numbers[0]}-{numbers[1]}={result1} 或 {numbers[1]}-{numbers[0]}={result2}, 目标 = {target}, 结果: {'通过' if valid else '失败'}")
if not valid:
return False
elif operator == '*':
result = np.prod(numbers)
valid = (result == target)
print(f"乘法验证: {numbers} = {result}, 目标 = {target}, 结果: {'通过' if valid else '失败'}")
if not valid:
return False
else: # division
if len(numbers) != 2:
print(f"除法操作数数量无效: {len(numbers)}")
return False
if numbers[0] == 0 or numbers[1] == 0:
print(f"除数为零: {numbers}")
return False
result1 = numbers[0] / numbers[1]
result2 = numbers[1] / numbers[0]
valid = (abs(result1 - target) < 0.001 or abs(result2 - target) < 0.001)
print(f"除法验证: {numbers[0]}÷{numbers[1]}={result1} 或 {numbers[1]}÷{numbers[0]}={result2}, 目标 = {target}, 结果: {'通过' if valid else '失败'}")
if not valid:
return False
except Exception as e:
print(f"检查运算时出错: {str(e)}")
return False
print("验证结果: 正确")
return True
except Exception as e:
print(f"验证时出错: {e}")
import traceback
traceback.print_exc()
return False
def main():
import argparse
import json
import os
parser = argparse.ArgumentParser(description='Verify Calcudoko solutions')
parser.add_argument('--grid_size', type=int, default=4, help='Size of the grid (N)')
parser.add_argument('--input_file', type=str, required=True, help='Path to the input JSONL file')
parser.add_argument('--output_file', type=str, help='Path to save valid samples (optional)')
args = parser.parse_args()
# Initialize verifier
verifier = CalcudokoVerifier()
total_puzzles = 0
valid_answers = 0
invalid_answers = 0
valid_samples = [] # 用于存储有效样本
def parse_regions_from_text(text):
"""从文本中解析regions信息"""
regions = []
# 查找Regions:后面的内容
regions_text = re.search(r'Regions:\n(.*?)(?:\n\n|$)', text, re.DOTALL)
if regions_text:
for line in regions_text.group(1).strip().split('\n'):
if line.strip():
# 解析每个region
cells_part, target = line.split(':')
# 使用正则表达式提取所有坐标对
cells = re.findall(r'\((\d+),(\d+)\)', cells_part)
# 将字符串转换为整数元组
cells = [tuple(map(int, pair)) for pair in cells]
# 处理target部分
target_value, operator = target[:-1], target[-1]
regions.append({
'cells': cells,
'target': int(target_value),
'operator': operator
})
return regions
try:
with open(args.input_file, 'r') as f:
for line in f:
try:
data = json.loads(line)
total_puzzles += 1
# 尝试从不同位置获取regions信息
regions = None
if 'regions' in data:
regions = data['regions']
elif 'regions' in data['metadata']:
regions = data['metadata']['regions']
elif 'question_data' in data:
question_data = json.loads(data['question_data'])
if 'regions' in question_data:
regions = question_data['regions']
elif 'question' in data:
regions = parse_regions_from_text(data['question'])
if not regions:
print(f"错误 - 无法找到regions信息")
continue
# 获取答案
answer = data.get('answer', '')
if not answer:
print(f"错误 - 无法找到答案")
continue
# 构造Data对象
data_obj = Data(
question=data['question'],
answer=answer, # 添加 answer 参数
metadata={'grid_size': args.grid_size, 'regions': regions}
)
# 验证答案
is_valid = verifier.verify(data_obj, answer)
if is_valid:
valid_answers += 1
# 将有效样本添加到列表中
valid_samples.append(data)
else:
invalid_answers += 1
print(f"验证失败 - 答案不符合规则")
print(f"模型答案: {answer}")
print(f"区域规则: {regions}\n")
except Exception as e:
print(f"处理答案时出错: {str(e)}")
invalid_answers += 1
except Exception as e:
print(f"读取文件时出错: {str(e)}")
return
# 保存有效样本
if args.output_file:
output_dir = os.path.dirname(args.output_file)
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir)
print(f"\n保存有效样本到: {args.output_file}")
with open(args.output_file, 'w', encoding='utf-8') as f:
for sample in valid_samples:
json.dump(sample, f, ensure_ascii=False)
f.write('\n')
print(f"成功保存 {len(valid_samples)} 个有效样本")
# 输出统计信息
print("\n结果统计:")
print(f"总题目数: {total_puzzles}")
print(f"有效答案数: {valid_answers}")
print(f"无效答案数: {invalid_answers}")
print(f"通过率: {(valid_answers/total_puzzles*100):.2f}%")
if __name__ == "__main__":
main()
# response = "[[2 3 4 6 1 5,1 2 3 4 5 6,3 1 2 5 6 4,4 5 6 1 2 3,5 6 1 3 4 2,6 4 5 2 3 1]]"
# metadata = {"grid_size": 6, "regions": [{"cells": ["(2,1)", "(1,1)", "(1,2)"], "target": 6, "operator": "*"}, {"cells": ["(3,5)", "(3,6)"], "target": 2, "operator": "-"}, {"cells": ["(1,5)", "(2,5)"], "target": 5, "operator": "÷"}, {"cells": ["(4,5)", "(4,4)"], "target": 2, "operator": "÷"}, {"cells": ["(5,6)", "(5,5)", "(6,5)"], "target": 9, "operator": "+"}, {"cells": ["(3,3)", "(2,3)", "(2,4)", "(3,4)"], "target": 120, "operator": "*"}, {"cells": ["(1,3)", "(1,4)"], "target": 2, "operator": "-"}, {"cells": ["(2,6)", "(1,6)"], "target": 1, "operator": "-"}, {"cells": ["(3,2)", "(3,1)", "(4,1)", "(5,1)"], "target": 60, "operator": "*"}, {"cells": ["(5,3)", "(5,2)", "(6,2)"], "target": 11, "operator": "+"}, {"cells": ["(5,4)", "(6,4)", "(6,3)"], "target": 10, "operator": "+"}, {"cells": ["(4,2)", "(4,3)"], "target": 1, "operator": "-"}, {"cells": ["(6,1)", "(2,2)"], "target": 3, "operator": "÷"}, {"cells": ["(6,6)", "(4,6)"], "target": 2, "operator": "-"}]}
# testClass = CalcudokoVerifier()
# dataClass = Data(question="", answer="")
# dataClass.metadata = metadata
# test_answer = testClass.parse_grid_from_answer(test_solution=response, data=dataClass)
# print(test_answer)
# print(testClass.verify(data=dataClass, test_answer=response))