2019年1月26日 星期六

Sudoku 獨數

關於 Sudoku 的玩法可以參考 wikipedia
https://zh.wikipedia.org/wiki/%E6%95%B8%E7%8D%A8

規則滿簡單的,但是玩起來說實在滿花時間的,這東西大概1998年,我還在當兵時流行了一陣子,現在還是會在一些報紙、雜誌中看到,在1998年時,就寫了一個簡單的 C code來計算答案。

最近公司導入新的制度,面試要考類似 leetcode 的coding題目,由於要出題,左想右想,覺得Sudoku 這是個不錯的好題目,規則簡單,實作上會用到些程式的觀念,也是現實生活中會看到的題目,而不是為了 coding 而 coding 的題目。

說實在二十年前的程式想法,早就已經忘光了,只好重新構思解法,年紀與經驗增長,同時加上 google 的威力,其實很快就大概有了方向。

  1. 首先看來需要一個 "檢查" 規則的 function
  2. 然後就是需要一個產生 1~9 的 function,產生後就把數字填入試試看,符合規則的話,就再試下一個空格的數字。

好像還差了什麼?Yes,如果目前的位置所有的數字都不合規則的話,就必需再回頭改一下上一次位置的數字,這樣子的話才可以把所有的狀況都試完,用 recursive 的 function 來說就是要做到 Recursive backtracking 回溯的功能。

想法有了就是寫 code 了,之前用 C,現在學習 AI 都用 Python,改用 Python 來試試好了。感謝google的威力,網路上就是現成的 Python code了。
https://stackoverflow.com/questions/1697334/algorithm-for-solving-sudoku

只是要做為 interview 的解答,希望要求再高一些,程式可讀再高一些,stackoverflow 上面的,還有些改善的空間。
  1. isValid function : 可讀可以再加強些,稍微改成 check row, check colum and check grid 看來就完美多了
  2. findNextCellToFill function : 這個只是找出下一個要填的位置,功用不大,其實可以不用這個 function
  3. solveSudoku function : 這個 recursive function,傳入 i, j 當作位置,要解決的位置,其實可以整合成 position 一個變數就好,上面的 findNextCellToFill 就可以變成簡單的 position + 1
  4. 最後加上些 test case,看看輸出是否正確就完成了
以下就是修正完的 code,看來比之前的好一些,思路變清楚,結構也易讀,以下是source code 的部分。


# -*- coding: utf-8 -*-
"""
Created on Thu Dec  6 11:26:29 2018

@author: Robin_Chiu
"""

# A standard Sudoku puzzle consists of a grid of 9 blocks. Each block contains 9 boxes arranged in 3 rows and 3 columns.
# · Each column must contain all of the numbers 1 through 9 and no two numbers in the same column of a Sudoku puzzle can be the same.
# · Each row must contain all of the numbers 1 through 9 and no two numbers in the same row of a Sudoku puzzle can be the same.
# · Each block must contain all of the numbers 1 through 9 and no two numbers in the same block of a Sudoku puzzle can be the same.
 
def isValid(grid, i, j, e):
    # check the row
    rowOk = all([e != grid[i][x] for x in range(9)])
    if not rowOk:
        return False
    
    # check the column
    columnOk = all([e != grid[x][j] for x in range(9)])
    if not columnOk:
        return False
    
    # check the grid
    # finding the top left x,y co-ordinates of the section containing the i,j cell
    secTopX, secTopY = 3 *(i//3), 3 *(j//3) #floored quotient should be used here. 
    for x in range(secTopX, secTopX+3):
        for y in range(secTopY, secTopY+3):
            if grid[x][y] == e:
                return False
            
    # pass all the check return True
    return True

def solveSudoku(grid, pos=0):
    # calculate the i, j position
    i = pos//9
    j = pos%9
    
    # if it's the end stop the recursive 
    if pos == 81:
        for each in grid:
            print(each)        
    # if there is number in it, check the next one
    elif grid[i][j] != 0:
        solveSudoku(grid, pos+1)
    # find the valid number in i, j
    else:
        for e in range(1,10):
            if isValid(grid,i,j,e):
                grid[i][j] = e
                solveSudoku(grid, pos+1)
                # Undo the current cell for backtracking
                grid[i][j] = 0
                

def main():
    puzzle1 = [[5, 1, 7, 6, 0, 0, 0, 3, 4], 
             [2, 8, 9, 0, 0, 4, 0, 0, 0], 
             [3, 4, 6, 2, 0, 5, 0, 9, 0], 
             [6, 0, 2, 0, 0, 0, 0, 1, 0], 
             [0, 3, 8, 0, 0, 6, 0, 4, 7], 
             [0, 0, 0, 0, 0, 0, 0, 0, 0], 
             [0, 9, 0, 0, 0, 0, 0, 7, 8], 
             [7, 0, 3, 4, 0, 0, 5, 6, 0], 
             [0, 0, 0, 0, 0, 0, 0, 0, 0]]
 
    puzzle2 = [[5, 3, 0, 0, 7, 0, 0, 0, 0],
              [6, 0, 0, 1, 9, 5, 0, 0, 0],
              [0, 9, 8, 0, 0, 0, 0, 6, 0],
              [8, 0, 0, 0, 6, 0, 0, 0, 3],
              [4, 0, 0, 8, 0, 3, 0, 0, 1],
              [7, 0, 0, 0, 2, 0, 0, 0, 6],
              [0, 6, 0, 0, 0, 0, 2, 8, 0],
              [0, 0, 0, 4, 1, 9, 0, 0, 5],
              [0, 0, 0, 0, 8, 0, 0, 7, 9]]
    
    solveSudoku(puzzle1)
    print()
    solveSudoku(puzzle2)

if __name__ == '__main__':
    main()





 




2019年1月11日 星期五

奇雞連連

奇雞連連 是一款小朋友的桌遊,有關於詳細的玩法介紹可以參考以下網址 https://www.youtube.com/watch?v=h7tnUZACSA8

   其實這已經是2017 一月的事了,小朋友學校老師鼓勵同學玩些桌遊,也買了幾套桌遊供小朋友在學校玩耍,有天回來跟我說他玩了"奇雞連連"打敗了不少同學,我上網看了一下這不是 "井字遊戲" 嗎?但是深入了解後,發現玩法不太一樣,還有記憶力的考驗。上Play store想找下相關的APP download 來玩玩看,結果竟然找不到,雖然說可以用不同大小的紙張 or 杯子之類的來代替,但覺得還是不太方便,之前有開發 "井字遊戲" 的經驗,就想說這個應該介面稍微改一下就OK了,於是就出現了這一個 APP。

   用電腦就是方便增加了 "反悔" 功能,可以一步一步退回,方便人類重下分析不同的狀況,使用APP再更深入研究這個遊戲之後,發現其實先手下的人是一定獲勝的, 改天再整理必勝的技巧來分享下,先手必勝的話,這個遊戲算是有設計上的缺陷的,但就一款訓練小朋友分析、記憶的遊戲來說,還是一款不錯的遊戲,至於勝負, 所謂 "勝敗乃兵家常事" ,就不用太計較了。

    以下是 Play Store 的連結 和 截圖,有興趣的人可以download玩玩看,也有加入AI對戰的功能,但這個AI的設計比 "井字遊戲" 難,因為棋子可以移動,所以decision tree的展開會有問題,尚未想到比較好的做法,有任何建議也歡迎提出。

https://play.google.com/store/apps/details?id=com.robin.gobblet