透過編寫“猜數字”遊戲來學習 Awk

roc_guo發表於2022-06-19

當你學習一門新的程式語言時,最好把重點放在大多數程式語言都有的共同點上:

  • 變數 —— 儲存資訊的地方
  • 表示式 —— 計算的方法
  • 語句 —— 在程式中表示狀態變化的方法

這些概念是大多是程式語言的基礎。

一旦你理解了這些概念,你就可以開始把其他的弄清楚。例如,大多數語言都有由其設計所支援的“處理方式”,這些方式在不同語言之間可能有很大的不同。這些方法包括模組化(將相關功能分組在一起)、宣告式與 式、物件導向、低階與高階語法特性等等。許多程式設計師比較熟悉的是程式設計“儀式”,即,在處理問題之前設定場景所需花費的工作。據說 Java 程式語言有一個源於其設計的重要儀式要求,就是所有程式碼都在一個類中定義。

但從根本上講,程式語言通常有相似之處。一旦你掌握了一種程式語言,就可以從學習另一種語言的基本知識開始,品味這種新語言的不同之處。

一個好方法是建立一組基本的測試程式。有了這些,就可以從這些相似之處開始學習。

你可以選擇建立的一個測試程式是“猜數字”程式。電腦從 1 到 100 之間選擇一個數字,讓你猜這個數字。程式一直迴圈,直到你猜對為止。

“猜數字”程式練習了程式語言中的幾個概念:

  • 變數
  • 輸入
  • 輸出
  • 條件判斷
  • 迴圈

這是學習一門新的程式語言的一個很好的實踐實驗。

:本文改編自 Moshe Zadka 在   中使用這種方法和 Jim Hall在   中使用這種方法的文章。

在 awk 程式中猜數

讓我們編寫一個實現“猜數字”遊戲的 Awk 程式。

Awk 是動態型別的,這是一種面向資料轉換的 語言,並且對互動使用有著令人驚訝的良好支援。Awk 出現於 20 世紀 70 年代,最初是 Unix 作業系統的一部分。如果你不瞭解 Awk,但是喜歡電子表格,這就是一個你可以   的訊號!

您可以透過編寫一個“猜數字”遊戲版本來開始對 Awk 的探索。

以下是我的實現(帶有行號,以便我們可以檢視一些特定功能):

BEGIN {
    srand(42)
    randomNumber = int(rand() * 100) + 1
    print "random number is",randomNumber
    printf "guess a number between 1 and 100\n"
}
{
    guess = int($0)
    if (guess < randomNumber) {
        printf "too low, try again:"
    } else if (guess > randomNumber) {
        printf "too high, try again:"
    } else {
        printf "that's right\n"
        exit
    }
}

我們可以立即看到 Awk 控制結構與 C 或 Java 的相似之處,但與 Python 不同。 在像  if-then-elsewhile 這樣的語句中, thenelse 和  while 部分接受一個語句或一組被  { 和  } 包圍的語句。然而,Awk 有一個很大的區別需要從一開始就瞭解:

根據設計,Awk 是圍繞資料管道構建的。

這是什麼意思呢?大多數 Awk 程式都是一些程式碼片段,它們接收一行輸入,對資料做一些處理,然後將其寫入輸出。認識到這種轉換管道的需要,Awk 預設情況下提供了所有的轉換管道。讓我們透過關於上面程式的一個基本問題來探索:“從控制檯讀取資料”的結構在哪裡?

答案是——“內建的”。特別的,第 7-17 行告訴 Awk 如何處理被讀取的每一行。在這種情況下,很容易看到第 1-6 行是在讀取任何內容之前被執行的。

更具體地說,第 1 行上的  BEGIN 關鍵字是一種“模式”,在本例中,它指示 Awk 在讀取任何資料之前,應該先執行  { ... } 中  BEGIN 後面的內容。另一個類似的關鍵字  END,在這個程式中沒有被使用,它指示 Awk 在讀取完所有內容後要做什麼。

