演習a2

関数型,関数リテラルについて,「離散曲線の描画」を例に演習します.

演習(補足:関数型,関数リテラル)

(1)

  • TextBoxを利用して,矩形領域の指定した位置に'*'を表示する抽象データ型 Canvasを定義せよ.
  • Canvasは,下図のように,ラベル,幅40,高さ20の表示領域と,その表示領域を囲む線,および,x座標,y座標(の下一桁)を表す数字からなるものとする.
  • Canvasは,新たなCanvasを生成するNewという関数と,CanvasをクリアするClear,指定した位置に'*'を表示するMark,ラベルを表示するLabelというメソッドを持つものとする

(2)

  • 関数型を引数にとり,その引数で指定された関数をCanvasに描画するメソッドDrawを作成せよ.

(3)

  • (1)(2)で作成した型Canvasを使って,次の整数値関数によって与えられた離散曲線を,キーが押されるごとに順に描画するプログラムを作成せよ.ただし,表示描画座標を 0 <= x < 40, 0 <= y < 20 とする.
  • y = x+3
  • y = x/3
  • y = x^2/20-x/2+4
  • y = (x^3-60x^2+900x)/200
  • y = x
  • y = x/2
  • y = x/3
  • y = x/4

下図はy=x/3を描画した例である.

y = x/3

----------------------------------------

9| |

8| |

7| |

6| |

5| |

4| |

3| *|

2| *** |

1| *** |

0| *** |

9| *** |

8| *** |

7| *** |

6| *** |

5| *** |

4| *** |

3| *** |

2| *** |

1| *** |

0|*** |

----------------------------------------

0123456789012345678901234567890123456789

解答例

  1. package main
  2. import (
  3. "os"
  4. "bufio"
  5. "./textbox"
  6. )
  7. const (
  8. W = 40 // カンバスの幅
  9. H = 20 // カンバスの高さ
  10. )
  11. type Canvas struct {
  12. *textbox.TextBox
  13. }
  14. // カンバスを生成
  15. func New() (c *Canvas) {
  16. return &Canvas{textbox.New(0,0,W+3,H+4)}
  17. }
  18. // カンバスをクリア
  19. func (c *Canvas) Clear() {
  20. // テキストボックスをクリア
  21. c.TextBox.Clear()
  22. for i:=0; i < H; i++ {
  23. c.Move(0,H+1-i)
  24. c.Putc(uint8(i%10)+48) // y軸の数を描画
  25. c.Putc('|') // 縦の境界線(左)を描画
  26. c.Move(W+2,H+1-i)
  27. c.Putc('|') // 縦の境界線(右)を描画
  28. }
  29. c.Move(2,1)
  30. for i:=0; i < W; i++ { c.Putc('-') } // 横の境界線(上)を描画
  31. c.Move(2,H+2)
  32. for i:=0; i < W; i++ { c.Putc('-') } // 横の境界線(下)を描画
  33. c.Move(2,H+3)
  34. for i:=0; i < W; i++ { c.Putc(uint8(i%10+48)) } // x軸の数を描画
  35. }
  36. // カンバスの1行目にラベルを表示
  37. func (c *Canvas) Label(s string) {
  38. c.Move(0,0)
  39. c.Puts(s)
  40. }
  41. // カンバスの座標(x,y)に'*'を表示
  42. func (c *Canvas) Mark(x,y int) {
  43. if x < 0 || x >= W || y < 0 || y >= H { return }
  44. c.Move(x+2, H+1-y)
  45. c.Putc('*')
  46. }
  47. // カンバスに離散曲線 y=f(x) を描画
  48. func (c *Canvas) Draw(f func(int) int) {// 関数型を引数とする関数
  49. for x := 0; x < W; x++ {
  50. y := f(x)
  51. c.Mark(x,y)
  52. }
  53. }
  54. // f(x) = x
  55. func f0(x int) (int) {return x}
  56. func main() {
  57. textbox.Clear() // 画面全体をクリア
  58. c := New() // カンバスを生成
  59. in := bufio.NewReader(os.Stdin) // 標準入力
  60. c.Clear() // カンバスをクリア
  61. c.Label("y = x+3") // ラベルを表示
  62. var f1 func (x int) (int) // 関数型の変数の宣言
  63. f1 = func (x int) (int) {return x+3} // 関数リテラルを代入
  64. c.Draw(f1) // y=f1(x)を描画
  65. in.ReadByte() // キーボードの押下まで待つ
  66. c.Clear()
  67. c.Label("y = x/3")
  68. f2 := func (x int) (int) {return x/3} // 関数リテラルを代入
  69. for x:=0; x < W; x++ {
  70. y := f2(x) // 代入した変数で関数呼び出し
  71. c.Mark(x,y)
  72. }
  73. in.ReadByte()
  74. c.Clear()
  75. c.Label("y = x^2/20-x/2+4")
  76. c.Draw(func (x int) (int) { // 関数リテラルを引数に
  77. z:=float32(x)
  78. return int(z*z/20-z/2+4)
  79. })
  80. in.ReadByte()
  81. c.Clear()
  82. c.Label("y = (x^3-60x^2+900x)/200")
  83. for x1:=0; x1 < W; x1++ { // 関数リテラルを直接呼び出す
  84. y := func (x int) (int) {
  85. z:=float32(x)
  86. return int((z*z*z-60*z*z+900*z)/200)
  87. }(x1) // 引数の指定
  88. c.Mark(x1,y)
  89. }
  90. in.ReadByte()
  91. // 関数を要素とするスライス
  92. operand := 2
  93. funcs := [](func(int) int){
  94. f0, // 554行目で定義
  95. func (x int) (int) {return x/operand},
  96. f2, // 68行目で代入
  97. func (x int) (int) {return x/operand},
  98. }
  99. labels := []string{"y = x","y = x/2","y = x/3","y = x/4"}
  100. for i:=0; i < len(funcs); i++ {
  101. c.Clear()
  102. c.Label(labels[i])
  103. if i==3 {operand = 4}
  104. c.Draw(funcs[i]) // 配列の要素(関数型)を引数に
  105. if i == len(funcs)-1 {break}
  106. in.ReadByte()
  107. }
  108. }

