init-commit

This commit is contained in:
lilinyang 2025-05-23 15:27:15 +08:00
commit 18a552597a
3461 changed files with 1150579 additions and 0 deletions

View file

@ -0,0 +1,261 @@
import random
import string
def generate_crypto_math(num_letters, num_puzzles, num_add=2):
"""
生成 Crypto-math puzzle 对的函数可指定加号数量 (num_add)
:param num_letters: int
表示题目中所有字母的数量即所用到的独立字母总数
:param num_puzzles: int
需要生成的 puzzle question-answer 的数量
:param num_add: int
题目里的加号数量即加数个数例如:
- num_add=2 -> X + Y = Z
- num_add=3 -> X + Y + W = Z
...
:return: list
返回一个列表列表元素为 {"question": question_str, "answer": answer_str} 形式的字典
"""
# 若所需字母数超过可用数字(0-9)数量,直接返回空列表
if num_letters > 10:
return []
results = []
seen_questions = set() # 用于去重
while len(results) < num_puzzles:
# 1. 随机生成 num_add 个加数
addends = [random.randint(1, 999) for _ in range(num_add)]
Z = sum(addends) # 结果
# 2. 提取所有数字
union_digits = set()
for val in addends:
union_digits |= set(str(val))
union_digits |= set(str(Z))
# 如果数字种类不等于所需字母数,则跳过
if len(union_digits) != num_letters:
continue
# 3. 将数字随机映射到字母
# - 随机选出 num_letters 个字母
letters_pool = random.sample(string.ascii_uppercase, num_letters)
union_digits_list = list(union_digits)
random.shuffle(union_digits_list)
digit_to_letter = {}
for i, d in enumerate(union_digits_list):
digit_to_letter[d] = letters_pool[i]
# 4. 构造 question
def num_to_letters(num):
"""将整数 num 转为对应的字母串"""
return ''.join(digit_to_letter[d] for d in str(num))
# 拼接加数部分 (e.g. "AB + CD + EF")
addends_str = "+".join(num_to_letters(val) for val in addends)
question_str = f"{addends_str}={num_to_letters(Z)}"
# 去重检测
if question_str in seen_questions:
continue
# 5. 构造 answer字母->数字)
letter_to_digit = {v: k for k, v in digit_to_letter.items()}
sorted_letters = sorted(letter_to_digit.keys()) # 按字母排序
answer_str = "[[" + ",".join(f"{letter}={letter_to_digit[letter]}"
for letter in sorted_letters) + "]]"
puzzle_dict = {
"puzzle": question_str,
"target": answer_str,
"num_add": num_add,
"num_letters": num_letters
}
# 记录并保存
seen_questions.add(question_str)
results.append(puzzle_dict)
return results
# # ============= 测试示例 =============
# if __name__ == "__main__":
# # 生成 3 道 puzzle每道 puzzle 中有 3 个加数(num_add=3),并且题目中恰好有 4 个独立字母(num_letters=4)
# puzzles = generate_crypto_math(num_letters=10, num_puzzles=10, num_add=20)
# for idx, p in enumerate(puzzles, start=1):
# print(f"Puzzle {idx}:")
# question = p["puzzle"]
# answer = p["target"]
# print(" Question:", question)
# print(" Answer: ", answer)
# answer = {item.split('=')[0]: int(item.split('=')[1]) for item in answer[2:-2].split(",")}
# for letter, digit in answer.items():
# question = question.replace(letter, str(digit))
# print(" Replaced question:", question)
# print(p)
# try:
# eval_res = eval(question.replace('=', '=='))
# except:
# print(" Eval failed!")
# eval_res = False
# if not eval_res:
# print(" Check failed!")
# break
# print()
import itertools
def solve_crypto_math(question, max_iterations=2_000_000):
"""
对给定的 Crypto-math puzzle ( "A+BC=DB") 进行求解
如果有解返回形如 "[[A=7,B=2,C=5,D=3]]" 的字符串无解则返回 False
:param question: str, 形如 "A+BC=DB" "ONE+ONE+TWO=FOUR"
:param max_iterations: int, 限制最大尝试次数防止极端情况下计算过长
:return: str bool
"""
print(f"[Step 0] 准备开始求解题目: {question}")
# 1. 解析题目
if '=' not in question:
print("[Step 1] 解析失败:题目中没有 '=',返回 False")
return False
left_side, right_side = question.split('=')
left_side = left_side.strip()
right_side = right_side.strip()
print(f"[Step 1] 成功用 '=' 将题目分为left_side='{left_side}'right_side='{right_side}'")
left_terms = left_side.split('+') # 左侧有多个加数
# 这里假设右侧只有一个整体表达式,如 "DB" 或 "FOUR"
# 如果右侧也可能有多个加数,可以类似地再 split('+')
right_term = right_side
print(f"[Step 2] 左侧加数列表 = {left_terms}")
print(f"[Step 2] 右侧表达式 = {right_term}")
# 2. 收集所有出现的字母 & 首字母
# (用于确保多位数首字母不为 0
letters_set = set()
leading_letters = set()
def analyze_term(term):
"""
term 为字符串 'ABC'
收集其中的所有字母并找出首字母
"""
term = term.strip()
for ch in term:
if ch.isalpha():
letters_set.add(ch)
# 若是多位数或至少 2 位,则把首字母记录为 leading letter
if len(term) > 1 and term[0].isalpha():
leading_letters.add(term[0])
# 分析左侧
for t in left_terms:
analyze_term(t)
# 分析右侧
analyze_term(right_term)
letters = sorted(letters_set) # 收集到的全部字母并排序
print(f"[Step 3] 收集到的所有字母 = {letters}")
print(f"[Step 3] 需要确保以下首字母 != 0: {sorted(list(leading_letters))}")
# 如果字母数量大于10无法映射到 0-9 的唯一数字
if len(letters) > 10:
print(f"[Step 3] 字母数量 = {len(letters)},超过 10 个,不可能一一映射到数字 0-9。返回 False")
return False
# 3. 准备求解
# 定义一个小函数,将某个映射应用到字符串上,返回对应的数字值
def term_value(term, mapping):
"""
将类似 'ABC' 根据 mapping 替换成数字然后转换为 int
比如 'ABC' -> '123' -> int(123)
"""
return int(''.join(str(mapping[ch]) for ch in term if ch.isalpha()))
# 4. 穷举letters 的所有数字排列
# 注意 leading_letters 不能为 0
# leading_letters 可能是集合,如 {'A', 'B', 'D'} => 对应的映射必须 > 0
attempt_count = 0
print(f"[Step 4] 准备开始尝试所有可能的字母->数字排列…(最多 {max_iterations} 次)")
for perm in itertools.permutations(range(10), len(letters)):
attempt_count += 1
if attempt_count > max_iterations:
# 超过给定的迭代上限,直接返回 False 以防无限拖延
print(f"[Step 4] 已尝试超过 {max_iterations} 种排列,仍未找到解,返回 False")
return False
# 构造 letter -> digit 映射
mapping = {}
for i, letter in enumerate(letters):
mapping[letter] = perm[i]
# 如果有任何 leading letter 被映射成了 0跳过
if any(mapping[ld] == 0 for ld in leading_letters):
continue
# 为了演示,可以在每隔一定数量的 permutation 时打印提示
# if attempt_count % 10000 == 0:
print(f"[Progress] 已尝试 {attempt_count} 次排列,当前映射示例 = {mapping}")
# 计算左侧总和
try:
left_sum = sum(term_value(t, mapping) for t in left_terms)
right_val = term_value(right_term, mapping)
except ValueError:
# 若转换出错,跳过(一般不会发生,防御性写法)
print(f"[Warning] 第 {attempt_count} 次尝试转换数值时出错,跳过。映射 = {mapping}")
continue
# 打印一下当前计算值(可以注释掉或更精简)
# print(f"尝试第 {attempt_count} 次映射: {mapping}, 左边和= {left_sum}, 右边= {right_val}")
if left_sum == right_val:
# 找到可行解,返回
print(f"[Success] 在第 {attempt_count} 次尝试中找到解: 左侧 {left_sum} = 右侧 {right_val}")
letter_to_digit_strs = []
for letter in sorted(mapping.keys()):
letter_to_digit_strs.append(f"{letter}={mapping[letter]}")
answer_str = "[[" + ",".join(letter_to_digit_strs) + "]]"
print(f"[Step 5] 返回解: {answer_str}")
return answer_str
# 所有排列尝试完都没找到可行解,返回 False
print(f"[Step 6] 穷举完所有排列也没能找到可行解,返回 False")
return False
# # ==================== 测试示例 ====================
# if __name__ == "__main__":
# # SYL+JAT+FOO+TJJ+KJF+OA+OLL+TSS+JK+FYR+JAS+TTT+OFY+AOO+JFR+FJS+YJT+JSL+LSA+OTF=LLKTT -> [[A=5,F=6,J=4,K=2,L=1,O=9,R=0,S=3,T=7,Y=8]]
# # 381+457+699+744+246+95+911+733+42+680+453+777+968+599+460+643+847+431+135+976=11277
# for puzzle in ["A+BC=DB"]: # , "A+BB=A", "ONE+ONE+TWO=FOUR", "SYL+JAT+FOO+TJJ+KJF+OA+OLL+TSS+JK+FYR+JAS+TTT+OFY+AOO+JFR+FJS+YJT+JSL+LSA+OTF=LLKTT"
# sol = solve_crypto_math(puzzle, max_iterations=2_000_000)
# print(puzzle, "->", sol)
# if sol != False:
# sol = {item.split('=')[0]: int(item.split('=')[1]) for item in sol[2:-2].split(",")}
# for letter, digit in sol.items():
# puzzle = puzzle.replace(letter, str(digit))
# try:
# eval_res = eval(puzzle.replace('=', '=='))
# except:
# print(" Eval failed!")
# eval_res = False
# if not eval_res:
# print(" Check failed!")
# break
# print(puzzle)
# print()