有個運營妹紙心算找不到數獨答案,心一橫乾脆寫代碼實現

0、代碼寫很差,心痛吖

家裏新購了一個數獨,週四午餐後消食便拿起來玩,半小時過去了,一小時過去了,一直沒成功……超級不服氣的,這道難題想逼我是嗎?乾脆直接寫python代碼破解好了!半小時後代碼還沒寫好,我被本身蠢哭了。在寫代碼這件事上,我顯然手生得很呢。值得欣慰的是,我起碼知道生物腦沒法完成的,電腦能夠代勞,只不過這代碼依然須要生物腦來完成。python

趁着端午小長假有點時間,就來琢磨下這件事好了。算法

一、不擅長寫代碼,總擅長需求分析吧?

計算目標:找到數獨的答案。是找一些答案,仍是找出全部答案呢?後者難度高,且包含前者,我選擇後者。框架

數獨規則:在下述六角形棋盤的12個位置中分別放入數字1至12,使得圖中每條連線4個位置加和爲26。dom

shudu

二、生物腦和電腦在演算方法上的迥異

個人生物腦是如未嘗試解出答案的呢?函數

從棋盤取下全部數字,先隨意選擇4個數字放入某條線使之加和爲26,而後再選擇3個數字放入與此線有交點的另一條線使之加和爲26,而後嘗試第3條線……結果是,每次嘗試到第三、4條線時開始吃力,偶爾能知足5條線但最後一條線沒法知足條件。優化

我將如何用代碼來指揮電腦演算呢?用代碼復原生物腦的思路,顯然是很是蠢的解法。大家知道我是如何知道本身蠢的嗎?由於上述思路拆解出來就是:spa

  1. 指定前3個位置數值,計算得出第4個數,使得一條線==26
  2. 選擇剩餘5條線中已獲得賦值的位置最多的那條線,計算得出使該條線加和爲26的數值
  3. 循環以上,嘗試使全部線的加和都知足26;若是不知足,則從新指令最初3個位置的數值

我徹底沒法快速用代碼表達出以上思路,基於此不難自我判斷上述算法是愚蠢的。code

通過一些摸索後,我把碳基問題抽象爲下述硅基問題:cdn

  1. 棋盤12個位置的不一樣取值,構成了一個列表。
  2. 天然數1至12有多少種排列組合的方式,就有多少個上述列表。
  3. 對於每一個列表,計算特定位置的加和(6條線)是否知足條件,是通用的,能定義爲函數。
  4. 窮舉天然數1至12全部的排列組合方式。

3. 多說無益,秀出代碼

3.1 版本1.0:能找到部分答案,但找不到全部答案

import math
import random

def areyouwin(point_list):
    if point_list[0] + point_list[6] + point_list[11] + point_list[4] != 26 :
        return False
    elif point_list[0] + point_list[7] + point_list[8] + point_list[2] != 26 :
        return False
    elif point_list[1] + point_list[7] + point_list[6] + point_list[5] != 26 :
        return False
    elif point_list[1] + point_list[9] + point_list[8] + point_list[3] != 26 :
        return False
    elif point_list[4] + point_list[9] + point_list[10] + point_list[2] != 26 :
        return False
    elif point_list[3] + point_list[10] + point_list[11] + point_list[5] != 26 :
        return False
    else:
        return True

point_list = [1,2,3,4,5,6,7,8,9,10,11,12]
k = 0
while k < 10 :#此處k表示找出10個答案,想要更多答案就改k的值便可
    random.shuffle(point_list)#新知識點:隨機打亂列表
    if areyouwin(point_list):
        print(point_list,k+1)
        k = k + 1
複製代碼

3.2 版本2.0:本身動手,窮舉全部排列組合

初步搜索,我沒有找到如何實現輸出列表項所有的排列組合。鑑於很是清楚本身對排列組合的代碼實現接近於0經驗,因而故意想要本身寫代碼實現。blog

計算目標的大框架已經實現,如今只缺排列組合。那我聚焦於此:

子目標:輸出天然數1至12的全部排列組合

演算方法是怎樣的呢?

  1. 位置1的取值範圍爲1至12,先取值1,完成下列全部演算後,再取值2,完成下列全部演算,再取值3……直至完成取值範圍內的全部數值
  2. 位置2的取值範圍爲除了位置1當前取值以外的11個數,先取值其中一個數值,完成下列全部演算後,再取值另一個數值,完成下列全部演算後,再取值第3個數值……直至完成取值範圍內的全部數值
  3. 位置3的取值範圍爲除了位置1和位置2當前取值以外的10個數,先取值其中一個數值,完成下列全部演算後,再取值另一個數值,完成下列全部演算後,再取值第3個數值……直至完成取值範圍內的全部數值
  4. 位置4的取值範圍爲除了位置1和位置2當前取值以外的9個數,……
  5. ……
  6. 位置12的取值範圍爲除了位置1至11當前取值以外的11個數,此爲惟一取值。輸出位置1至位置12的當前取值

這個演算方法可行嗎?代碼將如何寫?彷佛有些困難。那我我先試着簡化問題:輸出天然數1至3的全部排列組合。並寫出代碼來驗證結果是否正確。

