跳轉到內容

程式設計的科學/你能降到多低?

來自 Wikibooks,開放世界中的開放書籍

回顧上一章,關於微積分的符號d表示“取微小部分”。讓我們練習取一個數字的微小部分,同時學習一些程式設計。並且,因為你必須作為未來的計算機科學家,你必須學會放手和變得狂野,我們將取一個微小部分的微小部分,以及那個非常微小部分的微小部分,以此類推,無限地


此時,您應該在您的系統上安裝 Sway


使用 Sway 直譯器

[編輯 | 編輯原始碼]

我們首先啟動 Sway 直譯器,Sway 是您將學習的程式語言。啟動後,直譯器會用一個提示符來獎勵你

   sway>

此提示符表示您需要輸入一些程式碼。當您這樣做時,直譯器將告訴您執行(或評估執行)該程式碼的結果。

   sway> 3;
   INTEGER: 3

要退出直譯器,請鍵入<Ctrl-d>或<Ctrl-c>。這些按鍵組合透過按住 Control 鍵並在同一時間輕敲 'd' 或 'c' 鍵生成。[1]在這裡,我們輸入了一個 3 後跟一個分號(分號告訴直譯器我們已經完成了程式碼輸入)。Sway(實際上是 Sway 直譯器)透過說 3 是一個值為 3 的整數來響應。當然,我們已經知道這一切,所以直譯器似乎並沒有那麼有用。但你知道 43 乘以 112 等於 4816 嗎?

   sway> 43 * 112;
   INTEGER: 4816

Sway 知道!讓我們利用直譯器擅長數學運算的事實來計算一個大數字的微小部分。假設一個微小部分是整體。[2]首先,讓我們計算一下作為十進位制數或實數,因為這樣更容易輸入

   sway> 1/16;
   SOURCE CODE ERROR
   file stdin,line 1
   an expression was expected, found token of type BAD_NUMBER
   error occurred prior to: 16;[END OF LINE]

糟糕。Sway 不喜歡緊跟著 '1' 的 '/'. 事實上,在大多數情況下,Sway 要求在除號等符號周圍留有空格。讓我們再試一次

   sway> 1 / 16;
   INTEGER: 0

好多了,至少我們得到了答案而不是錯誤。但是,直譯器似乎並不擅長數學。如果我們戴上夏洛克·福爾摩斯的帽子思考一下,我們會發現直譯器說 1 除以 16 的結果是整數,但我們知道它應該是實數。事實證明,Sway 語言,就像大多數程式語言一樣,使用一條規則,即組合兩種相同型別的事物會產生相同型別的結果。在這種情況下,零恰好是小於所需結果的最大整數。換句話說,直譯器截斷了實數的小數部分,並給了我們剩下的整數。讓我們試驗一下是否如此

   sway> 7 / 2;
   INTEGER: 3

似乎是。所以回到我們最初的問題,我們如何找到 1 除以 16 的實數結果?讓我們將數字作為實數而不是整數輸入

   sway> 1.0 / 16.0;
   REAL_NUMBER: 0.0625000000

好多了!

現在讓我們計算一下一百萬的微小部分是多少(使用我們對微小的假設)

   sway> 1000000 * 0.0625;
   REAL_NUMBER: 62500.0000000000

六萬二千五百。從絕對意義上講仍然很大,但比一百萬小得多。讓我們變得狂野和瘋狂,取一個微小部分的微小部分

   sway> 1000000 * .0625 * .0625;
   REAL_NUMBER: 3906.2500000000

大約 4000。小得多。


此時,您應該熟悉Sway 原語組合,包括 Sway 的優先順序和結合性規則。


使用變數

[編輯 | 編輯原始碼]

我們是否應該繼續取越來越小的部分?

在我們這樣做之前,我必須承認,我內心深處是一個懶惰的人,就像大多數計算機科學家一樣。[3]輸入所有這些數字實在是太麻煩了!我將使用兩個簡短的符號來分別表示一百萬和分數

   sway> var x = 1000000;
   INTEGER: 1000000
   sway> var f = .0625;
   REAL_NUMBER: 0.0625000000