(1)

11行目~53行目でCanvasを定義.

詳細は,プログラム中のコメントを参照してください.

(2)

55行目~61行目でDrawを定義.

(3)

66行目~120行目で離散関数を順に描画

詳細は,プログラム中のコメントを参照してください.

解説

関数型

  • 関数型は,同一のパラメータと同一の戻り値を持つすべての関数の集合を表します.
  • 関数型の変数を宣言したり,関数型の引数や戻り値をもつ関数を宣言したりすることができます.
  • 62行目では,func (x int) (int)という関数型の変数f1を宣言して,63行目でその変数に関数リテラルを代入しています.62行目と63行目は省略形で f1:= func (x int) (int) {return x+3} と書くこともできます.
  • 47行目から52行目では,func(int) int型の引数をもつメソッドDrawを定義しています.

関数リテラル

  • 関数リテラルは匿名関数を表し,関数の型と本体からなります.解答例中の関数リテラルは,以下のものです.
    • 63行目の func (x int) (int) {return x+3}
    • 68行目の func (x int) (int) {return x/3}
    • 76行目から79行目の func (x int) (int) { z:=float32(x); return int(z*z/20-z/2+4) }
    • 85行目から87行目の func (x int) (int) { z:=float32(x); return int((z*z*z-60*z*z+900*z)/200) }
    • 95行目の func (x int) (int) {return x/two}
    • 97行目の func (x int) (int) {return x/four}
  • 関数リテラルは変数に代入することができます.また,その変数を関数の実引数として渡すことができます.
    • 64行目で関数リテラルが代入された変数f1を引数としてメソッドDrawを呼び出しています.
  • 関数リテラルが代入された変数は関数の識別子となります.
    • 70行目で関数リテラルが代入された変数f2を使って,71行目で関数呼び出ししています.
  • 関数リテラルを,直接,関数の引数として記述することも可能です.
    • 76行目から79行目では,関数リテラルを直接メソッドDrawの引数としています.
  • 関数リテラルは,その後に引数のリストを指定することで,直接実行することができます.
    • 84行目から87行目で関数リテラルを定義し,その後に"(x1)"を記述することで,x1を引数としてその関数リテラルを呼び出しています.
  • 関数リテラルの配列やスライスを作ることもできます.
    • 108行目から113行目は,func(int) int 型のスライスを4つの要素で初期化しています.
  • 関数リテラルはクロージャーです.関数リテラル内から外側の関数内で定義した変数を参照できます.
    • 95行目,97行目のoperandは,関数リテラルの外側(92行目)で宣言・初期化されていますが,関数リテラル内から参照できます.
    • 92行目でoperandの値は2に初期化されるので,93行目の関数リテラル(funcs[1])の実行時のoperandの値は2です.
    • 97行目の関数リテラル(funcs[3])が呼ばれる前に,103行目でoperandの値に4を代入しているので,funcs[3]の実行時のoperandの値は4です.