回到第 7-17 行,我們看到它們建立了一個類似程式碼塊  { ... } 的片段,但前面沒有關鍵字。因為在  { 之前沒有任何東西可以讓 Awk 匹配,所以它將把這一行用於接收每一行輸入。每一行的輸入都將由使用者輸入作為猜測。

讓我們看看正在執行的程式碼。首先,是在讀取任何輸入之前發生的序言部分。

在第 2 行,我們用數字 42 初始化隨機數生成器(如果不提供引數,則使用系統時鐘)。為什麼要用 42?  第 3 行計算 1 到 100 之間的隨機數,第 4 行輸出該隨機數以供除錯使用。第 5 行邀請使用者猜一個數字。注意這一行使用的是  printf,而不是  print。和 C 語言一樣, printf 的第一個引數是一個用於格式化輸出的模板。

既然使用者知道程式需要輸入,她就可以在控制檯上鍵入猜測。如前所述,Awk 將這種猜測提供給第 7-17 行的程式碼。第 18 行將輸入記錄轉換為整數; $0 表示整個輸入記錄,而  $1 表示輸入記錄的第一個欄位, $2 表示第二個欄位,以此類推。是的,Awk 使用預定義的分隔符(預設為空格)將輸入行分割為組成欄位。第 9-15 行將猜測結果與隨機數進行比較,列印適當的響應。如果猜對了,第 15 行就會從輸入行處理管道中提前退出。

就這麼簡單!

考慮到 Awk 程式不同尋常的結構,程式碼片段會對特定的輸入行配置做出反應,並處理資料,讓我們看看另一種結構,看看過濾部分是如何工作的:

BEGIN {
    srand(42)
    randomNumber = int(rand() * 100) + 1
    print "random number is",randomNumber
    printf "guess a number between 1 and 100\n"
}
int($0) < randomNumber {
    printf "too low, try again: "
}
int($0) > randomNumber {
    printf "too high, try again: "
}
int($0) == randomNumber {
    printf "that's right\n"
    exit
}

第 1–6 行程式碼沒有改變。但是現在我們看到第 7-9 行是當輸入整數值小於隨機數時執行的程式碼,第 10-12 行是當輸入整數值大於隨機數時執行的程式碼,第 13-16 行是兩者相等時執行的程式碼。

這看起來“很酷但很奇怪” —— 例如,為什麼我們會重複計算  int($0)?可以肯定的是,用這種方法來解決問題會很奇怪。但這些模式確實是分離條件處理的非常好的方式,因為它們可以使用正規表示式或 Awk 支援的任何其他結構。

為了完整起見,我們可以使用這些模式將普通的計算與只適用於特定環境的計算分離開來。下面是第三個版本:

BEGIN {
    srand(42)
    randomNumber = int(rand() * 100) + 1
    print "random number is",randomNumber
    printf "guess a number between 1 and 100\n"
}
{
    guess = int($0)
}
guess < randomNumber {
    printf "too low, try again: "
}
guess > randomNumber {
    printf "too high, try again: "
}
guess == randomNumber {
    printf "that's right\n"
    exit
}

認識到這一點,無論輸入的是什麼值,都需要將其轉換為整數,因此我們建立了第 7-9 行來完成這一任務。現在第 10-12、13-15 和 16-19 行這三組程式碼,都是指已經定義好的變數 guess,而不是每次都對輸入行進行轉換。

讓我們回到我們想要學習的東西列表:

  • 變數 —— 是的,Awk 有這些;我們可以推斷出,輸入資料以字串形式輸入,但在需要時可以轉換為數值
  • 輸入 —— Awk 只是透過它的“資料轉換管道”的方式傳送輸入來讀取資料
  • 輸出 —— 我們已經使用了 Awk 的  print 和  printf 函式來將內容寫入輸出
  • 條件判斷 —— 我們已經學習了 Awk 的  if-then-else 和對應特定輸入行配置的輸入過濾器
  • 迴圈 —— 嗯,想象一下!我們在這裡不需要迴圈,這還是多虧了 Awk 採用的“資料轉換管道”方法;迴圈“就這麼發生了”。注意,使用者可以透過向 Awk 傳送一個檔案結束訊號(當使用   終端視窗時可透過快捷鍵  CTRL-D)來提前退出管道。

不需要迴圈來處理輸入的重要性是非常值得的。Awk 能夠長期保持存在的一個原因是 Awk 程式是緊湊的,而它們緊湊的一個原因是不需要從控制檯或檔案中讀取的那些格式程式碼。

讓我們執行下面這個程式:

$ awk -f guess.awk
random number is 25
guess a number between 1 and 100: 50
too high, try again: 30
too high, try again: 10
too low, try again: 25
that's right
$

我們沒有涉及的一件事是註釋。Awk 註釋以  # 開頭,以行尾結束。

總結

Awk 非常強大,這種“猜數字”遊戲是入門的好方法。但這不應該是你探索 Awk 的終點。你可以看看  ,Gawk 是 Awk 的擴充套件版本,如果你在電腦上執行 Linux,可能會有這個。或者,從它的原始開發者那裡閱讀關於   的各種資訊。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901823/viewspace-2901422/,如需轉載,請註明出處,否則將追究法律責任。

相關文章