我所做的是建立了一個變數來代替一百萬,以及一個變數來代替分數,分別為xf[4]現在我可以使用這些變數來代替數字。讓我們檢查一下我是否做對了

   sway> x * f * f;
   REAL_NUMBER: 3906.2500000000

看起來我做對了。讓我們更進一步

   sway> x * f * f * f;
   REAL_NUMBER: 244.1406250000

變數似乎是一種減少輸入量的好方法。唯一的缺點是記住變數代表什麼。這就是為什麼用一種便於您回憶其含義的方式命名變數非常重要的原因。通常,單個字母變數名'是一個好主意(儘管此規則存在例外)。


要了解更多資訊,請參閱Sway 變數

使用函式

[編輯 | 編輯原始碼]

我們可以繼續這樣做,但您還沒有理解我的懶惰的深度。即使反覆輸入* f對於我的敏感性來說也太多。我將定義(或編寫)一個函式來幫我完成工作(如果您像我一樣懶惰,不想自己輸入,可以將此函式複製貼上到直譯器中)

   function smaller(amount,fraction)
       {
       inspect(amount * fraction);
       }

無論您是貼上還是輸入,您都應該從直譯器中獲得以下響應

   sway>     function smaller(amount,fraction)
   more>         {
   more>         inspect(amount * fraction);
   more>         }
   FUNCTION: <function smaller(amount,fraction)>

themore>提示表示 Sway 直譯器正在等待更多程式碼。

在處理函式時,您必須做兩件事,(1)定義它們和(2)呼叫它們。在這裡,我們剛剛定義了一個函式;現在我們需要呼叫它。我們將透過鍵入函式的名稱,後跟一個包含引數的括號列表來呼叫它。有時我們說這是“將引數傳遞給函式”。

在呼叫函式smaller[5]時,引數的值將繫結到變數amountfraction,它們在函式定義中的函式名之後找到。這些變數被稱為函式的形式引數。這種傳遞和繫結本質上為我們定義了這些變數。請注意,引數和形式引數確實需要用空格分隔;逗號用作分隔符。

在這些隱式變數定義之後,花括號 {} 之間的程式碼將被評估,就像您直接將其鍵入到直譯器中一樣。這就是我在上一章中說函式可以為您完成工作的原因。如果我們檢視我們剛剛定義的函式的程式碼(或主體),我們會看到一個名為inspect的函式被呼叫。由於我們還沒有定義inspect,因此我們可以假設此函式已存在於直譯器中。從它的名稱可以推測,它將告訴我們當我們將amount乘以fraction時會發生什麼

   sway> smaller(x,f);
   amount * fraction is 62500.000000
   REAL_NUMBER: 62500.000000

這裡有很多內容需要解釋。首先是x(1000000)的值被繫結到函式smaller的形式引數amount。同樣,f(0.0625)的值被繫結到形式引數fraction。然後評估函式smaller的主體,觸發對inspect函式的呼叫。inspect所做的是打印出其文字引數,後跟字串“ is ”,後跟其引數的值。由於對 inspect 的呼叫是smaller的最後一件事情,因此inspect返回的任何內容都將由smaller返回。此返回值似乎是已評估的引數。

請注意,直譯器報告了兩件事。第一個是inspect生成的字串。第二個是我們之前看到過的返回值報告。我們將對呼叫函式外部產生影響的操作(在本例中,inspect的列印)稱為副作用


要了解更多資訊,請參閱Sway 函式

現在,到了這一步,你可能在想,我不僅懶惰,而且一定很笨,因為我在編寫smaller函式並呼叫它上面花費了比直接輸入更多的精力。

   sway> x * f;
   REAL_NUMBER: 62500.000000

如果這就是我要做的全部,那麼你的評估是完全正確的。但我還沒有完成。現在我將使smaller變得更加強大。

   function smaller(amount,fraction)
       {
       inspect(amount * fraction);
       smaller(amount * fraction,fraction);
       }

