斑马问题多方法求解

人工智能导论实验导航

实验一:斑马问题 https://blog.csdn.net/weixin_46291251/article/details/122246347

实验二:图像恢复 https://blog.csdn.net/weixin_46291251/article/details/122561220

实验三:花卉识别 https://blog.csdn.net/weixin_46291251/article/details/122561505

实验四:手写体生成 https://blog.csdn.net/weixin_46291251/article/details/122576478

实验源码: xxx

1.1实验介绍

1.1.1实验背景

演绎推理(Deductive Reasoning)是由一般到特殊的推理方法。与“归纳法”相对。推论前提与结论之间的联系是必然的,是一种确实性推理。
运用此法研究问题,首先要正确掌握作为指导思想或依据的一般原理、原则;其次要全面了解所要研究的课题、问题的实际情况和特殊性;然后才能推导出一般原理用于特定事物的结论。
演绎推理的形式有三段论、假言推理和选言推理等。在教育工作中, 依据一定的科学原理设计和进行教育与教学实验等,均离不开此法。

1.1.2实验目的

本章实验的主要目的是掌握逻辑与推理相关基础知识点,了解算法推理相关基础知识,掌握归纳法和演绎推理的主要步骤,熟悉python编程。

通过实验,基本掌握逻辑编程的思想,了解逻辑编程与命令式编程的区别。能够依据给定的事实以及规则编写代码,解决逻辑约束问题(CLP)。

1.1.3实验简介

5个不同国家且工作各不相同的人分别住在一条街上的5所房子里,每所房子的颜色不同,每个人都有自己养的不同宠物,喜欢喝不同的饮料。
根据以下提示,你能告诉我哪所房子里的人养斑马,哪所房子里的人喜欢喝矿泉水吗?

存在的条件汇总:

  • 国籍:英国、西班牙、日本、意大利、挪威
  • 房子颜色:红色、绿色、白色、蓝色、黄色
  • 工作:油漆工、摄影师、外交官、小提琴家、医师
  • 宠物:狗、蜗牛、狐狸、马、斑马
  • 饮料:茶、牛奶、咖啡、橘子汁、矿泉水

已知条件如下:

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

1.2概要设计

1.2.1手动求解

  • 1.罗列初始条件。

  • 2.首先由“英国人住在红色的房子里,绿房子在白房子的右边,外交官住在黄房子里”以及上述条件可知:
    a:红白绿房子必在右边三个房子。
    b:挪威人必住在黄房子且他是外交官
    c:红房子要么是第三间,要么是第五间。

  • 3.然后假设在红房子在第三间外交官,可得:
    a.由表格知挪威人不喝咖啡和牛奶,又意大利喝茶,小提琴家喝桔子汁(挪威人是外交官),所以挪威人喝矿泉水。
    b.由小提琴家和桔子汁知,小提琴家不是英国人(喝牛奶),挪威人(矿泉水),意大利人(茶),日本人(油漆工),所以小提琴家是西班牙人。

  • 4.结合上述分析可得
    a.由“养狐狸的人所住的房子与医师的房子相邻”知,医师不可能在绿色和红色房子(相邻宠物不为狐狸),也不可能在黄色房子(外交官),所以医师是蓝色房子。
    b.由外交官,小提琴,油漆工的国籍已定,且医师不是英国人(蓝色房子),知摄影师是英国人,养了蜗牛。所以挪威人养了狐狸(养狐狸的人所住的房子与医师的房子相邻)。
    c.狐狸,马,蜗牛,狗的主人身份已确定,知斑马是油漆工,即日本人的宠物。
    d.由矿泉水,牛奶,桔子汁,咖啡主人已确定,且意大利人喝茶,知意大利人所住房子为蓝色。

  • 5.综合以上分析可得下面的表格:
    在这里插入图片描述

1.2.2 python求解

通过上述分析,结合python求解逻辑推理问题的常见库,得到以下思路:

解法1:穷举法

将所有可能的组合通过全排列的形式穷举出来
然后根据已有的条件依次匹配
若全部通过则为正确答案
否则为错误方案

解法2:

利用google or-tools 。
OR-Tools是一个用于优化的开源软件套件,用于解决车辆路径、流程、整数和线性规划以及约束编程等世界上最棘手的问题。
当前ortools提供的优化器包括: - 约束规划 - 线性与混合整数规划 - 路径规划 - 调度规划 - 网络规划 - 装箱。
并且在其GitHub的源代码中有解决斑马问题的示例代码,地址如下:
https://github.com/google/or-tools/blob/master/examples/python/zebra_sat.py

解法3:

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

综上:

通过上述对三种方法的分析可知,方法1过于简单粗暴,能够简单的解决问题,但是算法的复杂度太高,不可取。
方法二和方法三都是通过调用python对应的逻辑分析库完成,但鉴于方法2的google or-tools 官方已有较为完善的示例代码,与独立完成实验的要求不符,故采用方法3。下面只给出方法3的解法设计与分析。

