斑马问题实验

实验五 斑马问题实验

一、实验背景

  1. 在本次实验中,我们学习将 Python 应用于逻辑编程,并尝试自主撰写逻辑规则解决斑马问题。
  2. 本章实验的主要目的是掌握逻辑与推理相关基础知识点,熟悉python编程。
  3. 能够依据给定的事实以及规则编写代码,解决逻辑约束问题。

二、实验简介

​ 5个不同国家且工作各不相同的人分别住在一条街上的5所房子里,每所房子的颜色不同,每个人都有自己养的不同宠物,喜欢喝不同的饮料。

​ 根据以下提示,你能告诉我哪所房子里的人养斑马,哪所房子里的人喜欢喝矿泉水吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
英国人住在红色的房子里
西班牙人养了一条狗
日本人是一个油漆工
意大利人喜欢喝茶
挪威人住在左边的第一个房子里
绿房子在白房子的左边
摄影师养了一只蜗牛
外交官住在黄房子里
中间那个房子的人喜欢喝牛奶
喜欢喝咖啡的人住在绿房子里
挪威人住在蓝色的房子旁边
小提琴家喜欢喝橘子汁
养狐狸的人所住的房子与医生的房子相邻
养马的人所住的房子与外交官的房子相邻

存在的条件汇总:

国籍:英国、西班牙、日本、意大利、挪威

房子颜色:红色、绿色、白色、蓝色、黄色

工作:油漆工、摄影师、外交官、小提琴家、医生

宠物:狗、蜗牛、狐狸、马、斑马

饮料:茶、牛奶、咖啡、橘子汁、矿泉水

三、实验步骤

​ 使用python逻辑编程kanren库,将题目中的条件形式化为一个个的表达式,加入约束到kanren的一个集合中,然后利用kanren内置函数run求解即可。

​ 根据已知条件的性质将条件分为三组:

1.第一组为属于一个实体的属性间的关系,有如下几个:

1
2
3
4
5
6
7
8
1.英国人住在红房子里
2.西班牙人养了一条狗
3.日本人是一个油漆工
4.意大利人喝茶。
7.摄影师养了一只蜗牛
8.外交官住在黄房子里
10.喜欢喝咖啡的人住在绿房子里
12.小提琴家喜欢喝橘子汁

​ kanren中加入一个逻辑表达式的方法:

  • 首先用houses = var() 创建一个逻辑变量houses。
  • 代码中的var表示任意变量。5个var()分别代表国籍、工作、饮料、宠物、屋子颜色
  • 然后利用membero函数表示一个约束, membero(item, houses) 表示 item是houses集合中的一个成员。如:

image-20241209152747529

2.第二组为表示实体在整体中的确切属性的条件

​ 添加方法为eq函数,eq(a,b) 表示ab相等。

1
2
5.挪威人住在左边的第一个房子里
9.中间房子的人喜欢喝牛奶

image-20241209153132087

3.第三组为描述两个实体间的不确定的(模糊的)关系的表达式

1
2
3
4
6.绿房子在白房子的左边
11.挪威人住在蓝色的房子旁边
13.养狐狸的人所住的房子与医生的房子相邻
14.养马的人所住的房子与外交官的房子相邻

​ 在kanren中没有能够直接描述以上关系的函数,所以需要人为定义

​ 实现的函数:

​ left(q,p,x)表示在关系集合x中q在p的左边

​ next(q,p,x)表示在关系集合x中q和p相邻

image-20241209153241032

​ 借助以上两个函数就可以将对应的四个逻辑表达式表达出来了

image-20241209153258415

​ 到这里所有的表达式都已经表达完毕,已经可以利用run运行了,但是此时的表达式中还没有斑马和矿泉水这两个变量,程序处理到他们时会用符号代替,下面添加这两个变量进入表达式:

image-20241209153316952

最后运行规则代码:

image-20241209153325516

四、实验代码总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
from kanren import *
from kanren.core import lall
import time

def left(q, p, list):
return membero((q, p), zip(list, list[1:]))
def right_of(q, p, list):
return left(p, q, list)
def next_to(q, p, list):
return conde([left(q, p, list)], [right_of(q, p, list)])
class Agent:
def __init__(self):
self.units = var() # 单个unit变量指代一座房子的信息(国家,工作,饮料,宠物,颜色)
# 例如('英国人', '油漆工', '茶', '狗', '红色')即为正确格式,但不是本题答案
# 请基于给定的逻辑提示求解五条正确的答案
self.rules_zebraproblem = None # 用lall包定义逻辑规则
self.solutions = None # 存储结果
def define_rules(self):
self.rules_zebraproblem = lall(
(eq, (var(), var(), var(), var(), var()), self.units),
# self.units共包含五个unit成员,即每一个unit对应的var都指代一座房子(国家,工作,饮料,宠物,颜色)
# 各个unit房子又包含五个成员属性: (国家,工作,饮料,宠物,颜色)
(membero, ('英国人', var(), var(), var(), '红色'), self.units),
(membero, ('西班牙人', var(), var(), '狗', var()), self.units),
(membero, ('日本人', '油漆工', var(), var(), var()), self.units),
(membero, ('意大利人', var(), '茶', var(), var()), self.units),
(eq, (('挪威人', var(), var(), var(), var()), var(), var(), var(), var()), self.units),
(right_of, (var(), var(), var(), var(), '绿色'), (var(), var(), var(), var(), '白色'),self.units),
(membero, (var(), '摄影师', var(), '蜗牛', var()), self.units),
(membero, (var(), '外交官', var(), var(), '黄色'), self.units),
(eq, (var(), var(), (var(), var(), '牛奶', var(), var()), var(), var()), self.units),
(membero, (var(), var(), '咖啡', var(), '绿色'), self.units),
(next_to, ('挪威人', var(), var(), var(), var()), (var(), var(), var(), var(), '蓝色'), self.units),
(membero, (var(), '小提琴家', '橘子汁', var(), var()), self.units),
(next_to, (var(), var(), var(), '狐狸', var()), (var(), '医生', var(), var(), var()), self.units),
(next_to, (var(), var(), var(), '马', var()), (var(), '外交官', var(), var(), var()), self.units),
(membero, (var(), var(), '矿泉水', var(), var()), self.units),
(membero, (var(), var(), var(), '斑马', var()), self.units),
)
def solve(self):
self.define_rules()
self.solutions = run(0, self.units, self.rules_zebraproblem)
return self.solutions
if __name__ == '__main__':
agent = Agent()
solutions = agent.solve()
# 提取解释器的输出
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))
print("\n")
# 解释器的输出结果展示
for i in solutions[0]:
print(i)

运行代码截图

image-20241209153531897

五、实验总结

​ kanren库能够起到按照指定的表达式寻找满足表达式的值,下面是常用的kanren函数及其常用用法。

​ eq(u,v)创建一个等式约束,表示变量a等于变量b,通常只是表达一个等式关系,返回值类型为一个逻辑表达式(函数)。

​ var()用于创建逻辑变量,可以绑定到其他逻辑变量或常量。

​ run(n,x,*goals)函数用于进行逻辑推理,返回指定个满足约束的变量绑定,返回值类型为python列表,列表的每个元素是字典。

​ 函数参数列表中n表示返回个数,x表示想要得到的逻辑变量,*goals指的是约束条件,约束条件可以有多个。