這是一個讓學生學習邏輯及Python OOP基礎程式設計的一個 Python3 程式庫。 數獨是一種益智類遊戲,同時也是學習邏輯的最好工具之一,而 Python 則是世界上最好的初學者學習程式設計的電腦語言。 所以,如果能夠結合這兩種來教導兒童、學生、青少年來學習邏輯的話,那將會是最好的組合! 這也是這個專案的緣起,也是這個專案追求的目標。
內容:
這個專案是一個 Python 程式庫以便讓使用者學習邏輯與 Python 程式設計。它包含了兩個套件,一個是 Sudoku,另一個是 Matrix。Sudoku 是一個以物件導向程式設計的模組,而 Matrix 則是一個傳統程式設計的模組。
這個專案與文件只要是提供給 Sudoku 套件來使用。Matrix 只是提供給傳統程式設計者做一參考。
你可以使用 pip 來安裝這個專案:
pip install SudokuStudyLib
你也可以到下列網址來複製整個專案:
https://github.com/RobertOfTaiwan/SudokuStudyLib
當你安裝完畢後,你安裝的目錄應包含了兩個套件,sudoku 及 matrix。下面就是整個目錄的結構表:
物件導向:sudoku,在 test.py:
from sudoku import *
# to solve a sudoku defined in data directory
solve("m18.data")
pass
# to solve a sudoku and just using the methods which level <= 15 and if can't solve, don't use guess method
solve("m3.data", level_limit=15, use_try=False)
pass
# to solve a sudoku with emulator methods and print the steps
solve("m12.data", use_emu=True, print_step=True)
pass
# to solve the world's best difficult sudoku
# by default method
solve("m10.data")
# by computer's try error
try_error(None, file="m10.data")
# by all methods but not using human guessing, it can't solve the sudoku
solve("m10.data", use_emu=True, use_try=False)
# by basic human methods and guess
solve("m10.data", level_limit=10, use_try=True)
solve("m10.data", level_limit=3, use_try=True)
傳統方法 :matrix,在 test.py:
from matrix import *
# solve it directly
m, n, p = main("m6.data")
# solve it by limit methods, it can't solve the sudoku
m, n, p = main("m3.data", methods=8)
# set the limit methods to the 10, and it can solve the sudoku
m, n, p = main("m3.data", methods=10)
# using the try error's method to solve the best difficult sudoku in the world
m, n, p = TryError("m10.data")
數獨(Sudoku)是一種智力遊戲, 也是一種學習邏輯的最好工具. 而 Python 則是世界上最好的電腦程式語言之一. 所以如果能夠結合這兩種工具, 來教導小孩或青少年來學習邏輯的話, 那就是最好的組合. 這就是這個專案的緣起, 也是這個專案的目標.
你可以從下列網站取得與學習數獨(sudoku)的相關知識:http://en.wikipedia.org/wiki/Sudoku
下面是一個典型的數獨題目: |
下面是一個典型解出來的數獨: |
|
![]() |
![]() |
假如我們開始在位置 (1, 1) 中置放一個數, 那必然是有9個可能的數字讓我們選擇. 然後當我要置放第二個數到位置 (1, 2) 時, 那我們會有8個數字可以選擇. 所以, 以此類推, 從上往下, 從左到右, 我們可以寫下每一個位置可以被選擇的數字:
9! | 6! | 3! | 6! | 3! | 1! | 3! | 1! | 1! |
---|---|---|---|---|---|---|---|---|
9 | 6 | 3 | 6 | 3 | 1 | 3 | 1 | 1 |
8 | 5 | 2 | 5 | 2 | 1 | 2 | 1 | 1 |
7 | 4 | 1 | 4 | 1 | 1 | 1 | 1 | 1 |
6 | 3 | 1 | 3 | 1 | 1 | 1 | 1 | 1 |
5 | 2 | 1 | 2 | 1 | 1 | 1 | 1 | 1 |
4 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
3 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
2 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
所以, 所有的組合就是 9!*6!*3!*6!*3!*1!*3!*1!*1* = 4,514,807,808,000
假如我們使用 python 來計算時:
>>> def n(x):
if x==1:
return 1
else:
return x*n(x-1)
>>> n(9)*n(6)*n(3)*n(6)*n(3)*n(1)*n(3)*n(1)*n(1)
關於數獨的數學知識, 你可以到下列網站來取得, http://en.wikipedia.org/wiki/Mathematics_of_Sudoku
最基礎的邏輯就是二分法,也就是「是」與「非」,或者在電腦科學裡面的「0」與「1」。這是人類在認知上、與他人溝通上的最小分類方法。所以學習邏輯對中小學生而言是非常重要的,因為這是所有知識的基礎,也是所有信仰的基礎。
如果依某一特定的角度、在某一特定時候,你無法判斷某個事物是「是」或「非」、或者「存在」或「不存在」,那你就是不了解這個世界,同時你也不了解你自己。
學習邏輯可以變成一件有趣的事情,如果我們能夠從遊戲中去學習的話,而數獨就是一種很好玩的遊戲。我們可以列舉很多以數獨來學習邏輯的優點:
它的規則相當簡單, 任何一個人能夠在5分鐘內了解它。
它又夠複雜,它可以有多達數十億種的組合。
它可以分各種不同的困難程度,來讓不同程度的人都能使用。
學習一種電腦語言是一種很直覺的方法來學習邏輯。Python 是一種直譯式電腦語言,提供一種簡便的方式來實作程式的設計。你可以在下面官方網站來取得該電腦語言的所有資源: https://www.python.org/。我們從其 FAQ<https://docs.python.org/3/faq/general.html> 中剪輯一段來說明為何 Python 適合程式設計的初學者:
對一個程式設計初學者而言,Python是否是一個好選擇?
是的。
現在學生開始學習程式時,還是在使用傳統的電腦語言,如 Pascal, C, C++。但這是有點古板了,學生應該得到更好與更簡單的方法來開始他們的程式探索,而 Python 就提供了這些特點。它擁有非常簡單及一致的文法結構,以及大量的標準程式庫,最重要的是,它讓學生能夠將心思放在解決問題的程式設計技巧上,而不是在電腦科學的細節上。使用 Python,學生可以很快地學習程式設的基本知識,如回圈(loops)、程序(procedures)。甚至在第一堂課,就可以讓他們嘗試使用自訂的物件。
一個從來沒有程式設計經驗者,一開始就使用靜態式電腦語言(如C等)似乎是不自然的。它讓學生必須先去了解電腦複雜的內部結構,使整個課程緩慢下來。 學生必須學習如何使用電腦的角度來思考、解決問題及設計界面。雖然就長期目標而言,學習靜態式電腦有其必要性與利益性,但對初學者而言,大可不必將程式設計的門檻弄得那麼高。
Python擁有很多優點來當作學習程式設計的首選電腦語言。就如同 Java,它擁有大量的標準程式庫,可以讓學生在課堂上去實作一些專案,而這些專案都可以是一些實用的應用程式,而不是簡單的加減乘除四則運算。同時,使用這些標準程式庫可以讓學生學習程式的再利用。其他豐富的第三方模組也非常好用,如 PyGame,都可以直接拿來讓學生使用。
Python是一個互動式的直譯器,可以讓學生直接邊撰寫程式邊測試。它可以讓你在一個視窗邊寫程式,而在另外一個視窗執行與測試這些程式碼。
這世上已經有太多的數獨遊戲或者學習程式,有些是為了娛樂,有些是為了數學的研究,而此專案則是專注在邏輯的學習上。而且,這裡的邏輯是專門以人的角度為出發點,而不是電腦科學的角度。所以這個專案有以下特點:
讓人們學習「邏輯」 的基本精神。
它「不是」要成為一般性的程式設計課程。
它「不是」要成為嚴謹的數學研究。
探索解數獨的方法是以人的角度,而「非」電腦科學角度。
讓人們自己去發現解數獨的方法,並且以自己的文字來命名它。( 這雖然不在此 Python Package 裡面,但必須將此安排在課程裡面。)
讓人們能夠學習 Python 程式設計以時做學生們自己探索出來的方法。
學習以物件導向程式設計(OOP)來求解數獨。OOP方法可以比擬為人的思考與行為角度。
我們可以安排一個暑期數獨營來上這些邏輯探索課程. 我們可以預先安排六種難易度不同的數獨題目. 目標不是讓學習者學會所有的數獨解法, 而是讓他們找到適合自己程度的數獨. 我們的目標不是教他們所有的解決數獨的技巧, 而是教他們邏輯. 所以, 我們可以讓不同的學習者, 使用不同的教材, 並且以邏輯的精神來解決它.
每一個上過小學3-4年級課程的人都適合.
14小時, 2小時/每天, 共 7 天
讓每個學習者找到適合他程度的數獨
學習電腦基礎知識
14小時, 2小時/每天, 共 7 天
當他們發現一個模式來解決時, 用自己的語言來命名, 並且寫下字句來描述此模式.
讓他們將自己發現的方法闡述給他人聽.
從解決他們以前家庭作業的方式來學習 Python 基本程式的撰寫, 如從 1 加到 100 等等...
14小時, 2小時/每天, 共 7 天
學習物件導向程式設計(OOP)的基礎觀念.
學習使用 OOP 來模擬他們所發現的方法.
我們認為, 類別定義, 是在OOP程式設計中最困難的部分. 所以在這些課程中, 我們不準備詳細地解釋如何設計類別, 屬性與方法. 我們只會解說在這個程式庫中既有的類別, 屬性與方法. 所以我們可以類比這些類別, 屬性與方法所構成的資料結構是一個解決數獨的模擬環境, 以便讓學習者以人的思考模式來學習如何解開一個數獨.
首先, 我們可以想像在一個美麗的山谷中, 有 9x9 間方方正正的房子, 它們排列如下:
一個假想的聯合王國
這裡有 9 個國家, 每一個國家有 9 個人, 他們決定一起定居在這個美麗的山谷. 而這個山谷共有 9x9 間房子. 他們決定每一排(x-way line), 每一列(y-way line), 及每一個 3x3 區塊都包含了每一個國家的人. 如此, 他們才會認為他們這個團體才是一個真正的聯合王國, 可以永久和平地生活在一起. 你可以幫助他們達到這個目的嗎?
接著, 我們就可以開始協助這些人來促成這一個美好的世界...
在OOP中,類別(class)的定義是主角。它就如同人類j為了探索、溝通或者紀錄等目的,會將有同樣行為、特性與外表的事物歸類一樣。如同我們會將動物當作一類,而大象就是動物類別的一個子類別,一隻大象就是同屬於動物及大象這兩類的一個物件。
雖然在自然上,一隻大象這個物件同時隸屬於大象類及動物類,但看目標的需要,也可以直接將一隻大象當做動物類別的一個物件即可。所以我們可以說在 OOP 所說的物件(Object)就是我們現實世界的一個實體,就如同人類若是一個類別,那你就是隸屬此類別的一個唯一獨立的物件。
依據我們設定的範圍與需要,我們可能設計出不同的類別,而讓同一個物件同時隸屬於它們。如我們想要研究城市生態時,我們可能會設計一個 animal 類別,而這個類別中的物件包括一些人,一些寵物等等...但當我們想要製作一個電話簿程式時,那我們就會設計一個 person 類別,一些人會成為此類別的物件,但不會包括寵物,除非這些寵物也都擁有手機。
在這個專案中,我們設計的主要類別有:
Number Class:
我們可以視數獨遊戲中每個數字是一個人。而整個數獨世界中有 9 個國家,每個國家有 9 個人。如此,這個 Number Class 就可以視為是一個國家類別。每一個國家都有一個識別代碼(ID),在這裡是 1-9,而每個國家都會紀錄它的人民住在這山谷中的位置。
Point Class:
Point 物件就是一間房子。 每間房子都會標註它是否已住人,如果已住人,那是哪個國家的人民;如果是空的,那可以讓哪些國家的人民來申請入住?
GroupBase Class:
GroupBase是一種群組類別,也就是它的物件不是一個實體,而是一群實體的組合。在這裡的 GroupBase 包含了三種群組,X 與 Y 軸方向的房子群組及區塊房子群組。 每個物件都將指出它包含了哪些房子,已經住了多少人,還有哪些國家的人民還沒住進來?
Box Class:
它是GroupBase的子類別,為區塊群組。在數獨世界中總共有 9 個區塊物件,從左到右,從上到下,被標註其區塊代碼如下:
![]()
lineX Class:
這是 GroupBase 子類別,為 X 軸的房子群組。在數獨世界中共有 9 個物件,從左到右被標註 1-9,如下圖:
![]()
lineY Class:
這是 GroupBase 子類別,為 Y 軸的房子群組。在數獨世界中共有 9 個物件,從上到下被標註 1-9,如下圖:
![]()
Matrix Class:
Matrix class定義了數獨遊戲的整個世界。它是一個美麗的山谷,包含了 9 個國家的人民,每一個國家有 9 個人,山谷中建造了 9x9 間房子以提供給這些人民來居住,以組成一個永遠和平的聯合王國。
屬性(Property)是在類別(Class)的定義裡面,類別用它來定義成員,特徵及紀錄狀況。如在一個人的類別裡面可能會包含以下一些屬性: 這人擁有多少錢、他有幾個小孩子、第一個小孩是男或是女、每個小孩各是幾歲?
以下是這個專案中幾個主要類別的主要屬性定義:
Number class:
v: 這個國家的代碼, 1-9
p: 這個國家每一個人民所居住的房子列表
filled: 有多少人已經住進房子了
Point class:
x: 這個房子的 x 軸座標
y: 這個房子的 y 軸座標
v: 這個房子居住了哪個國家的人民,如果是空的,那它的值就是 0
b: 這個房子隸屬哪個區塊
GroupBase class:
idx: 這個群組的代碼
p: 隸屬這個群組的房子列表
filled: 在這個群組裡面已經居住了多少人
possilbe: 在這個群組裡面還有哪些國家還沒住進來,值為這些國家的代碼列表
Box class:
包含所有 GroupBase 的屬性
effects: 這個區塊的所有鄰居區塊
effectsX: 這個區塊的 x 軸方相鄰居
effectsY: 這個區塊的 y 軸方相鄰居
lineX class:
與 GroupBase 的屬性相同
lineY class:
與 GroupBase 的屬性相同
Matrix class:
p: 一個二階陣列的房子列表,從 p[0][0] 到 p[8][8],代表這個山谷的所有房子。
lineX: x 軸方向房子群組的列表
lineY: y 軸方向房子群組的列表
b: 區塊房子群組的列表
n: 所有國家的列表
filled: 紀錄已經有多少人入住在這個山谷了
方法(methods)是一個類別及其物件的一些特定行為。舉個例子,如果我們定義了一個收音機類別,它將包含一些按鈕的屬性,而當我們按下這些按鈕時,我們就必須定義一些方法來執行這個動作。這些動作有可能是開始接收某個電台的節目、或者是錄製節目到CD等等...
以下是這個專案中的類別中使用道的主要方法:
Number class:
setit(p1): 當有人找到屬於自己的房子時,就會啟動這個方法
Point class:
can_see(p1): 測試一個房子是否能夠**看到**另外一個房子(p1)?
can_see_those(posList): 測試一個房子能否**看見** posList 所列的房子,並將所有能看見的房子列表傳回。
註解
甚麼是「看見」?
對一個房子而言,與它同一排、同一列、或者同在一個區塊的其他房子,都是它能夠**看見**的房子。
GroupBase class:
allow(v): 測試一個房子群組能否讓標記為 v 的國家人民來居住?
get_num_pos(v): 在這個房子群組中,取得 v 國人民居住的房子物件,如果該國尚未有人入住,則以None來回應。
count_num_possible(count): 在一個房子群組中,取得國家的id及可供該國人民居住的房子列表,如果有參數 count,則表示要取得的可供居住的房子數要等於 count 才取回。
get_all_pos(method): 如果 method = “a”, 取得一個房子群組的所有房屋物件列表;如果 method=”u”,則取得所有空房列表;如果 method=”s”,則取得所有已住人的房子列表。
Box class:
所有 GroupBase 類別的方法
get_group_number(num): 測試這個國家代碼 num 在一個房子區塊中能否形成一個 虛擬國民?
註解
甚麼是「虛擬國民」?
虛擬國民存在於一個房子區塊群組中。在一個區塊中尚未有人入住的房子中,如果所有可能讓某個國家居住的房子在同一個方向時(無論是 x 軸或 y 軸),那我們就可以稱這些房子形成了一個**虛擬國民**。雖然我還不曉得在這個區塊中,這個國家的人民最後將居住在哪裡,但我們從虛擬國民中知道,在與它同個方向的其他區域的房子,都將不允許居住這個國家的人民了。
lineX class:
與 GroupBase 有相同的方法
lineY class:
與 GroupBase 有相同的方法
Matrix class:
get_all_pos(method): 如果 method = “a”, 取得所有房屋物件列表;如果 method=”u”,則取得所有空房列表;如果 method=”s”,則取得所有已住人的房子列表。
sort_unassigned_pos_by_possibles(possibles): 取得所有的空房列表,而這些空房數必須僅能夠讓 possibles 個國家的人入住,如果 possibles == 0, 將取得全部空房。回應回來時將以可居住國家數 ,從小排到大。
can_see(p0, method=”u”, num=0): 取得能夠看見某一房子(p0)的房子列表,如國 num!=0,表示僅取得可讓國家代碼為 num 者居住的房子。
setit(x, y, v): 讓國家代碼為 v 的人民安住在座標為(x, y)的房子。
reduce(x, y, v): 當一個房子(x, y)被入住時,任何能夠看見此房子的其他空房,都可用此方法來減掉已入住這個國家人民(v)的可能性。
allow(x, y, v): 測試這個國家(v)的人民是否能夠居住於座標(x, y)的房子?
read(file): 從一個定義檔(file)中讀入最初到此山谷的國家、人民與居住位置。
你能夠定義數獨遊戲的最初狀況。在文字檔中一行定義一個房子的座標及居住者,格式為x, y, v,如下圖,此專案附有一些已定義好的數獨,置於 [安裝的目錄]/sudoku/data/ 目錄裡。
m3.data | 起始的數獨外觀 |
已解的數獨外觀 |
---|---|---|
![]() |
![]() |
![]() |
當我們已經在電腦建構好一個數獨模擬世界時,對那些我們以前使用手寫的方式所發現的解數獨方法,我們就可以著手來將其以電腦語言(這裡是 Python)來重新闡述。就術語來說,稱為程式設計(Programming)。所以程式設計可以視為我們在教導電腦如何去執行我們解決問題的方法。
一開始,我們先介紹這個專案整個求解數獨的環境,然後我們再說明一些基本方法。
我們設計了 solve() 這個函數來做整個求解數獨的入口,另外也定義了兩個事件類別,SudokuDon 及 SudokuError,以處理求解過程中發現已經解開數獨或者產生違反數獨規則的狀況。
註解
甚麼是「例外處理(Exception)」?
「例外處理(Exceptions)」是一種事件定義,當構成這個事件的條件滿足時,系統就會停止處理目前的事務,而跳到該事件的處理程序。在這個專案中有兩個主要的例外處理:
當這個數獨已經被解開時,這個例外處理會被叫醒
當求解過程中發現違反數獨規則時,這個例外處理會被叫醒
為了讓整個求解環境能夠知道有多少種求解方法它能夠使用,我們設計了一個類別,SolveMethod。我們以這個類別來將所有的求解方法置入成一個**虛擬大腦**。我們可以將這個虛擬大腦視為是這個美麗山谷的守護神。每當有新進來此山谷者,找不到居住所時,都可以透過祂來選擇出適合的房子,但祂也可能回答:「對不起,我也不知道該如何選擇!」
對**虛擬大腦**而言,每一個解數獨的方法都化為一個 SovleMethod 物件儲存在裡面,這些物件有以下主要屬性:
fun: 解數獨方法的 Python 函數名稱
idx: 方法的排序,從簡單的方法開始到困難的方法依序排列,虛擬大腦將依序一個一個地使用這些方法來解開數獨。
name: 此方法的名稱,使用者可以自訂其名稱
level: 困難度,對人的直覺而言。系統用此來計數解開整個數獨的困難積分。
下面是主要求解函數,solve(),的流程圖:
註解
「有用(work)」或「沒用(not work)」?
一個求解方法如果是「有用」,表示它能夠:
讓一個或多個人找到他們的居所
或者是可讓一間或多間房子知道一個或多個國家的人不能居住在他們的房子
在這張流程圖中,我們知道:
當一個求解方法有用時,設定了一個人的住所或降低了一間房子可居住者的可能性時,整個虛擬大腦會重新回到第一個求解方法來繼續求解。
假如一個求解方法沒用時,它會交給下一個方法來解決。
當到最後一個方法都沒用時,他會離開整個求解環境並說:「我無法解開這個遊戲,抱歉!」
在整個求解過程中,如果發現已解開數獨(Done)或者違反了數獨規則(Error)時,它就會跳離整個求解環境。
fill_only_one_possible:
在每一個房子群組中,檢查每一間房子,是否只有一間房子能夠讓某國的人民居住,如果是,則該間房子必然要讓那國的人民遷入。
fill_last_position_of_group:
當一個房子群組中只剩一間空房時,那必然是只有一個國家的人民尚未入住。
check_obvious_number:
檢查每一個國家中已經定居的國民,看他們所在的位置所交互影響、而尚未有該國人民入住的區塊中,是否可以找到僅剩一間房子容許該國國民來定居,如果是,則可以很明顯地配給該房子給與該國的人民。
check_inobvious_number:
這個求解方法與 check_obvious_number 相同,只是先找到一些區塊的虛擬國民,讓他們以及已定居的國民一起來探測其他尚未有本國國民居住的區塊。
reduce_by_group_number:
如果在一個房子區塊中形成了一個虛擬國民時, 那與這個虛擬國民同方向的房子群組中的其他空房子,就不可能居住這個國民。
update_chain:
當一些房子開始住人以後,會影響一些房子不再能夠允許一些國家的居民,而這些空房子可能因此在一個房子群組中(x軸、y軸、區塊)中產生了一個**鍵鏈(Chain)**。這個方法就是去尋找可能的鍵鏈,並將鍵鏈所影響的空房子減少它們被這些鍵鏈的國民居住的可能。
我們來實作一個只為一個國家的人民來看是否能有一個直覺的方法來為一個尚未有該國住民的區塊找到住所,我們命為 check_obvious_for_a_country(m, num):
1 def check_obvious_for_a country(m, num): 2 checked = list() 3 for p1 in m.n[num].p: 4 for b in m.b[p1.b].effects: 5 possible = [] 6 if b in checked: 7 continue 8 else: 9 checked.add(b) 10 if num not in m.b[b].possible: 11 continue 12 for p2 in m.b[b].p: 13 if p2.v != 0 or p2.can_see(p1) > 0: 14 continue; 15 if not m.lineX[p2.x].allow(num): 16 continue 17 if not m.lineY[p2.y].allow(num): 18 continue 19 possible.append(p2) 20 if len(possible) == 1: 21 m.setit(possible[0].x, possible[0].y, num, d="Obvious For a Country People")
line#1, 定義一個求解方法, m 是這個遊戲的數獨世界,num 則是一個國家代碼, 可能值為1-9。
line#3, 將每一個已經入住此山谷的此國國民都找出他們的住所。
line#4-9, 檢查每一個尚未被檢查的房子區塊。
line#10-11, 如果此區塊已有該國人民居住,那就不用檢查了。
line#12-19, 檢查此區塊每一個空房是否可以讓該國的人民來居住。如果是,則將該房子放進去可能的名單裡面。
line#20-21, 最後檢查可能的名單中,如果只有一個時,那就表示該房子必然由該國人民來居住。
這是使用物件導向程式設計來求解數獨的程式庫