1.3详细设计

首先导入实验所需的包

from kanren import *
from kanren.core import lall

下面根据已知条件的性质将条件分为几组,首先有着确定关系的实体和其属性的条件,以及同属于一个实体的属性间的关系,有如下几个:

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

然后介绍kanren中加入一个逻辑表达式的方法:

首先用houses = var() 创建一个逻辑变量house。
然后利用membero函数表示一个约束, membero(item, coll) 表示 item 是 coll集合中的一个成员。如:

(membero, ('Britain', var(), var(), var(), 'red'), houses),  # 英国人住在红房子里

代码中的var表示任意变量。
5个var()分别代表 国籍、工作、饮料、宠物、屋子颜色

(membero, ('Britain', var(), var(), var(), 'red'), houses),  # 英国人住在红房子里

(membero, ('Spain', var(), var(), 'dog', var()), houses),  # 西班牙人养了一条狗

(membero, ('Japan', 'painter', var(), var(), var()), houses),  # 日本人是一个油漆工

(membero, ('Italy', var(), 'tea', var(), var()), houses),  # 意大利人喝茶。

(membero, (var(), 'Photographer', var(), 'snail', var()), houses),  # 摄影师养了一只蜗牛

(membero, (var(), 'diplomat', var(), var(), 'yellow'), houses),  # 外交官住在黄房子里

(membero, (var(), var(), 'coffee', var(), 'green'), houses),  # 喜欢喝咖啡的人住在绿房子里

(membero, (var(), 'Violinist', 'juice', var(), var()), houses),  # 小提琴家喜欢喝橘子汁

然后是表示实体在整体中的确切属性的条件,有以下两个:

  • 挪威人住在左边的第一个房子里
  • 中间房子的人喜欢喝牛奶

添加方法和第一种一样,不一样的是这里用的是eq函数,eq(a,b ) 表示ab相等。

(eq, (('Norwegian', var(), var(), var(), var())
      , var(), var(), var(), var()), houses),  # 挪威人住在左边的第一个房子里

(eq, (var(), var(),
      (var(), var(), 'milk', var(), var()),
      var(), var()), houses),  # 中间那个房子的人喜欢喝牛奶

然后是描述两个实体间的不确定的(模糊的)关系的表达式,有以下几个:

  • 绿房子在白房子的右边
  • 挪威人住在蓝房子旁边。
  • 养狐狸的人所住的房子与医生的房子相邻
  • 养马的人所住的房子与外交官的房子相邻

但是在kanren中没有能够直接描述以上关系的函数,所以定义了以下两个函数,用来实现上述要求:
其中利用到的kanren内的函数:

  • zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。
  • CONDE:一个用于逻辑和/或OR的目标构造函数。

实现的函数:

Left(a,b,x)表示在关系集合x中a在b的右侧
Next(a,b,x)表示在关系集合x中a和b相邻

其中next是借助left表示出来的,即:两个变量a,b相邻只有两种情况,a在b左边和b在a左边。

def left(q, p, list):
    return membero((q, p), zip(list, list[1:]))


def next(q, p, list):  # p q 相邻意味着要么p在q左边,要么q在p左边
    return conde([left(q, p, list)], [left(p, q, list)])

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

(left,  # 绿房子在白房子的右边
 (var(), var(), var(), var(), 'green'),
 (var(), var(), var(), var(), 'white'),
 houses),

(next, ('Norwegian', var(), var(), var(), var()),
 (var(), var(), var(), var(), 'blue'), houses),  # 挪威人住在蓝房子旁边。

(next, (var(), 'physician', var(), var(), var()),  # 养狐狸的人所住的房子与医生的房子相邻
 (var(), var(), var(), 'fox', var()), houses),

(next, (var(), 'diplomat', var(), var(), var()),  # 养马的人所住的房子与外交官的房子相邻
 (var(), var(), var(), 'horse', var()), houses),

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

(membero, (var(), var(), var(), 'zebra', var()), houses),  # 有人养斑马

(membero, (var(), var(), 'water', var(), var()), houses)  # 有人喝水

现在完成了所有表达式和变量的表达。
接下来开始运算。

solutions = run(0, houses, rules_zebraproblem) 

根据运算的结果,判断谁是斑马的主人以及谁爱喝矿泉水。

判断是否有解

if len(solutions):
    zebra_owner = ""
    water_drinker = ""
    for i in solutions[0]:
        if "zebra" in i:
            zebra_owner = i[0]  # 找到斑马的主人
        if "water" in i:
            water_drinker = i[0]  # 找到喝矿泉水的人
        print(i)

    print('\nzebra_owner:\t\t' + zebra_owner)  # 打印结果
    print('water_drinker:\t' + water_drinker)

else:
    print("no answer")