point_list = [1,2,3]

#列表A賦值給列表B時須要用切片的方式,不然列表B改變時會引起列表A改變!
point_1_list = point_list[:]
for i in point_1_list:#實現位置1的循環
    point_2_list = point_1_list[:]
    point_2_list.remove(i) #位置2的取值範圍是位置1的取值範圍移除位置1的當前值
    #print(point_2_list)
    for j in point_2_list:
        #print(j,"j的值")
        point_3_list = point_2_list[:]
        point_3_list.remove(j)#位置3的取值範圍是位置2的取值範圍移除位置1的當前值
        k = point_3_list[0]#位置3的取值範圍只剩1個數值
        print(i,j,k)#打印排列組合結果
複製代碼

在完成上述代碼的過程當中,剛開始列表賦值並無採用切片的方式,致使全部列表均發生改變。無他,仍是反映我對列表的操做不熟。

先不論代碼是否簡潔優雅,至少它在功能上實現了。那麼如今,先試着用這個思路實現天然數1至12的所有排列組合,並計算得出數獨26的解法共有多少個!

下面就是不簡潔不優雅版本的數獨全部答案的代碼:

def areyouwin(point_list):
    if point_list[0] + point_list[6] + point_list[11] + point_list[4] != 26 :
        return False
    elif point_list[0] + point_list[7] + point_list[8] + point_list[2] != 26 :
        return False
    elif point_list[1] + point_list[7] + point_list[6] + point_list[5] != 26 :
        return False
    elif point_list[1] + point_list[9] + point_list[8] + point_list[3] != 26 :
        return False
    elif point_list[4] + point_list[9] + point_list[10] + point_list[2] != 26 :
        return False
    elif point_list[3] + point_list[10] + point_list[11] + point_list[5] != 26 :
        return False
    else:
        return True

point_list = [1,2,3,4,5,6,7,8,9,10,11,12]

#列表A賦值給列表B時須要用切片的方式,不然列表B改變時會引起列表A改變!
point_1_list = point_list[:]
for p_1 in point_1_list:#實現位置1的循環
    point_2_list = point_1_list[:]
    point_2_list.remove(p_1) #位置2的取值範圍是位置1的取值範圍移除位置1的當前值
    for p_2 in point_2_list:
        point_3_list = point_2_list[:]
        point_3_list.remove(p_2)#位置3的取值範圍是位置2的取值範圍移除位置1的當前值
        for p_3 in point_3_list:
            point_4_list = point_3_list[:]
            point_4_list.remove(p_3)
            for p_4 in point_4_list:
                point_5_list = point_4_list[:]
                point_5_list.remove(p_4)
                for p_5 in point_5_list:
                    point_6_list = point_5_list[:]
                    point_6_list.remove(p_5)
                    for p_6 in point_6_list:
                        point_7_list = point_6_list[:]
                        point_7_list.remove(p_6)
                        for p_7 in point_7_list:
                            point_8_list = point_7_list[:]
                            point_8_list.remove(p_7)
                            for p_8 in point_8_list:
                                point_9_list = point_8_list[:]
                                point_9_list.remove(p_8)
                                for p_9 in point_9_list:
                                    point_10_list = point_9_list[:]
                                    point_10_list.remove(p_9)
                                    for p_10 in point_10_list:
                                        point_11_list = point_10_list[:]
                                        point_11_list.remove(p_10)
                                        for p_11 in point_11_list:
                                            point_12_list = point_11_list[:]
                                            point_12_list.remove(p_11)
                                            p_12=point_12_list[0]
                                            one_list=[p_1, p_2, p_3, p_4, p_5, p_6, p_7, p_8, p_9, p_10, p_11, p_12]

                                            if areyouwin(one_list):
                                                print(one_list)
複製代碼

3.3 還有什麼遺憾?

上述代碼for循環嵌套代碼部分,不夠簡潔優雅。如何優化,是我接下來須要再琢磨的。笨辦法也是辦法,終於把數獨的答案所有演算出來了,終於能安心睡個好覺了。

這就完了嗎?固然不!這個碳基轉硅基的解決方式中,有個內核的問題是,棋盤是個對稱的六角星型,因而碳基世界的正確答案,其實只需硅基代碼演算出答案的1/6。

以正確答案[1,2,3,6,11,7,5,12,10,8,4,9]爲例,棋盤每轉動60°就產生一個新答案: [2,3,6,11,7,1,12,10,8,4,9,5] [3,6,11,7,1,2,10,8,4,9,5,12] [6,11,7,1,2,3,8,4,9,5,12,10] [11,7,1,2,3,6,4,9,5,12,10,8] [7,1,2,3,6,11,9,5,12,10,8,4]

咿??如何優化代碼,能撇掉那5/6臃腫的答案呢?——難度超綱,我仍是暫時放下吧。

一句話小結

代碼寫得好很差,手熟與否很關鍵呀!而手熟並不是簡單的常常寫就夠了,而是要專門分解出哪些基礎功或知識點薄弱,並重點突破之。此乃大咖們常曰的「刻意練習」。

相關文章
相關標籤/搜索