斑馬問題多方法求解


人工智能導論實驗導航

實驗一:斑馬問題 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM