斑马问题多方法求解

人工智能导论实验导航

实验一:斑马问题 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.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内的函数:

实现的函数:

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

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
乘风的头像乘风管理团队
上一篇 2023年3月11日
下一篇 2023年3月11日

相关推荐