如何简化CLP逻辑谜题中的约束条件表达实现?
逻辑谜题Prolog约束求解优化方案
谜题背景
当地游戏夜活动上,四名年轻人参与了拼字游戏(Scrabble)和国际象棋两个项目的角逐:国际象棋项目中利亚姆(Liam)击败了马克(Mark),詹姆斯(James)获得第三名,16岁的选手夺冠;拼字游戏项目中利亚姆获得第二名,15岁的选手夺冠,詹姆斯击败了18岁的选手,19岁的选手获得第三名;凯文(Kevin)比马克小3岁;国际象棋排名垫底的选手在拼字游戏中获得第三名,且仅有一名选手在两个项目中的排名完全相同。
现有代码的问题
你当前调整后的版本已经用permutation/2简化了域声明和全不同约束,但还存在明显可优化的点,以及隐藏错误:
- 存在笔误:国际象棋排名范围为1~4,约束中错误写了
CL #= 5,该分支永远无法满足;最早版本漏写了AM的年龄域,重复声明了AK的年龄域 - 所有「某年龄选手获得某排名」的条件都用硬编码4个分支的枚举写法,冗余且可读性差
- 「仅有一名选手两个项目排名相同」的约束硬编码4种分支,写法死板
- 遗漏了拼字游戏中「詹姆斯击败了18岁的选手」的核心约束,会导致结果不符合题目要求
优化思路
- 用列表统一映射人名和对应属性,用CLPFD的
element/3替代枚举分支,简化属性和排名的对应约束 - 用
sum/3统计两个项目排名相等的人数,直接约束为1,替代硬编码分支 - 修正所有笔误,补充遗漏的约束
优化后完整代码
:- use_module(library(clpfd)). solve() :- % 列表顺序对应4个人:詹姆斯(J)、凯文(K)、利亚姆(L)、马克(M) Age = [AJ, AK, AL, AM], ChessRank = [CJ, CK, CL, CM], ScrabbleRank = [SJ, SK, SL, SM], % 基础约束:年龄、排名都是指定值的全排列 permutation(Age, [15,16,18,19]), permutation(ChessRank, [1,2,3,4]), permutation(ScrabbleRank, [1,2,3,4]), % 国际象棋规则约束 CL #< CM, % 利亚姆击败马克 CJ #= 3, % 詹姆斯第三名 element(ChessChampionIdx, Age, 16), element(ChessChampionIdx, ChessRank, 1), % 16岁选手夺冠 element(ChessLastIdx, ChessRank, 4), element(ChessLastIdx, ScrabbleRank, 3), % 象棋垫底的选手拼字获第三 % 拼字游戏规则约束 SL #= 2, % 利亚姆获第二名 element(ScrabbleChampionIdx, Age, 15), element(ScrabbleChampionIdx, ScrabbleRank, 1), % 15岁选手夺冠 element(ScrabbleThirdIdx, Age, 19), element(ScrabbleThirdIdx, ScrabbleRank, 3), % 19岁选手获第三名 element(Age18Idx, Age, 18), element(Age18Idx, ScrabbleRank, S18), SJ #< S18, % 詹姆斯击败18岁选手 % 通用规则约束 AK #= AM - 3, % 凯文比马克小3岁 % 仅1名选手两个项目排名完全相同 maplist(#=, ChessRank, ScrabbleRank, EqList), sum(EqList, #=, 1), % 求解并输出结果 label(Age), label(ChessRank), label(ScrabbleRank), format('詹姆斯:年龄~d,象棋第~d,拼字第~d~n', [AJ, CJ, SJ]), format('凯文:年龄~d,象棋第~d,拼字第~d~n', [AK, CK, SK]), format('利亚姆:年龄~d,象棋第~d,拼字第~d~n', [AL, CL, SL]), format('马克:年龄~d,象棋第~d,拼字第~d~n', [AM, CM, SM]).
优化说明
- 用
element/3约束某年龄的选手对应某排名,不用再枚举4个人的变量,代码更简洁,修改调整也更方便 - 用
maplist和sum组合实现“仅一个排名相同”的约束,逻辑更清晰,不需要硬编码所有分支 - 补充了遗漏的拼字游戏约束,同时修正了原代码的笔误,可直接运行得到唯一正确解
内容的提问来源于stack exchange,提问作者vasily
相关产品推荐
相关产品推荐

