【說在前面】:算法
以前,我在微信朋友圈看到一個同事發了一個狀態,說的是她在家輔導孩子作做業,一個數獨的題目,好像沒有作出來。我看了下,我也作不出來,後來仔細想了下,花了兩個多小時時間,用Python編了個程序,把那個數獨題目解出來了。隨後我就發了一個公衆號的推送,這個推送被我老婆看見了,說:「人工解數獨兩分鐘,你寫個程序花兩個多小時?!何須呢?」(學霸就是學霸,說話都這麼霸氣!)我說:「這個程序能夠解答任何9×9的數獨問題。」她說:「若是換一個數獨題目,又要從新改代碼,不太方便!並且我也不懂什麼編程,不會用你的程序。」我想一想也是,這也是我當初準備考慮用VBA的緣由,就是由於VBA依託於電子表格,出題、解答、展現結果都比較直觀,程序使用起來也比較方便。後來之因此用Python來編程,是由於Python處理這種大量數據計算比較方便快捷。編程
當初用Python編寫程序,利用到了面向對象的編程方法,若是你不瞭解面向對象編程,可能對我以前寫的程序理解起來有難度。因此我想着用VBA編程,採用面向過程的方式編寫一個程序來解答數獨問題。數組
這幾個週末的閒暇時間,我一直在考慮用VBA來寫一個解數獨問題的程序,採用面向過程的方式來編寫,這樣便於理解。若是面向過程都搞定了,未來在轉換到面向對象,就很容易了。我在網上查閱了不少資料,也看了不少網友寫的程序,都測試了一下,基本上都知足不了個人需求。不是解題太慢,就是解不出來,解答一些骨灰級難度的數獨,還致使死機,系統電腦直接卡死、崩潰。有的還須要使用「猜想法」來解數獨,感受超級不爽。微信
好吧!既然動了用VBA解數獨的心思,那就必須得搞定。因而,想了幾天,反覆測試,編寫代碼,終於搞定了。通常數獨能夠秒解。app
我在網上查了不少資料和其餘網友寫的程序,都沒有我這個厲害,找一個骨灰級難度的數獨,解出來也就兩分多鐘。網上一些朋友提供的數獨程序,骨灰級別難度的數獨,根本解決不了。編程語言
以上就是一個骨灰級別難度的數獨,用的程序解出來了。嘗試了661773次,耗時207.829秒。是否是很牛X。我在網上尚未發現有誰用VBA寫出過能這麼快解數獨的程序。ide
看視頻演示,全網最強VBA解答數獨問題!函數
注意:全網最強!強!!強!!!測試
不服來戰~!網站
【若是是手機觀看,建議最大化視頻,手機橫屏觀看!】
視頻地址:https://www.bilibili.com/video/BV1PZ4y1G7Zv
怎麼樣?展現視頻看完,是否是以爲這個程序很強大?視頻裏面演示的各類難度的數獨,都是我在一個在線的數獨網站上找的,你們有興趣也能夠本身試試,測試一下。
免費在線數獨網址:
http://www.cn.sudokupuzzle.org/
【一個小插曲】
以前,我把這個數獨程序寫完了,作了展現視頻,寫了這個技術文檔的草稿,給老婆看。她是一個程序小白,若是她都大概能看懂,有興趣把展現視頻看完,那說明這個技術文章寫得仍是比較詳細的,由於個人題目是《詳解VBA解決數獨問題》,是「詳解」。做爲徹底不懂編程的她看後,也提了不少問題和建議,大多都是很弱的問題。我想她若是有這個問題,可能你們在看的時候,也會有一樣的問題,因此我就作了一些修改和進一步的講解。也就有了下面的:
〇、程序界面設計 這個章節,這是我新增長的一個,因此用 〇 來編號。而後再給老婆看了下,她基本上滿意了,因此我就準備發佈了。
跟老婆的對話:(理工男和萌妹子的平常對話)
她:你此次改的還不錯,寫的很詳細,可是我不想看,太長了,看了頭疼。
我:那我作的展現視頻怎麼樣?看看吧!
她:視頻配樂很不錯。感受你這個程序仍是很厲害的!
我:這都是在你的指導下,作的修改啊!
她:你比較像白居易。
我:什麼意思啊?
她:白居易在寫詩的時候,都會先讀給老婆婆聽,若是老婆婆聽得懂,以爲好,他纔會發。因此他的詩都通俗易懂,並且又不失高端大氣。
我:白居易?不知道,我只知道白雲邊。
她:·······,(直接不理我了)
------ 語法不兼容,系統已死機 ------
成功的把天聊死了 呵呵
---------- 以上都是廢話,下面進入正題 ----------
不少時候,咱們以爲計算機很聰明,很厲害!其實,計算機很笨,他惟一的優勢就是「快」!他能很快的處理一些複雜的,須要反覆處理的事情。
因此,對於計算機來講,只有咱們想不到的事情,沒有他作不到的事情。若是他作不到,那就是咱們尚未把問題想清楚、分析透徹,沒法翻譯成代碼,讓計算機執行。
所以,只有瞭解計算機的運行原理,懂得計算機的思惟方式,咱們才能把現實中的問題,按計算機可以理解的思路,想清楚,分析透,並翻譯成代碼,讓計算機幫咱們作。
下面就向你們介紹一下VBA解數獨的思路和代碼。
剛纔說了,計算機有一個很大的特色就是「快」!如何發揮它「快」的優點?那就要利用循環,快速的反覆運算處理。那麼怎麼造成循環呢?那就找規律,要分析解決問題的核心規律,抽象成代碼和變量,利用循環,快速運算處理。
如今咱們就來分析數獨問題:
什麼是數獨?
數獨是一種數學遊戲。數獨盤面是個九宮,每一宮又分爲九個小格。玩家須要根據9×9盤面上的已知數字,推理出全部剩餘空格的數字,並知足每一行、每一列、每個粗線宮(3*3)內的數字均含1-9,不重複。因此又稱「九宮格」。
〇、程序界面設計
爲了用戶可以很好的體驗這個程序,同時也爲了後續的開發方便,咱們須要對程序界面作一個很好的設計。
程序主界面:
我是在WPS的電子表格裏設計的,能夠看到最左邊和最上邊的數字編號,這是表格的行、列編號,是絕對定位編號,我設計的數獨九宮格,是在上面空了兩行,左邊空了一列開始繪製表格的,因此我在上面標註了 起始行號:3,起始列號:2,這就是針對我繪製的數獨九宮格第一個格子的位置,相對於電子表格編號的定位,這是相對定位,便於之後調整表格位置,不用改代碼,只須要修改起始行號和起始列號的數字就能夠了。做答區也是這個思路設計的。
第13行,我作了一個 顯示解題過程 的開關,只須要用鼠標點擊那個小黑點,就能夠自由的啓用和關閉 顯示解題過程 了,方便操做。
這些都是交互功能,不在此次詳解的範圍內,不細說,有興趣能夠看個人源代碼。
1、數獨區域劃分和命名:
一、行、列編號
根據數獨格子的樣式,咱們給他命名並編號:
行編號:Row = 1 表示第一行,以此類推,第九行 Row = 9。
列編號:Col = 1 表示第一列,以此類推,第九列 Col = 9。
區編號:Box = 1 表示第一個小九宮格,以此類推,第九區 Box = 9。
二、點位編號:
這是每一個單元格的編號,其目的是方便找到所在點位對應的行號、列號和區號。
2、創建數獨模型
咱們仍是以以前我那個同事發的數獨題目爲例:
能夠看到,81個格子裏面,已經有27個格子有數字了,54個格子是空的。這就是數獨題目。如今咱們就要考慮,把這個題目讀取出來,在程序代碼裏面造成數獨模型。以前我考慮用數組來處理,可是VBA的數組操做很麻煩,因而果斷放棄,這裏採用字符串模式來處理。
咱們用一個嵌套循環,逐行讀取數獨題目中的數字,若是格子是空的,咱們就用0來填充佔位,確保字符串長度爲81。
代碼:
解釋:
函數名爲 GetShuDuList(Row As Integer, Col As Integer) As String (核心函數)
用一個嵌套的For循環來讀取數獨表格的數據,第九行代碼,這裏用了一個 IIF語句,意思就是,在指定的行號和列號定位的單元格內是空值,就填充0,不然就讀取單元格內的數字。
讀取完後,用 GetShuDuList = List 返回。
最後獲得:
300500000000000346002003000003000200010840000006320980035100069091050000000900007
這樣的一個字符串。
之後咱們全部的操做和運算,都基於這個字符串來進行。
舉例:
以添加了灰背景色的數字 3 爲例。對應單元格點位編號,能夠對照知道,這個3對應的點位爲30點位。
GetShuDuList = 30050000000000034600200300000300020……
在數獨模型GetShuDuList字符串中,也是對應的第30點位,即標紅的那個3。
那麼,如今咱們就要考慮一個重要的問題,如何經過這個點位30,算出這個點位對應在數獨表格中的行號、列號和區號。
這裏須要用到一點點數學知識,就是除法的取整和取餘的問題。
很明顯,這個30點位,對應的行號是4,對應的列號是3,對應的區號是4。那麼如何算呢?
先算行號和列號:
這個數獨是9行9列的表格,那麼咱們就用點位號30除以9:
30÷9=3 餘 3
很容易理解了,除法結果整數部分 +1 就是行號,餘數部分就是列號。即30點位行號是4,列號是3。
咱們再試一下點位14看看。
14÷9=1 餘 5
按上面計算方法,行號是2,列號是5。沒問題,是正確的。
咱們再試試點位18看看。
18÷9=2 餘 0
按上面計算方法,行號是3,錯誤。列號是0,錯誤。
咱們發現,當出現9的倍數的點位號時,不正確了。
稍微分析一下就能夠知道,當出現9的倍數的點位號,也就是點位號除以9餘數爲0時,行號就是兩個數除法運算的結果,列號就是9。
因而,咱們就把這個規律找出來了。那麼如何翻譯成VBA語言,讓計算機理解執行呢?
在VBA中的語法,
除法的運算符是 / 例如 18 / 9 = 2
除法取整的運算符是 \ 例如 14 \ 9 = 1
除法取餘數的運算符是 Mod 例如 14 Mod 9 = 5
好,咱們來寫代碼:
計算行號的函數:GetRowIndex(ShuDuIndex As Integer) As Integer
計算列號的函數:GetColIndex(ShuDuIndex As Integer) As Integer
這裏咱們依然用到了IIF函數。
算行號:先取模(取餘數),餘數爲零,行號就是點位號除以數獨尺寸9,就獲得了行號;餘數不爲零,就直接取整數再 +1,就是他的行號。
算列號:先取模(取餘數),餘數爲零,列號就是數獨尺寸9,餘數不爲零,就取餘數(取模),獲得的就是列號。
容易理解吧~!?
下面咱們就來處理計算區號的問題:
這個問題我沒有找到很好的算法,就用笨辦法來處理吧!
計算區號的函數:GetBoxIndex(ShuDuIndex As Integer) As Integer
以第四行代碼爲例講解:
這裏用的Select Case 語法,當點位號等於1,2,3,10,11,12,19,20,21中的一個數字時,表示在第一區,返回區號1。其餘以此類推。
也就是把全部區內的點位號都羅列出來,逐個判斷這個點位在哪一個區。
也很好理解吧?~!
好,如今咱們已經能夠經過點位號來獲得這個點位所在的行號、列號和區號了,而後咱們就要統計這個行、這個列、這個區內存在的數字了。
這裏咱們寫三個函數來處理:
一、統計點位所在行中數字的函數(去掉0):
GetRowList(ShuDuIndex As Integer, ShuDuList As String) As String
第5行,第6行代碼就是利用咱們以前寫的函數,經過點位號計算出這個點位所在的行號和列號。這裏雖然用不到列號,仍是寫着吧!
第8行,用兩個函數的嵌套來統計數字。
Mid函數,用來截取整行字符串。
Mid(ShuDuList, (R - 1) * ShuDuSize + 1, ShuDuSize)
翻譯一下:若是R = 3,表示第三行,那麼
紅色部分:
(3 - 1) * 9 + 1 = 19,表示第三行起始點位號是19。
綠色部分:
ShuDuSize = 9,這在以前的公共變量裏面已經定義了。
整句話的意思是:在ShuDuList字符串中,從第19個點位開始截取字符串,截取9個,這樣就恰好把第三行的數字截取出來了。
GetShuDuList = 30050000000000034600200300000300020……
上面的紅色部分。即:002003000
Replace函數,套在Mid函數的外面,用來將截取出來的字符串中的0所有刪除。即獲得:23 兩個數字字符串。
這是一個很基礎的小招數,這裏不深刻探討,你們有興趣深刻研究能夠在網上查閱相關資料或查看VBA參考手冊。
Mid函數介紹:
Replace函數介紹:
二、統計點位所在列中數字的函數(去掉0):
GetColList(ShuDuIndex As Integer, ShuDuList As String) As String
這裏沒法用Mid函數一次性來截取,由於他跳行了,不是連續的,因此,咱們只能用For循環來截取,For循環的步長Setp = ShuDuSize 即步長爲9,經過循環截取,這樣以來,咱們就能夠獲得整列的數字,而後執行第10行代碼,去掉裏面的0。
三、統計點位所在區中數字的函數(去掉0):
GetBoxList(ShuDuIndex As Integer, ShuDuList As String) As String
方法跟上面相似,只是咱們這裏專門爲獲取區塊內的數字寫了一個函數:JoinBoxList(BoxIndex As Integer, ShuDuList As String) As String
一樣是經過Mid函數來截取,最後用Replace函數來去掉裏面的0。
最後的結果:
以標綠的單元格3行,4列,2區爲例,在VBE本地窗口能夠看到,
23:就是紅框框第三行裏的數字,刪掉了0。
58319:就是橙色框框第四列裏的數字,刪掉了0。
53:就是藍色框框第二區裏的數字,刪掉了0。
程序計算沒有問題。
好,如今咱們能夠經過
GetShuDuList(Row As Integer, Col As Integer) As String 獲得數獨模型列表
GetRowList(ShuDuIndex As Integer, ShuDuList As String) As String 獲得點位所在行中的數字列表。
GetColList(ShuDuIndex As Integer, ShuDuList As String) As String 獲得點位所在列中的數字列表。
GetBoxList(ShuDuIndex As Integer, ShuDuList As String) As String 獲得點位所在區中的數字列表。
下面我就就要統計空表格所對應的行號、列號、區號和可能填入的數字列表。
咱們寫一個函數來處理:
CanPointInfo(ShuDuList As String) As Variant (核心函數)
解釋:
第3行:ReDim CanPointList(1 To 1) As Variant
定義一個一維可變數組 CanPointList,用來存放每一個空點位的相關信息。
第5行、第6行。定義一個ArrId = 1,用於表示數組下標,最後進行遞增,以達到擴充數組的目的。
第7行,定義一個一維數組Point,用來存放空點位的相關信息。
第10行,作一個For循環,從1循環到81,遍歷整個數組模型列表。
第11行,循環過程當中,利用Mid函數,逐個取出數組模型列表中的數字,若是等於0,則表示數獨中這個點位是空的。
第12-15行,經過以前寫的函數獲得行號,列號,區號。
Point(1) 存放行號信息
Point(2) 存放列號信息
Point(3) 存放區號信息
Point(4) 存放行可能填入的數字信息,這裏先讓他爲空,後續再處理。
第17-23行,定義三個字符串變量,經過以前寫的函數,獲得點位所在行、所在列、所在區的數字信息。
第25-32行,經過一個循環語句,從1循環到9,而後比對行中的數字,列中的數字,區中的數字,若是都沒有出現,表示這個數字是可能填入的數字,而後將可能填入的數字壓入Point(4)裏面。
第34行,把整理好的Point存入擴充後的CanPointList裏。
例如:
標灰的單元格,點位號是29,經過計算能夠獲得他的行號、列號、區號。
即:
Point(1) = 4,在第4行。
Point(2) = 2,在第2列。
Point(3) = 4,在第4區。
而後統計行、列、區的數字:
RowNumList = 「32」
ColNumList = 「139」
BoxNumList = 「316」
而後運行25-32行代碼:
數字1:在ColNumList、BoxNumList 中都存在,捨去;
數字2:在RowNumList 中存在,捨去;
數字3:在RowNumList、ColNumList、BoxNumList 中都存在,捨去;
數字4:都不存在,能夠保留。執行第30行代碼;
數字5:都不存在,能夠保留。執行第30行代碼;
數字6:在BoxNumList 中存在,捨去;
數字7:都不存在,能夠保留。執行第30行代碼;
數字8:都不存在,能夠保留。執行第30行代碼;
數字9:在ColNumList中存在,捨去。
循環結束後,Point(4) = 「4578」
最後整理一下:
Point(1) = 4
Point(2) = 2
Point(3) = 4
Point(4) = 「4578」
即:4行3列這個單元格對應的區位號是4,這個單元格可能填入的數字是 4578中的任何一個。
而後執行第33-35行代碼:
ReDim Preserve CanPointList(1 To ArrId) As Variant
保留原有數組內的信息,擴充數組,並存入剛纔獲得的Point信息,而後CanPointList數組下標自增,以便於後續擴充,便於填入新的數據。
當第10行到第37行代碼循環執行完後,那麼這個數獨的全部點位都遍歷完了,而且把全部空單元格的信息都已經統計出來,並存入了CanPointList數組中了,這時,CanPointList數組就變成了一個二維數組。
好了,如今咱們經過
GetShuDuList 函數獲得了整個數組模型列表
CanPointInfo 函數獲得了每一個空點位的信息和可能填入的數組信息。
下面咱們咱們就要考慮,若是咱們從CanPointInfo拿出一個點位信息,填入到GetShuDuList 列表中,而後,咱們就要檢查咱們填入的這個數字,在這個點位上的行中、列中,區中是否存在,若是不存在,咱們就能夠填入,若是存在,就不符合數獨規則,就要退出來,換另一個可能填入的數字,並恢復以前嘗試的數字,而後進行下一輪,繼續嘗試。
這個檢查的過程,咱們依然須要寫函數來實現。
這裏,咱們寫一個Check函數,用來檢查填入數字是否合法。
解釋:
第3-6行,判斷嘗試填入的數字是否爲0,若是是0,則表示還未賦值給嘗試的點位,直接返回False,表示數據非法。
第9行,這裏是經過點位信息裏的行號和列號計算出數獨模型列表的索引位置,爲了程序的簡潔,我這裏寫了一個函數來處理。
這個算法很容易理解啊,很少說。
第11-17行,經過以前寫的函數,獲取點位所在行、所在列、所在區中的數字列表。
第19-25行,跟以前的算法同樣,經過InStr函數來判斷嘗試的數字是否存在,若是不存在,就都等於0,則返回True,表示這個數字能夠填入,不然,返回False,表示這個數字非法。
好,到這裏,咱們就要考慮如何從CanPointInfo 數組中取一個點位信息,存入GetShuDuList 列表中,並用Check函數來檢查了。
從CanPointInfo 數組中取點位信息,有不少方法,從前面取也能夠,從後面取也能夠,從中間取,也能夠。這裏爲了知足未來遞歸算法的要求,咱們從最後一個來取,這樣取也不會打亂前面數組結構信息。由於從前面取或從中間取點位信息,CanPointInfo 數組結構會發生變化。
那麼如何從最後一個數組元素取信息呢?在別的編程語言裏面有專門的函數,像Python中,他自帶一個pop()方法,能夠取出數組最後一個元素,取出後,並將原數組中最後一個元素刪除。
而VBA則沒有這個方法,因此,咱們又得本身寫函數來實現了。
首先,咱們寫一個獲得數組最後一個元素的函數。
GetArrLast(Arr As Variant) As Variant
很簡單的一個函數,一句話搞定。
用UBound方法獲得數組的最大下標,而後取出,並返回。
再寫一個刪除數組最後一個元素的函數。
DelArrLast(Arr As Variant) As Variant
解釋:
第11行,經過LBound獲得數組的最小下標,經過UBound獲得數組最大下標,而後ReDim Preserve 從新定義動態數組,並保留數組原始信息,讓最大下標減1。就等於把最後一個元素刪除了,而後再返回。
這裏須要注意,當數組只剩一個元素的時候,最小下標和最大下標都是1,若是最大下標再減1,這個數組就會在從新定義時報錯。我當時在調試程序的時候,這個地方報錯,我查了好久才查出來是這裏的問題,後來我就用了一個條件判斷語句來處理這個問題。
第6-10行,若是最大下標等於1,咱們就給這個數組的頭兩個元素賦值爲0。
就至關於
Point(1) = 0
Point(2) = 0
也就是說,這個點位信息的行號爲0,列號爲0。正常狀況下,點位信息的行號和列號不可能爲0,這裏咱們強行的賦值爲0,之後在遞歸的時候,就能夠以此爲判斷依據,若是行號等於0,那麼這個遞歸就要結束了,能夠做爲遞歸終止的判斷,防止發生死循環,致使系統崩潰。
如今咱們完成了從數組最後一個元素取值的函數,也完成了刪除數組最後一個元素的函數。下面咱們就要考慮,若是取出來後,嘗試不行,咱們又要恢復數組原始狀態,咱們還要考慮在數組最後增長元素信息的方法。Python中,他自帶一個append()方法,能夠很容易的實現。而VBA沒有,因此,咱們仍是得寫函數來完成。
函數名稱:AddArrLast(ArrSub As Variant, Arr As Variant) As Variant
一樣用到了ReDim Preserve,保留數組原始信息,擴充數組的方法。以前已經講過了,這裏再也不重複。
接下來,咱們考慮如何把咱們準備填入的數字放到數獨模型列表中的問題。
若是是數組,就很容易處理,可是以前咱們考慮過,用數組雖然很好處理這個問題,可是處理其餘問題就比較麻煩,因此,最後咱們這裏的數獨模型列表並無用數組,而是用的字符串。如今咱們就要考慮如何把咱們須要填入的數字,替換到數獨模型列表中的數字。
一說替換,你們可能立刻會想到用Replace方法。可是,這是不行的,由於咱們須要填入的數字在數獨模型列表中都是以0來填充的,若是用Replace來替換,你究竟是要替換哪一個0呢?你不說清楚,計算機是不知道的,他會把全部的0都替換掉。固然,你也能夠定位,可是定位後用Replace替換,他會把定位前的字符都刪除掉,不知道這個函數他們是怎麼設計的,爲何要這麼操做,也不是很清楚。因此,這也是不行的。
因而,咱們這裏考慮用Left()和Right()函數來處理。
咱們寫一個函數。
ReplaceMid(Str As String, RepStr As Variant, ShuDuIndex As Integer) As String
若是咱們須要替換掉第10個數字,那麼咱們用Left取出左邊的9個數字,Right取出第10個以後的全部的數字,而後左邊的數字拼接上替換的數字,再拼接上右邊的數字,就至關於把指定位置的數字替換掉了。很好理解吧?並且這樣也比較簡單高效。
詳解:
好了,到此,咱們就要考慮在空單元格填入可能的數字並檢查合法性的問題了。
這個問題很複雜,因此,必須得寫個函數來處理。
TryInPoint(Point As Variant, ShuDuList As String, CanPointList As Variant) (核心函數)
解釋:
第4行:取出點位信息中可能填入數字的列表。
第8行:循環取出可能填入的數字,每次取一個。
第9行:將取出來準備填入的數字放入Point(5)中。
第10行:利用咱們剛纔寫的Check函數判斷合法性。
第11行:若是是合法的,就替換掉數獨模型列表中對應的數字。
第12行:判斷CanPointList數組是否已經到了最後一個,若是到了最後一個,即行號、列號等於 0 的時候,就調用 ShowOkShuDu函數,顯示正確結果。
ShowOkShuDu這個函數咱們還沒寫。後面再寫!
第16-18行:再從CanPointList中取出最後一個數組元素。
第20行:開始再次調用TryInPoint函數,遞歸嘗試。
第22-25行:若是嘗試出現非法數據,就恢復上一輪的操做。
到這裏爲止。咱們就把數獨問題的核心算法寫完了。剩下就是考慮如何把正確的數獨結果顯示出來了。
寫一個顯示數獨結果的函數。
ShowOkShuDu(ShuDuList As String) (核心函數)
就是利用循環來展現,展現完後,執行第14行代碼,彈出對話框,提示數獨問題解答完成!
而後執行第15行代碼,終止整個程序運行。
很容易理解,很少講。
如今,數獨問題,已經能夠解決了。考慮到程序的強壯性和智能化。咱們還須要考慮一些其餘的問題。
好比,在出題過程當中,是否已經存在同行、同列或一個區中存在重複的數字,即數獨題目有誤。這個咱們在做答前必需要檢查一個題目是否有問題。
寫一個檢查數獨題目是否正確的函數。
CheckShuDuOk(ShuDuList As String)
解釋:
第8行:應爲數獨一共有9行、9列、9區,因此,咱們只須要循環九次,就能夠檢查全部的行、列、區了。
第9行:獲得一行中的全部數字,並去掉0。
第12行:從1-9這九個數字,依次和行中的數字進行比較。若是等於0,則表示沒有這個數字,嘗試下一個;若是不等於0,說明裏面存在,可是這樣是否是就能夠判斷這個數字合法呢?不必定,由於你不知道里面究竟是存在一個仍是存在多個?存在一個,合法,存在多個,不合法,這是一個核心問題!!!
這個問題怎麼弄?
用InStr和InStrRev這兩個函數就能夠搞定。
InStr 函數介紹:
InStrRev函數介紹:
好比:我這裏有一個字符串:」351658」
當咱們檢查到3時,發現不等於0,則表示裏面有3。而後咱們在看是否只有一個。
InStr(」351658」, 」3」) = 1
從左往右找,3出如今第一個位置。
InStrRev(」351658」, 」3」) = 1
從右往左找,3也出如今第一個位置。
此時InStr和InStrRev的值相等,說明裏面就只有一個3,合法。
當咱們檢查到5時,發現不等於0,則表示裏面有5。而後咱們在看是否只有一個。
InStr(」351658」, 」5」) = 2
從左往右找,5出如今第二個位置。
InStrRev(」351658」, 」5」) = 5
從右往左找,5出如今第五個位置。
此時InStr和InStrRev的值不相等,說明裏面至少有兩個5,非法。
好理解吧?!
如下檢查列中的數字和檢查區中的數字,方法相似,不在重複講解。
還有一種錯誤:就是咱們在出題的時候,輸入錯誤,輸入了一個字母,或者輸入了大於9或小於1的數字,這都是不符合數獨遊戲規則的,咱們也要將他判斷出來。
因而,咱們還得寫一個判斷是否是數字的函數。
很簡單,一句話搞定,經過IsNumeric函數來處理。而後用再判斷是否大於9或小於1。
還有一種狀況就是,數獨出題區是滿的,沒有空單元格了,這也是一種錯誤。因此,咱們對GetShuDuList函數作些許修改。
解釋:
增長第11行至18行代碼,用來判斷輸入非法的字符和大於9小於1的數字。
增長第24行至27行,用來判斷是否是滿格數獨。
好,如今咱們把這個數獨問題的程序基本上寫完了,而後咱們須要一個主程序來讓他運行起來。
主程序-程序入口
ShuDuKu() (核心函數)
解釋:
第2-3行:定義一個變量,賦值「此題無解」。當沒有獲得正確結果時,就會執行第24行代碼,彈出「此題無解」的對話框。
第4行:公共變量賦值,表示數獨尺寸爲9X9的數獨。
第5-8行:獲得出題區的起始行、列號和做答區的起始行、列號。
第11行:調用GetShuDuList函數,獲得數獨模型列表。
第12行:調用CheckOkShuDu函數,檢查出題是否正確。
第15行:調用CanPointInfo函數,獲得空單元格的點位信息和可能存入的數字信息。
第18行:從CanPointList數組中取出最後一個數組元素,複製給點位信息數組。
第20行:調用DelArrLast函數,刪除CanPointList數組的最後一個元素。
第22行:調用TryInPoint函數,開始嘗試填入數字並檢查合法性。
若是嘗試完成,獲得正確結果,在TryInPoint函數裏面調用了ShowOkShuDu函數,最後有一個End語句,終止了整個程序,因此第24行就不會執行。
若是嘗試完成,沒有獲得正確結果,則在TryInPoint函數中不會調用ShowOkShuDu函數,也就不會執行ShowOkShuDu函數中的End語句,程序會這行主程序的第24行語句,彈出此題無解信息。
好啦,如今整個程序就寫完了。
固然,爲了程序有更好的交互性,還能夠在裏面加入更多的人機交互的內容,好比如何繪製表格,如何給表格添加灰色背景,如何將出題區的內容複製到做答區等等,這不是這篇文章的重點,這裏不詳細介紹。
後面附上數獨源文件,你們能夠看源代碼瞭解。
連接:https://pan.baidu.com/s/1KwKXaU0ipKCryriImP9zLQ
提取碼:zd4x