Erlang 練習題:尋寶遊戲

http://using-c.blogspot.com/2007/09/c.html 看到很精彩的 C 語言程式設計練習題。如果大學新生剛開始上課,第二個作業就遇到這種題目,一定做得累得半死。蠻有趣的。

拉回正題,在這裏用 Erlang 解決問題,怎麼寫呢?

仿照大學一年級學生初學 C/C++ 的處理方式,一切的選路結構全都寫在程式中,若不是使用 if-else ,就使用 switch 。至於,使用 Erlang 語言,要掌握的就是函數的樣式匹配能力 (Pattern-Matching) 。

對那一張分支圖,分岔口由左至右、由上向下依序編號為 1 2 3 4 5 6 ,最後的三岔編二個號碼。七個終點也按照由左至右、由上向下順序編號。然後我們要寫一些程式,像:

        fork(1) ->
                ...... ,
                case Input of
                        l ->
                                goal(1);
                        r ->
                                fork(2)
                end.

Erlang 的文字輸入是使用像

{ok, Term} = io:read("Choose (l)eft or (r)ight? ")

這樣的句子,呼叫 io 模組 read/1 函數,參數為提示輸入文字。這個呼叫會做一個接受輸入的動作,輸入動作是可以打任何文字,最後加一個句點結束。如果輸入讀取成功,則會傳回能與 {ok, Term } 樣式匹配的值。那麼,先寫一小段來測試一下:

        -module(test).
        -compile(export_all).
        fork(1) ->
                {ok, Input} = io:read("Choose (l)eft or (r)ight? "),
                case Input of
                        l ->
                                io:format("Left.~n");
                        r ->
                                io:format("Right.~n")
                end.

然後執行一下,結果是:

        3> test:fork(1).
        Choose (l)eft or (r)ight? r.
        Right.
        ok

程式的確執行到 Input = r 的段落。所以,確定可以繼續寫下去。

以下定義 6 個分岔:

fork(1) ->
    {ok, Term} = io:read("Choose (l)eft or (r)ight? "),
    case Term of
	l ->
	    goal(1);
	r ->
	    fork(2)
    end;
fork(2) ->
    {ok, Term} = io:read("Choose (l)eft or (r)ight? "),
    case Term of
	l ->
	    fork(3);
	r ->
	    fork(4)
    end;
fork(3) ->
    {ok, Term} = io:read("Choose (l)eft or (r)ight? "),
    case Term of
	l ->
	    goal(3);
	r ->
	    goal(2)
    end;
fork(4) ->
    {ok, Term} = io:read("Choose (l)eft or (r)ight? "),
    case Term of
	l ->
	    fork(5);
	r ->
	    goal(4)
    end;
fork(5) ->
    {ok, Term} = io:read("Choose (l)eft or (r)ight? "),
    case Term of
	l ->
	    fork(6);
	r ->
	    goal(5)
    end;
fork(6) ->
    {ok, Term} = io:read("Choose (l)eft or (r)ight? "),
    case Term of
	l ->
	    goal(7);
	r ->
	    goal(6)
    end.

接著要定義七個結果:

goal(1) -> io:format("You're caught in a trap. Game over.~n");
goal(2) -> io:format("You met a killing bear. Game over.~n");
goal(3) -> io:format("You became a long-life being and lived lonely. Game over.~n");
goal(4) -> io:format("You met your teacher. Game over.~n");
goal(5) -> io:format("You got a good-guy card. Game over.~n");
goal(6) -> io:format("You got F point. Game over.~n");
goal(7) -> io:format("You didn't find any treasure, but you got a good vacation.~n").

把程式整理好,送出去編譯執行,情況如下:

5> test:fork(1).
Choose (l)eft or (r)ight? l.
You're caught in a trap. Game over.
ok
6> test:fork(1).
Choose (l)eft or (r)ight? r.
Choose (l)eft or (r)ight? l.
Choose (l)eft or (r)ight? l.
You became a long-life being and lived lonely. Game over.
ok

不過,題目中的路線是一個很好的樹狀結構。在 Erlang 可以多使用值組 (tuple) 來建立樹,或者用列表 (list) 做出多元樹也可以。假設將路線用值組整理成二元樹,如下:

        {fork, {goal, 1},
               {fork, {fork, {goal, 3},
                             {goal, 2}
                      },
                      {fork, {fork, {fork, {goal, 7},
                                           {goal, 6}
                                    },
                                    {goal, 5}
                             },
                             {goal, 4}
                      }
                }
         }

我們可以用電腦數學語言 BNF 描述二元樹的結構為:

Tree ::= {fork, Tree, Tree} | {goal, Num}
Num ::= 1 | 2 | 3 | 4 | 5 | 6 | 7

接著可以按照以上綱要,寫出新的 fork/1 。接受一個二元樹,遇到 fork 原子就處理二個子樹,或是遇到 goal 原子就呼叫 goal/1 。程式如下:

fork({fork, LT, RT}) ->
    {ok, Term} = io:read("Choose (l)elft or (r)ight? "),
    case Term of
	l ->
	    fork(LT);
	r ->
	    fork(RT)
    end;
fork({goal, Num}) ->
    goal(Num).

執行情況如下:

7> test:fork(
        {fork, {goal, 1},
               {fork, {fork, {goal, 3},
                             {goal, 2}
                      },
                      {fork, {fork, {fork, {goal, 7},
                                           {goal, 6}
                                    },
                                    {goal, 5}
                             },
                             {goal, 4}
                     }
                }
         }).
Choose (l)elft or (r)ight? l.
You're caught in a trap. Game over.
ok
8> test:fork(
        {fork, {goal, 1},
               {fork, {fork, {goal, 3},
                             {goal, 2}
                      },
                      {fork, {fork, {fork, {goal, 7},
                                           {goal, 6}
                                    },
                                    {goal, 5}
                             },
                             {goal, 4}
                     }
                }
         }).
Choose (l)elft or (r)ight? r.
Choose (l)elft or (r)ight? l.
Choose (l)elft or (r)ight? r.
You met a killing bear. Game over.
ok

以上,您可以用程式、也可以用資料,表達分歧選擇結構。超讚的,對不對?

廣告

About 黃耀賢 (Yau-Hsien Huang)

熱愛 Erlang ,並且有相關工作經驗。喜歡程式語言。喜歡邏輯。目前用 Python 工作。
本篇發表於 Erlang, Introduction。將永久鏈結加入書籤。

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s