程序设计完毕,下面进行运行测试。

1.4运行测试

运行程序,输出结果
在这里插入图片描述

分析程序输出的结果可知,日本人的宠物是斑马,挪威人爱喝矿泉水。
结合前面手动计算出的结果,可知两个结果完全一致,程序正确运行。

穷举法

# -*- coding: GBK -*-
import time
import itertools

"""
本题的思路:
    将所有可能的组合通过全排列的形式穷举出来
    然后根据已有的条件依次匹配
    若全部通过则为正确答案
    否则为错误方案
"""


# **********************匹配函数******************************

def handle(data):
    # 英国人住红色房子
    index = data[0].index('红色')
    if data[1][index] != '英国':
        return -1

    # 瑞典人养狗
    index = data[4].index('狗')
    if data[1][index] != '瑞典':
        return -2

    # 丹麦人喝茶
    index = data[2].index('茶')
    if data[1][index] != '丹麦':
        return -3

    # 抽Pall Mall香烟的人养鸟
    index = data[3].index('Pall Mall')
    if data[4][index] != '鸟':
        return -6

    # 黄色房子主人抽Dunhill香烟
    index = data[3].index('Dunhill')
    if data[0][index] != '黄色':
        return -7

    # 抽Blue Master的人喝啤酒
    index = data[3].index('BlueMaster')
    if data[2][index] != '啤酒':
        return -12

    # 德国人抽Prince香烟
    index = data[3].index('Prince')
    if data[1][index] != '德国':
        return -13

    # 绿色房子在白色房子左面
    index = data[0].index('绿色')
    if index == 4:  # 绿色房子在最后
        return -4
    if data[0][index + 1] != '白色':
        return -4

    # 绿色房子主人喝咖啡
    if data[2][index] != '咖啡':
        return -5

    # 抽Blends香烟的人住在养猫的人隔壁
    index = data[3].index('Blends')
    cat_index = data[4].index('猫')
    if cat_index - index != 1 and cat_index - index != -1:
        return -10

    # 养马的人住抽Dunhill香烟的人隔壁
    index = data[3].index('Dunhill')
    horse_index = data[4].index('马')
    if horse_index - index != 1 and horse_index - index != -1:
        return -11

    # 抽Blends香烟的人有一个喝水的邻居
    index = data[3].index('Blends')
    water_index = data[2].index('水')
    if water_index - index != 1 and water_index - index != -1:
        return -15

    print('找到答案:')
    for d_item in data:
        print(d_item)
    return 0


# **********************全排列列表******************************


# itertools.permutations 返回可迭代对象的所有数学全排列方式
# 以下是所有元素的全排列构成的列表
colour_list_all = list(itertools.permutations(['红色', '黄色', '蓝色', '白色', '绿色'], 5))
country_list_all = list(itertools.permutations(['英国', '丹麦', '挪威', '德国', '瑞典'], 5))
drinks_list_all = list(itertools.permutations(['茶', '水', '咖啡', '啤酒', '牛奶'], 5))
smoke_list_all = list(itertools.permutations(['Pall Mall', 'Dunhill', 'BlueMaster', 'Blends', 'Prince'], 5))
pet_list_all = list(itertools.permutations(['猫', '马', '鱼', '鸟', '狗'], 5))

# **********************过滤后的全排列列表******************************


# 全排列数量过大以下是过滤后出的列表
colour_list = []
country_list = []
drinks_list = []
smoke_list = []
pet_list = []

for colour in colour_list_all:
    colour = list(colour)
    if colour[1] == '蓝色':
        colour_list.append(colour)

for country in country_list_all:
    country = list(country)
    if country[0] == '挪威':
        country_list.append(country)

for drinks in drinks_list_all:
    drinks = list(drinks)
    if drinks[2] == '牛奶':
        drinks_list.append(drinks)

# 5! = 120
for i in range(len(smoke_list_all)):
    smoke_list.append(list(smoke_list_all[i]))

for i in range(len(pet_list_all)):
    pet_list.append(list(pet_list_all[i]))

# **********************遍历所有可能的方案并进行匹配******************************
# 由于程序采用穷举法,效率较低,故记录时间。
start = time.time()


for country in country_list:
    for drinks in drinks_list:
        for smoke in smoke_list:
            for pet in pet_list:
                for colour in colour_list:
                    data_list = [colour, country, drinks, smoke, pet]
                    # ~ num += 1
                    if handle(data_list) == 0:
                        # ~ print("总运行次数 %d" % num)
                        exit()
        end = time.time()
        print("\r计算用时:%0.4f秒" % (end - start), end="")

其他解法

利用google or-tools 求解
https://github.com/google/or-tools/blob/master/examples/python/zebra_sat.py

prolog求解
https://blog.csdn.net/weixin_45841983/article/details/109813808

  • 26
    点赞
  • 174
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cheney822

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值