一、问题描述
5 个不同国家 (英国、西班牙、日本、意大利、挪威) 且工作各不相同 (油漆工、摄影师、外交官、小提琴家、医生) 的人分别住在一条街上的 5 所房子里,每所房子的颜色不同 (红色、白色、蓝色、黄色、绿色),每个人都有自己养的不同宠物 (狗、马、狐狸),喜欢喝不同的饮料 (ERK, PW, ZR, RB, OE)。
根据以下提示,你能告诉我哪所房子里的人养斑马,哪所房子里的人喜欢喝矿泉水吗?
1. 英国人住在红色的房子里
2. 西班牙人养了一条狗
3. 日本人是一个油漆工
4. 意大利人喜欢喝茶
5. 挪威人住在左边的第一个房子
6. 绿房子在白房子的右边
7. 摄影师养了一只蜗牛
8. 外交官住在黄房子里
9. 中间那个房子的人喜欢喝牛奶
10. 喜欢喝咖啡的人住在绿房子里
11. 挪威人住在蓝色的房子旁边
12. 小提琴家喜欢喝橘子汁
13. 养狐狸的人所住的房子与医生的房子相邻
14. 养马的人所住的房子与外交官的房子相邻
问题核心:
通过逻辑推理,确定每所房子的属性(国家、职业、饮料、宠物、颜色),并找到养斑马和喜欢喝矿泉水的人在哪里。
二、设计思想
1. 采用的方法
采取逻辑编程的方法,使用Python的 kanren
包来定义逻辑规则并求解问题。逻辑编程是一种编程典范,它设置答案须匹配的规则来解决问题,而非设置步骤哦来解决问题。过程是“事实+规则=结果”。
2. 改进与优化
-
临近规则的实现
-
使用
left
、right
和next
函数来表示房子之间的位置关系,确保逻辑约束的精确性。
-
-
唯一性约束
-
通过
membero
确保每个属性(国家、职业、饮料、宠物、颜色)在五所房子中唯一。
-
-
通过阅读
kanren
库的源代码,其实现逻辑推理的主要方法是 穷举 与 回溯,生成所有可能的解,然后使用统一和逻辑目标组合来过滤合适的解。即-
穷举:
kanren
会在给定的规则和事实的1基础上,试图找到所有可能的解,并将其存储为流式输出。 -
回溯:当遇到不满足条件的结果的时候,会回溯并尝试其他可能的解,以此确保找到所有可能符合条件的解。
-
因此,使用
kanren
在进行逻辑的时候,不同的逻辑规则顺序对kanren
包的推理时间有显著的影响。将约束性较强的规则放在前面,会减少一定的推理时间。例如将约束性较强的规则放在前面
# 约束较强的规则在前 # 5. 挪威人住在左边的第一个房子里 (eq, house1, ('挪威人', var(), var(), var(), var())), # 9. 中间那个房子的人喜欢喝牛奶 (eq, house3, (var(), var(), '牛奶', var(), var())), # 1. 英国人住在红色的房子里 (membero, ('英国人', var(), var(), var(), '红色'), self.units), # 6. 绿房子在白房子的右边 (next, (var(), var(), var(), var(), '绿色'), (var(), var(), var(), var(), '白色'), self.units), # 其他规则
经过测试所用时间如下:
代码运行时间: 2.7001 秒 绿色房子里的人养斑马 黄色房子里的人喜欢喝矿泉水 ('挪威人', '外交官', '矿泉水', '狐狸', '黄色') ('意大利人', '医生', '茶', '马', '蓝色') ('英国人', '摄影师', '牛奶', '蜗牛', '红色') ('日本人', '油漆工', '咖啡', '斑马', '绿色') ('西班牙人', '小提琴家', '橘子汁', '狗', '白色')
测试五次所需要的时间如下:
第一次 第二次 第三次 第四次 第五次 平均值 2.7001 2.7974 2.7053 2.8735 2.8607 2.78740 而使用原先的顺序进行逻辑推理,测试五次所需要的时间如下表所示:
第一次 第二次 第三次 第四次 第五次 平均值 3.7872 3.5496 3.7617 3.5045 3.8671 3.69402 从数据中可以看出,调整约束条件的顺序对程序执行时间有相当大的影响,约束性强的条件放在前面对于整体的时间有很大的优化。求解的过程中会快速缩小搜索空间,减少不必要的尝试。
再次进行顺序的调换,调换的结果如下:
# 约束较强的规则在前 # 5. 挪威人住在左边的第一个房子里 (eq, house1, ('挪威人', var(), var(), var(), var())), # 9. 中间那个房子的人喜欢喝牛奶 (eq, house3, (var(), var(), '牛奶', var(), var())), # 11. 挪威人住在蓝色的房子旁边 (next, house1, (var(), var(), var(), var(), '蓝色'), self.units), # 1. 英国人住在红色的房子里 (membero, ('英国人', var(), var(), var(), '红色'), self.units), # 6. 绿房子在白房子的右边 (next, (var(), var(), var(), var(), '绿色'), (var(), var(), var(), var(), '白色'), self.units),
这次调换后得到的五次运行时间为:
第一次 第二次 第三次 第四次 第五次 平均值 0.9106 0.9173 0.9864 0.8901 0.8057 0.90202 代码的运行速度提升显著,也应证了
kanren
的推理顺序与约束条件所给的顺序有关
-
三、代码内容
-
逻辑推理智能体
from kanren import run, eq, membero, var, conde # kanren一个描述性Python逻辑编程系统 from kanren.core import lall # lall包用于定义规则 import time # def left(q, p, list): """定义左邻近规则,q在p的左边""" return membero((q, p), zip(list, list[1:])) def right(q, p, list): """定义右邻近规则,q在p的右边""" return membero((p, q), zip(list, list[1:])) def next(q, p, list): """定义邻近规则,q与p相邻""" return conde([left(q, p, list)], [left(p, q, list)]) class Agent: """ 推理智能体. """ def __init__(self): """ 智能体初始化. """ self.units = var() # 单个unit变量指代一座房子的信息(国家,工作,饮料,宠物,颜色) # 例如('英国人', '油漆工', '茶', '狗', '红色')即为正确格式,但不是本题答案 # 请基于给定的逻辑提示求解五条正确的答案 self.rules_zebraproblem = None # 用lall包定义逻辑规则 self.solutions = None # 存储结果 def define_rules(self): """ 定义逻辑规则. """ # 定义变量 house1 = var() house2 = var() house3 = var() house4 = var() house5 = var() # 定义五所房子 self.units = (house1, house2, house3, house4, house5) # 定义逻辑规则 self.rules_zebraproblem = lall( # 1. 英国人住在红色的房子里 (membero, ('英国人', var(), var(), var(), '红色'), self.units), # 2. 西班牙人养了一条狗 (membero, ('西班牙人', var(), var(), '狗', var()), self.units), # 3. 日本人是一个油漆工 (membero, ('日本人', '油漆工', var(), var(), var()), self.units), # 4. 意大利人喜欢喝茶 (membero, ('意大利人', var(), '茶', var(), var()), self.units), # 5. 挪威人住在左边的第一个房子里 (eq, house1, ('挪威人', var(), var(), var(), var())), # 6. 绿房子在白房子的右边 (right, (var(), var(), var(), var(), '绿色'), (var(), var(), var(), var(), '白色'), self.units), # 7. 摄影师养了一只蜗牛 (membero, (var(), '摄影师', var(), '蜗牛', var()), self.units), # 8. 外交官住在黄房子里 (membero, (var(), '外交官', var(), var(), '黄色'), self.units), # 9. 中间那个房子的人喜欢喝牛奶 (eq, house3, (var(), var(), '牛奶', var(), var())), # 10. 喜欢喝咖啡的人住在绿房子里 (membero, (var(), var(), '咖啡', var(), '绿色'), self.units), # 11. 挪威人住在蓝色的房子旁边 (next, house1, (var(), var(), var(), var(), '蓝色'), self.units), # 12. 小提琴家喜欢喝橘子汁 (membero, (var(), '小提琴家', '橘子汁', var(), var()), self.units), # 13. 养狐狸的人所住的房子与医生的房子相邻 (next, (var(), var(), var(), '狐狸', var()), (var(), '医生', var(), var(), var()), self.units), # 14. 养马的人所住的房子与外交官的房子相邻 (next, (var(), var(), var(), '马', var()), (var(), '外交官', var(), var(), var()), self.units), # 确保每所房子的属性唯一 (membero, ('英国人', var(), var(), var(), var()), self.units), (membero, ('西班牙人', var(), var(), var(), var()), self.units), (membero, ('日本人', var(), var(), var(), var()), self.units), (membero, ('意大利人', var(), var(), var(), var()), self.units), (membero, ('挪威人', var(), var(), var(), var()), self.units), (membero, (var(), '油漆工', var(), var(), var()), self.units), (membero, (var(), '摄影师', var(), var(), var()), self.units), (membero, (var(), '外交官', var(), var(), var()), self.units), (membero, (var(), '小提琴家', var(), var(), var()), self.units), (membero, (var(), '医生', var(), var(), var()), self.units), (membero, (var(), var(), '矿泉水', var(), var()), self.units), (membero, (var(), var(), '茶', var(), var()), self.units), (membero, (var(), var(), '牛奶', var(), var()), self.units), (membero, (var(), var(), '咖啡', var(), var()), self.units), (membero, (var(), var(), '橘子汁', var(), var()), self.units), (membero, (var(), var(), var(), '狗', var()), self.units), (membero, (var(), var(), var(), '蜗牛', var()), self.units), (membero, (var(), var(), var(), '狐狸', var()), self.units), (membero, (var(), var(), var(), '马', var()), self.units), (membero, (var(), var(), var(), '斑马', var()), self.units), (membero, (var(), var(), var(), var(), '红色'), self.units), (membero, (var(), var(), var(), var(), '白色'), self.units), (membero, (var(), var(), var(), var(), '蓝色'), self.units), (membero, (var(), var(), var(), var(), '黄色'), self.units), (membero, (var(), var(), var(), var(), '绿色'), self.units), ) def solve(self): """ 规则求解器(请勿修改此函数). return: 斑马规则求解器给出的答案,共包含五条匹配信息,解唯一. """ self.define_rules() self.solutions = run(0, self.units, self.rules_zebraproblem) return self.solutions
-
逻辑推导
agent = Agent() solutions = agent.solve() # 提取解释器的输出 if solutions: output = [house for house in solutions[0] if '斑马' in house][0][4] print('\n{}房子里的人养斑马'.format(output)) output = [house for house in solutions[0] if '矿泉水' in house][0][4] print('{}房子里的人喜欢喝矿泉水'.format(output)) # 解释器的输出结果展示 for i in solutions[0]: print(i) else: print("未找到满足条件的解。")
四、实验结果
绿色房子里的人养斑马 黄色房子里的人喜欢喝矿泉水 ('挪威人', '外交官', '矿泉水', '狐狸', '黄色') ('意大利人', '医生', '茶', '马', '蓝色') ('英国人', '摄影师', '牛奶', '蜗牛', '红色') ('日本人', '油漆工', '咖啡', '斑马', '绿色') ('西班牙人', '小提琴家', '橘子汁', '狗', '白色')
五、总结
-
可能改进的方向、性能的提升
-
跟换更加合理的约束条件的顺序,可能会得到更好的结果。找到最佳的规则顺序
-
可以利用并行计算来加速求解过程
-
可以结合
kanren
库,对其中的规则进行扩展,便于后续的复用
-