請注意,在呼叫inspect函式之後,我添加了對smaller函式的呼叫。當一個函式呼叫自身時,它被稱為遞迴函式,它表現出遞迴的特性,並且在遞迴呼叫的點上,函式遞迴[6] 進一步注意,在那個內部呼叫中,smaller的第一個引數將傳送一個(希望是)更小的數字到smaller函式。在那個呼叫中,smaller將再次被呼叫,並傳入一個更小的數字,依此類推。讓我們試一試。

   sway> smaller(1000000,.0625);
   amount * fraction is 62500.0000000000
   amount * fraction is 3906.2500000000
   amount * fraction is 244.1406250000
   amount * fraction is 15.2587890625
   amount * fraction is 0.9536743164
   amount * fraction is 0.0596046448
   amount * fraction is 0.0037252903
   amount * fraction is 0.0002328306
   amount * fraction is 1.4551915228e-05
   amount * fraction is 9.0949470177e-07
   ...
   amount * fraction is 0.0000000000e+00
   amount * fraction is 0.0000000000e+00
   amount * fraction is 0.0000000000e+00
   amount * fraction is 0.0000000000e+00
   amount * fraction is 0.0000000000e+00
   amount * fraction is 0.0000000000e+00
   amount * fraction is 0.0000000000e+00
   amount * fraction is 0.0000000000e+00
   encountered a fatal error...
   stack overflow.

哇!除非你的眼睛非常快,否則你看到的只是此輸出的底部部分。發生了什麼?我們所做的是定義一個函式,當我們呼叫它時,它陷入了無限迴圈。無限迴圈發生是因為我們從未告訴我們的函式何時停止呼叫自身。因此,它試圖無限地呼叫自身。當然,計算機的記憶體是有限的,因此在這種特定情況下,呼叫不可能永遠持續下去。[7] 讓我們重新定義我們的函式,以便它在每次檢查後暫停,以便我們能夠減慢輸出速度。

   function smaller(amount,fraction)
       {
       inspect(amount * fraction);
       pause();
       smaller(amount * fraction,fraction);
       }

您可以再次啟動 Sway 直譯器並將我們修改後的函式定義貼上進去。

現在,當我們呼叫我們的函式時,我們將看到此輸出(假設您反覆按下鍵盤上的 Enter 鍵)。

   sway> smaller(1000000,.0625);
   amount * fraction is 62500.0000000000
   
   amount * fraction is 3906.2500000000
   
   amount * fraction is 244.1406250000
   
   amount * fraction is 15.2587890625
   
   amount * fraction is 0.9536743164
   
   amount * fraction is 0.0596046448
   
   amount * fraction is 0.0037252903
   
   amount * fraction is 0.0002328306

您可以透過輸入 <Ctrl>-c 來停止直譯器,該命令透過在按下 Control 鍵的同時點選一次“c”鍵從鍵盤輸入。

我們可以看到amount * fraction很快就會變得非常小。如果您重新開始並繼續,您將看到amount * fraction最終達到零。理論上,它不會,但在某個時刻,數量變得小於 Sway 可以表示的數量,因此 Sway 報告為零。


您應該閱讀更多關於遞迴的內容,並在繼續之前學習關於if 表示式的內容。


讓我們嘗試一個新版本的函式,這個函式在停止之前會呼叫自身給定次數。

   function smaller(x,f,n)
       {
       if (n == 0)
           {
           :done;
           }
       else
           {
           inspect(x);
           smaller(x * f,f,n - 1);
           }
       }

這次,我們有一個新的形式引數n,它表示函式遞迴呼叫自身的次數。我們還更改了對inspect的呼叫以打印出x的值。請注意,遞迴呼叫不僅使x變小,還使n變小。當n變得足夠小時,函式將返回符號done

我們必須記住向呼叫新增一個額外的引數。讓我們傳入 8 作為遞迴呼叫的次數。

   sway> smaller(1000000,.0625,8);
   x is 1000000
   x is 62500.000000
   x is 3906.2500000
   x is 244.14062500
   x is 15.258789062
   x is 0.9536743164
   x is 0.0596046448
   x is 0.0037252903
   SYMBOL: :done

