1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        海龜繪圖案例分析之移動(dòng)火柴變等式游戲

        共 2323字,需瀏覽 5分鐘

         ·

        2022-02-15 05:50

        說在前面

        移動(dòng)一根火柴棒使等式成立,是一種簡單有趣的智力游戲,只需具備簡單的算術(shù)知識(shí),就能參與游戲,老少皆宜,深受大家的喜愛。前幾天,微信群里有老師聊起了這個(gè)游戲,毛毛老師還編寫了創(chuàng)建題庫的程序,好評(píng)如潮。在毛毛老師創(chuàng)意的啟發(fā)下,我對(duì)代碼做了進(jìn)一步改進(jìn),并利用海龜繪圖的鼠標(biāo)響應(yīng)功能,制作了可以移動(dòng)火柴棒的小游戲。

        由于該小游戲涉及功能較多,總計(jì)400多行代碼(含注釋),文章篇幅較大,我把它分成了三個(gè)部分,力求解析到位。今天和大家分享第一部分——?jiǎng)?chuàng)建題庫。

        1.游戲規(guī)則

        只允許移動(dòng)一根火柴棒使等式成立。移動(dòng)火柴棒的方法有三種:

        一是在數(shù)字內(nèi)部移動(dòng)火柴棒。例如原始等式為9-5=1,則可移動(dòng)數(shù)字9的一根火柴棒,使其變成6,從而獲得正確等式6-5=1。

        二是在數(shù)字之間移動(dòng)火柴棒。例如原始等式為5-7=5,則可將數(shù)字7的一根火柴棒移到左邊數(shù)字5上,使得7變成1,5變成6,從而獲得正確等式6-1=5。

        三是在數(shù)字和運(yùn)算符之間移動(dòng)火柴棒。例如原始等式為5+3=3,則可將運(yùn)算符的一根火柴棒移到數(shù)字5上,使得+變成-,5變成6,從而獲得正確等式6-3=3。

        2.數(shù)據(jù)結(jié)構(gòu)

        根據(jù)上述移動(dòng)火柴棒的方法,我們可以創(chuàng)建3個(gè)字典分別用來存儲(chǔ)移動(dòng)、增加或減少火柴棒后,各數(shù)字可能變成的新數(shù)字。

        例如,我們創(chuàng)建字典keep_dic用來存儲(chǔ)在數(shù)字內(nèi)部移動(dòng)火柴棒后,各數(shù)字可能變成的新數(shù)字。如下圖所示,數(shù)字2可以變成數(shù)字3,我們記作keep_dic[2]=[3];數(shù)字3可以變成數(shù)字2或5,我們記作keep_dic[3]=[2,5];同理keep_dic[5]=[3],keep_dic[6]=[0,9]。

        因?yàn)閿?shù)字1不能變成任何數(shù)字,故我們記作keep_dic[1]=[]。根據(jù)上述規(guī)則,我們定義keep_dic ={1:[],2:[3],3:[2,5],4:[],5:[3],6:[0,9],7:[],8:[],9:[0,6],0:[6,9]}。

        同理,我們創(chuàng)建字典add_dic用來存儲(chǔ)為數(shù)字增加一根火柴棒后,各數(shù)字可能變成的新數(shù)字。如下圖所示,數(shù)字1增加一根火柴后可以變成數(shù)字2,數(shù)字5增加一根火柴后可以變成數(shù)字6或9。

        由此我們定義add_dic ={1:[7],2:[],3:[9],4:[],5:[6,9],6:[8],7:[],8:[],9:[8],0:[8]}。

        同理,我們定義dec_dic ={1:[],2:[],3:[],4:[],5:[],6:[5],7:[1],8:[0,6,9],9:[3,5],0:[]},用來存儲(chǔ)為數(shù)字減少一根火柴棒后,各數(shù)字可能變成的新數(shù)字。

        另外,我們還創(chuàng)建了兩個(gè)空列表ques和anss,分別用來存儲(chǔ)有效問題和對(duì)應(yīng)答案。

        3.算法思想

        程序采用枚舉算法,采用四重循環(huán),依次枚舉運(yùn)算符和3個(gè)運(yùn)算數(shù)的值。

        自定義函數(shù)get_question_bank(ques,anss)用來創(chuàng)建題庫,它通過枚舉不同運(yùn)算符和操作數(shù)尋找所有可能的原始算術(shù)和答案。程序巧妙使用f-string來生成緊湊的字符串,并利用eval()函數(shù)計(jì)算出字符串表達(dá)式的結(jié)果,生成通用的關(guān)系表達(dá)式,以便篩選出等號(hào)不成立的原始算式。參考代碼如下:

        '''函數(shù)功能:通過枚舉不同運(yùn)算符和操作數(shù)尋找所有可能的原始算式和答案函數(shù)名:get_question_bank(ques,anss)參數(shù)表:ques -- 用來存儲(chǔ)問題(原始算式)的列表;        sign -- 用來存儲(chǔ)答案(正確算式)的列表,每個(gè)問題可能有多個(gè)答案。返回值:沒有返回值。'''   def get_question_bank(ques,anss): #獲取題庫(包括問題和答案)    for sign in ['+','-']:        for a in range(10):            for b in range(10):                for c in range(10):                    if eval(f'{a}{sign}') !=c: #只考慮等號(hào)不成立的原始算式                        ans = fun(a, b, c,sign) #調(diào)用尋找答案的驅(qū)動(dòng)函數(shù),返回答案                        if ans != []: #若問題有解,則存儲(chǔ)問題和答案                           ques.append(f'{a}{sign}={c}') #存儲(chǔ)問題(原始算式)                            anss.append(ans) #存儲(chǔ)答案(正確算式)

        知識(shí)小貼士

        eval()函數(shù)用于返回傳入字符串的表達(dá)式的結(jié)果,還可以實(shí)現(xiàn)list、dict、tuple與str之間的轉(zhuǎn)化。

        其語法格式為:eval(expression[,globals[, locals]])

        參數(shù)說明:

        expression :表達(dá)式。

        globals :(可選參數(shù))變量作用域,全局命名空間,如果被提供,則必須是一個(gè)字典對(duì)象。

        locals :(可選參數(shù))變量作用域,局部命名空間,如果被提供,可以是任何映射對(duì)象。

        當(dāng)后兩個(gè)參數(shù)都為空時(shí),等價(jià)于eval(expression),就是計(jì)算表達(dá)式的值。例如eval('pow(2,3)')的值為8,eval('2+3<5')的值為False;當(dāng)x=3時(shí),eval(f'x+5*4')的值為23,但是eval(f'{x+5}*4')的值為32,因?yàn)榘鸦ɡㄌ?hào)內(nèi)部的表達(dá)式看作一個(gè)整體。

        當(dāng)locals參數(shù)為空,globals參數(shù)不為空時(shí),先查找globals參數(shù)中是否存在變量,并計(jì)算。例如eval("{'name':'Lucy','age':age}",{"age":18})的值為字典{'name': 'Lucy', 'age': 18}。

        當(dāng)兩個(gè)參數(shù)都不為空時(shí),先查找locals參數(shù),再查找globals參數(shù)。

        eval()與input()函數(shù)配合,可以方便地實(shí)現(xiàn)表達(dá)式的還原與計(jì)算,例如執(zhí)行語句>>>eval(input("輸入一個(gè)表達(dá)式:")),當(dāng)用戶輸入字符串"3+2*6"時(shí),系統(tǒng)輸出15。

        但要注意其帶來的安全問題,因?yàn)楸粓?zhí)行的字符串表達(dá)式可能是某個(gè)系統(tǒng)命令。例如執(zhí)行語句>>>eval(input("輸入一個(gè)表達(dá)式:")),當(dāng)用戶惡意輸入字符串"__import__('os').system('dir')"時(shí),會(huì)顯示當(dāng)前目錄文件。


        4.驅(qū)動(dòng)函數(shù)

        如上所述,有三種移動(dòng)火柴棒的方法。對(duì)應(yīng)每一種方法,我們都可以設(shè)置一個(gè)自定義函數(shù)來枚舉可能的答案。為了體現(xiàn)模塊化編程思想,我們?cè)O(shè)置一個(gè)驅(qū)動(dòng)函數(shù)fun(),依次調(diào)用這三個(gè)自定義函數(shù),把所有可能的答案統(tǒng)一存儲(chǔ)到列表ans中。參考代碼如下:

        '''函數(shù)功能:依次調(diào)用fun_1,fun_2,fun_3,為原始算術(shù)尋找可能的答案函數(shù)名:fun(a, b, c, sign)參數(shù)表:a,b,c -- 依次表示從左到右的3個(gè)操作數(shù)(單個(gè)數(shù)字);        sign -- 運(yùn)算符(加或減)。返回值:返回存儲(chǔ)了所有答案的列表ans,列表的每個(gè)元素都是一個(gè)表示答案的字符串。'''    def fun(a, b, c, sign): #驅(qū)動(dòng)函數(shù)    ans = []    fun_1(ans, a, b, c, sign) #數(shù)字內(nèi)部移動(dòng)火柴棒    fun_2(ans, a, b, c, sign) #數(shù)字之間移動(dòng)火柴棒    fun_3(ans, a, b, c, sign) #數(shù)字和運(yùn)算符之間移動(dòng)火柴棒return ans

        5.功能模塊

        接下來依次給出三種方法對(duì)應(yīng)的自定義函數(shù)。

        '''函數(shù)功能:利用字典keep_dic,通過在數(shù)字內(nèi)部移動(dòng)火柴棒來改變?cè)摂?shù)字函數(shù)名:fun_1(ans, a, b, c,sign)參數(shù)表:ans -- 列表的值為一個(gè)字符串,表示一個(gè)答案(正確算式);        a,b,c -- 依次表示從左到右的3個(gè)操作數(shù)(單個(gè)數(shù)字);        sign -- 運(yùn)算符(加或減)。返回值:沒有返回值。'''def fun_1(ans, a, b, c,sign): #數(shù)字內(nèi)部移動(dòng)火柴棒    for i in keep_dic[a]: #在數(shù)字a內(nèi)部移動(dòng)火柴棒        if eval(f'{i}{sign}') == c:            ans.append(f'{i}{sign}={c}')    for i in keep_dic[b]: #在數(shù)字b內(nèi)部移動(dòng)火柴棒        if eval(f'{a}{sign}{i}') == c:            ans.append(f'{a}{sign}{i}={c}')    for i in keep_dic[c]: #在數(shù)字c內(nèi)部移動(dòng)火柴棒        if eval(f'{a}{sign}') == i:            ans.append(f'{a}{sign}={i}') '''函數(shù)功能:利用字典dec_dic和add_dic,通過在數(shù)字之間移動(dòng)火柴棒同時(shí)改變2個(gè)數(shù)字函數(shù)名:fun_2(ans, a, b, c,sign)參數(shù)表:ans -- 列表的值為一個(gè)字符串,表示一個(gè)答案(正確算式);        a,b,c -- 依次表示從左到右的3個(gè)操作數(shù)(單個(gè)數(shù)字);        sign -- 運(yùn)算符(加或減)。返回值:沒有返回值。'''               def fun_2(ans, a, b, c,sign): #數(shù)字之間移動(dòng)火柴棒    for i in dec_dic[a]: #從a中拿一根火柴到b中        for j in add_dic[b]:            if eval(f'{i}{sign}{j}') == c:                ans.append(f'{i}{sign}{j}={c}')    for i in dec_dic[a]: #從a中拿一根火柴到c中        for j in add_dic[c]:            if eval(f'{i}{sign}') == j:                ans.append(f'{i}{sign}={j}')    for i in dec_dic[b]: #從b中拿一根火柴到a中        for j in add_dic[a]:            if eval(f'{j}{sign}{i}') == c:                ans.append(f'{j}{sign}{i}={c}')                flag = True    for i in dec_dic[b]: #從b中拿一根火柴到c中        for j in add_dic[c]:            if eval(f'{a}{sign}{i}') == j:                ans.append(f'{a}{sign}{i}={j}')    for i in dec_dic[c]: #從c中拿一根火柴到a中        for j in add_dic[a]:            if eval(f'{j}{sign}') == i:                ans.append(f'{j}{sign}={i}')    for i in dec_dic[c]: #從c中拿一根火柴到b中        for j in add_dic[b]:            if eval(f'{a}{sign}{j}') == i:                ans.append(f'{a}{sign}{j}={i}') '''函數(shù)功能:利用字典dec_dic或add_dic,通過在數(shù)字和運(yùn)算符之間移動(dòng)火柴棒同時(shí)改變1個(gè)數(shù)字和運(yùn)算符函數(shù)名:fun_3(ans, a, b, c,sign)參數(shù)表:ans -- 列表的值為一個(gè)字符串,表示一個(gè)答案(正確算式);        a,b,c -- 依次表示從左到右的3個(gè)操作數(shù)(單個(gè)數(shù)字);        sign -- 運(yùn)算符(加或減)。返回值:沒有返回值。'''                        def fun_3(ans, a, b, c,sign): #數(shù)字和運(yùn)算符之間移動(dòng)火柴棒    if sign == '+': #從運(yùn)算符中拿一根火柴到數(shù)字        sign, dic = '-', add_dic    else:        sign, dic = '+', dec_dic    for i in dic[a]: #在運(yùn)算符與數(shù)字a間移動(dòng)火柴        if eval(f'{i}{sign}') == c:            ans.append(f'{i}{sign}={c}')    for i in dic[b]: #在運(yùn)算符與數(shù)字b間移動(dòng)火柴        if eval(f'{a}{sign}{i}') == c:            ans.append(f'{a}{sign}{i}={c}')    for i in dic[c]: #在運(yùn)算符與數(shù)字c間移動(dòng)火柴        if eval(f'{a}{sign}') == i:????????????ans.append(f'{a}{sign}={i}')

        代碼說明:

        函數(shù)fun_1()利用字典keep_dic,通過在數(shù)字內(nèi)部移動(dòng)火柴棒來改變?cè)摂?shù)字。函數(shù)依次枚舉了在數(shù)字a、b、c內(nèi)部移動(dòng)火柴棒的情形,一旦找到滿足條件的等式,就將其作為答案插入到列表ans中。

        函數(shù)fun_2()利用字典dec_dic和add_dic,通過在數(shù)字之間移動(dòng)火柴棒同時(shí)改變2個(gè)數(shù)字。函數(shù)依次枚舉了從a拿一根火柴到b,從a拿一根火柴到c,從b拿一根火柴到a等6種情形。一旦找到滿足條件的等式,就將其作為答案插入到列表ans中。

        函數(shù)fun_3()利用字典dec_dic或add_dic,通過在數(shù)字和運(yùn)算符之間移動(dòng)火柴棒同時(shí)改變1個(gè)數(shù)字和運(yùn)算符。函數(shù)先判斷運(yùn)算符是'+'還是'-',若為'+',則從運(yùn)算符中拿一根火柴到數(shù)字,即把運(yùn)算符改成'-',同時(shí)設(shè)置字典dic 的值為add_dic,表示數(shù)字要增加一根火柴棒;反之則設(shè)置成從數(shù)字中拿一根火柴到運(yùn)算符。接下來依次枚舉在運(yùn)算符與數(shù)字a、b、c間移動(dòng)火柴的情形,一旦找到滿足條件的等式,就將其作為答案插入到列表ans中。

        6.程序改進(jìn)

        前面3個(gè)自定義函數(shù),功能都很明確,算法清晰明了,代碼實(shí)現(xiàn)也很直接,可讀性較強(qiáng)。但是它們都有一個(gè)缺陷,就是重復(fù)的代碼太多了,顯得有些拖沓和繁瑣。

        我們經(jīng)常將重復(fù)出現(xiàn)的順序結(jié)構(gòu)程序轉(zhuǎn)換成循環(huán)結(jié)構(gòu),那么該如何精簡代碼呢?

        以fun_1()函數(shù)為例,它用了3個(gè)結(jié)構(gòu)相同的一重循環(huán)語句,我們完全可以把它精簡成一個(gè)二重循環(huán)語句。

        我們先把a(bǔ)、b、c三個(gè)變量存儲(chǔ)到列表role中,定義role = [a, b, c],這樣就可以用for循環(huán)遍歷它們了。因?yàn)閍、b、c在算式中的位置不同,原fun_1()函數(shù)的3個(gè)for循環(huán)中的f字符串寫法也不一樣,但現(xiàn)在分別用role[0]、role[1]、role[2]來代替 a、b、c,就可以統(tǒng)一設(shè)置f字符串了。

        根據(jù)這個(gè)思路,我編寫了如下代碼:

        但實(shí)踐證明這段代碼是錯(cuò)誤的。

        問題出在哪里呢?

        沒錯(cuò),問題就出在role[k] = i語句上。

        當(dāng)k==0時(shí),枚舉keep_dic[role[k]]就相當(dāng)于枚舉keep_dic[a],此時(shí)修改role[k] = i,相當(dāng)于修改數(shù)字a的值,但同時(shí)也修改了列表role的元素值,導(dǎo)致后面枚舉另兩個(gè)數(shù)字時(shí),role[0]不再等于變量a的值,而是其移動(dòng)火柴棒后的新數(shù)字。

        那該怎么辦呢?既要使用列表遍歷三個(gè)數(shù)字,枚舉在它們內(nèi)部移動(dòng)火柴棒后的可能值,又不能修改列表元素。有兩全其美的方法嗎?

        當(dāng)然有!辦法就是在內(nèi)層循環(huán)中復(fù)制一份列表role的拷貝,然后在枚舉過程中修改這份拷貝,而不是role本身。參考代碼如下:

        再來看fun_2()函數(shù),它用6個(gè)結(jié)構(gòu)相同的二重循環(huán),依次枚舉了在數(shù)字間移動(dòng)火柴棒的6種情形。因?yàn)閿?shù)字a、b、c在算式中的位置不同,原fun_2()函數(shù)的6個(gè)for循環(huán)中的f字符串寫法也不一樣,所以每一個(gè)二重循環(huán)的語句都有微妙的差別,合并代碼的難度很大。

        但它們的結(jié)構(gòu)如此一致,肯定能夠用一個(gè)循環(huán)語句來代替重復(fù)的順序結(jié)構(gòu)!

        必須要有堅(jiān)定的信念,才能找到堅(jiān)持的動(dòng)力!

        在精簡fun_1()函數(shù)的實(shí)踐過程中,我們采用了2個(gè)重要的方法:創(chuàng)建列表role存儲(chǔ)變量a、b、c,和在循環(huán)體內(nèi)創(chuàng)建列表role的拷貝op。

        我們可以把舊有的經(jīng)驗(yàn)遷移到解決新問題中去。

        但這里有一個(gè)新的問題:從一個(gè)數(shù)字中拿走的火柴棒可能放到另外任意一個(gè)數(shù)字中,假設(shè)被拿走火柴棒的數(shù)字在列表role中的下標(biāo)為i,則另外兩個(gè)數(shù)字的下標(biāo)分別是多少呢?

        若i=0,則另外兩個(gè)可以用i+1和i+2來表示;若i=1,則另外兩個(gè)可以用i-1和i+1來表示;若i=2,則另外兩個(gè)可以用i-2和i-1來表示。也就是沒有統(tǒng)一的表達(dá)式,不方便用變量i來表示另外兩個(gè)數(shù)字的下標(biāo)。

        真的沒有辦法嗎?

        辦法肯定是有的!只是還沒有找到。

        必須要有堅(jiān)定的信念,才能找到堅(jiān)持的動(dòng)力!

        a,b,c變量的地位是相等的,不要把它們看作站成一排,而是把它們看作圍成一圈——這不就是約瑟夫環(huán)嗎!循環(huán)報(bào)數(shù)?用模運(yùn)算啊!

        思路出來了!

        我們可以用(i+1)%3和(i+2)%3分別表示另外兩個(gè)元素的下標(biāo),則當(dāng)i=0時(shí),另外兩個(gè)下標(biāo)分別為1和2;當(dāng)i=1時(shí),另外兩個(gè)下標(biāo)分別為2和0;當(dāng)i=2時(shí),另外兩個(gè)下標(biāo)分別為0和1。

        簡直是完美!

        這樣我們就可以構(gòu)造一個(gè)for循環(huán),用變量j來遍歷這兩個(gè)下標(biāo),實(shí)現(xiàn)枚舉從數(shù)字role[i]中拿走一根火柴棒,增加到數(shù)字role[j]中的情形。加上遍歷列表role的最外層循環(huán),總共4重循環(huán)。參考代碼如下:

        好了,解說就進(jìn)行到這里,剩下fun_3()函數(shù)的精簡任務(wù)就作為課后作業(yè)交給大家完成吧。

        課后練習(xí)

        1. ?參考fun_1()函數(shù)的改進(jìn)方法,將fun_2()函數(shù)精簡為二重循環(huán)結(jié)構(gòu)。

        2. ?有一種特殊的等式,可以移動(dòng)其中一根火柴棒,使其變成另一個(gè)正確的等式。如下圖所示:

        等式3+6=9,通過從數(shù)字6移動(dòng)一根火柴棒到數(shù)字9中,形成新的等式3+5=8;

        等式0+4=4,通過從加號(hào)中移動(dòng)一根火柴棒到數(shù)字0中,形成新的等式8-4=4。

        你還可以找出類似的等式嗎?如何用程序?qū)崿F(xiàn)呢?


        需要本文PPT、源代碼和課后練習(xí)答案的,可以加入“Python算法之旅”知識(shí)星球參與討論和下載文件,Python算法之旅”知識(shí)星球匯集了數(shù)量眾多的同好,更多有趣的話題在這里討論,更多有用的資料在這里分享。

        我們專注Python算法,感興趣就一起來!

        相關(guān)優(yōu)秀文章:

        閱讀代碼和寫更好的代碼

        最有效的學(xué)習(xí)方式

        Python算法之旅文章分類

        瀏覽 87
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            草逼网123 | 十八禁污污 | 午夜激情在线观看 | 做a视频在线 | 国产成人精品久 | 强行扒开紫嫣双腿猛烈进入 | 2019天天操夜夜操 | 欧美三级观看片 | 美女大逼逼 | 97久久爽无码人妻AⅤ精品牛牛 |