プログラムの実行結果

y = x+3

----------------------------------------

9| * |

8| * |

7| * |

6| * |

5| * |

4| * |

3| * |

2| * |

1| * |

0| * |

9| * |

8| * |

7| * |

6| * |

5| * |

4| * |

3|* |

2| |

1| |

0| |

----------------------------------------

0123456789012345678901234567890123456789

y = x/3

----------------------------------------

9| |

8| |

7| |

6| |

5| |

4| |

3| *|

2| *** |

1| *** |

0| *** |

9| *** |

8| *** |

7| *** |

6| *** |

5| *** |

4| *** |

3| *** |

2| *** |

1| *** |

0|*** |

----------------------------------------

0123456789012345678901234567890123456789

y = x^2/20-x/2+4

----------------------------------------

9| |

8| * |

7| * |

6| |

5| * |

4| * |

3| |

2| * |

1| * |

0| |

9| * |

8| * |

7| * |

6| * |

5| ** |

4|* ** |

3| ** ** |

2| ***** |

1| |

0| |

----------------------------------------

0123456789012345678901234567890123456789

y = (x^3-60x^2+900x)/200

----------------------------------------

9| ** ** |

8| * * |

7| * * |

6| * |

5| * * *|

4| * |

3| * |

2| * * |

1| * |

0| * * |

9| * |

8| * |

7| * * |

6| * |

5| * |

4| * * * |

3| * |

2| * * |

1| * * |

0|* ***** |

----------------------------------------

0123456789012345678901234567890123456789

y = x

----------------------------------------

9| * |

8| * |

7| * |

6| * |

5| * |

4| * |

3| * |

2| * |

1| * |

0| * |

9| * |

8| * |

7| * |

6| * |

5| * |

4| * |

3| * |

2| * |

1| * |

0|* |

----------------------------------------

0123456789012345678901234567890123456789

y = x/2

----------------------------------------

9| **|

8| ** |

7| ** |

6| ** |

5| ** |

4| ** |

3| ** |

2| ** |

1| ** |

0| ** |

9| ** |

8| ** |

7| ** |

6| ** |

5| ** |

4| ** |

3| ** |

2| ** |

1| ** |

0|** |

----------------------------------------

0123456789012345678901234567890123456789

y = x/3

----------------------------------------

9| |

8| |

7| |

6| |

5| |

4| |

3| *|

2| *** |

1| *** |

0| *** |

9| *** |

8| *** |

7| *** |

6| *** |

5| *** |

4| *** |

3| *** |

2| *** |

1| *** |

0|*** |

----------------------------------------

0123456789012345678901234567890123456789

y = x/4

----------------------------------------

9| |

8| |

7| |

6| |

5| |

4| |

3| |

2| |

1| |

0| |

9| ****|

8| **** |

7| **** |

6| **** |

5| **** |

4| **** |

3| **** |

2| **** |

1| **** |

0|**** |

----------------------------------------

0123456789012345678901234567890123456789