耶!我們的程式停止了無限遞迴。正式地說,函式內部的if有兩個情況:基本情況,它不包含遞迴呼叫;和遞迴情況,它包含遞迴呼叫。當基本情況從未達到時,就會發生無限遞迴迴圈,通常是由於基本情況條件的陳述錯誤或對基本情況條件中正在測試的形式引數所取值的範圍的誤解造成的。

回到微積分

[編輯 | 編輯原始碼]

這個找到越來越小的數字的練習與微積分有什麼關係?好吧, 符號表示“取一小部分”。多小?無限小,或者換句話說,由無限次遞迴呼叫smaller計算的值。當然,這比我們所能想象的要小,但這並不重要。我們無法理解我們的大腦是如何工作的,但我們相處得還不錯,或者至少我們大多數人是這樣。

所有公式均使用小學算術優先順序編寫。

1. 當你組合一個整數和一個實數時會發生什麼?

2. 此問題和後續問題指的是smaller的最後一個版本。為什麼對smaller的呼叫返回了一個實數?

3. 重新定義smaller,以便它打印出九次檢查而不是八次,對於 8 的相同初始值。

4. 當將零作為計數傳遞給smaller時會發生什麼?

5. 當將 -1 作為計數傳遞給smaller時會發生什麼?為什麼?

6. 如果smaller中的遞迴呼叫被替換為smaller(x *f,f,n)?為什麼?

7. 編寫一個 Sway 函式來表示,並針對進行評估。

8. 使用前面問題中的 Sway 函式,編寫一個新的 Sway 函式,並像以前一樣進行評估。

9. 級數是一系列相加的項。如果隨著越來越多的項相加,這些項的總和越來越接近一個數字k,那麼k被稱為該級數的極限。本書用芝諾悖論解釋了這一點。假設你距離一堵牆 1 個單位的距離,並且每一步你都走完剩餘距離的一半到達牆邊(為什麼這是一個悖論?)。使用遞迴和 Sway,定義一個函式zeno(n)來演示此過程。zeno的引數是採取的步數,返回值應該是總行程距離。zeno級數的極限是多少?

10. 令 。計算 。用 表示 。在 的範圍內繪製 h 的圖形。

11. 在魔獸世界(一款流行的大型多人線上角色扮演遊戲或簡稱MMORPG)中,非玩家角色(NPC)造成致命一擊的機率公式為 。對於 70 級 NPC,將其表示為 的函式,並給出使機率為 0 的防禦值。

12. 角色升級所需的經驗值為 ,其中x為角色等級,並且 是消滅與角色等級相同的怪物獲得的基本經驗值。計算 。用x表示 。繪製h 範圍內的影像。

  1. 如果您重新啟動 Sway 直譯器,可以使用鍵盤上的向上箭頭恢復之前的命令。如果向上翻得太遠,可以使用向下箭頭以相反的方向瀏覽之前的命令列表。您也可以編輯之前的命令。
  2. 計算機科學家喜歡 2 的冪,例如 1、2、4、8、16、32 等。這些數字的倒數也備受喜愛。
  3. 懶惰,就像我討厭做本可以(也應該)自動化的工作。
  4. 變數可以被認為是其他事物的名稱。但是,它不是那個事物本身,就像您的名字不是您本人,而是一種方便人們稱呼您的方式。
  5. 變數smaller實際上並不是一個函式,而只是我們定義的函式的方便名稱。但與其說“由變數smaller命名的函式”,我們通常說“函式smaller”。從技術上講,這是不正確的,但更簡單易懂。
  6. 一些可憐的人會使用動詞“recurse”而不是正確的“recur”。Recur表示呼叫自身,Recurse表示再次發誓。不要犯這個錯誤,否則人們會認為你無知。
  7. 有一些聰明的語言,其中一些無限遞迴迴圈不會佔用計算機記憶體。總有一天,Sway 會變得如此聰明,本節將需要重寫。


可怕的概念 · 一些這樣,一些那樣

華夏公益教科書