Skip to content

Instantly share code, notes, and snippets.

@akanehara
Last active August 29, 2015 14:07
Show Gist options
  • Save akanehara/801a7a43b63ac71d417e to your computer and use it in GitHub Desktop.
Save akanehara/801a7a43b63ac71d417e to your computer and use it in GitHub Desktop.
『プログラミングの基礎』読書会 10. 再帰関数を使ったプログラミング

『プログラミングの基礎』読書会

10. 再帰関数を使ったプログラミング

10.1 関数のネスト

接頭語を返す関数 prefix を補助関数 add_to_each を使って作る

接頭語

  • [1;2;3] というリストの接頭語は [[1]; [1;2]; [1;2;3]]

  • 空リストは接頭語に含めない

補助関数 add_to_each

各接頭語の先頭にもうひとつ要素を付け加える

テスト
(* テスト *)

let test1 = add_to_each 1 [] = []

let test2 = add_to_each 1 [[2]]
                          = [[1; 2]]

let test3 = add_to_each 1 [[2]; [2; 3]]
                          = [[1;2]; [1;2;3]]

let test4 = add_to_each 1 [[2]; [2;3]; [2;3;4]]
                          = [[1;2]; [1;2;3]; [1;2;3;4]]
テンプレート
(* 目的: 受け取った lst の要素それぞれの先頭に n をくっつける *)
(* add_to_each : int -> int list list -> int list list *)
let rec add_to_each n lst = match lst with
    [] -> []
  | first :: rest -> [] (* add_to_each n rest *)
実演

関数 prefix

リストの接頭語を求める

テスト
(* テスト *)

let test5 = prefix [] = []

let test6 = prefix [1]
                   = [[1]]

let test7 = prefix [1; 2]
                   = [[1]; [1;2]]

let test8 = prefix [1; 2; 3; 4]
                   = [[1];[1; 2];[1; 2; 3];[1; 2; 3; 4]]
テンプレート
(* 目的:受け取った lst の接頭語のリストを求める *)
(* prefix : int list -> int list list *)
let rec prefix lst = match lst with
    [] -> []
  | first :: rest -> [] (* prefix rest *)
実演

まとめ

  • 関数から別の関数を呼び出すことで、より複雑な関数を作ることができる

  • 関数呼び出しの結果をほかの関数に渡すことができる(関数呼び出しをネストする)

問題 10.1

問題 10.2

問題 10.3

問題 10.4

10.2 リストの中の最小値を求める関数

テスト
(* テスト *)
let test1 = minimum [3] = 3
let test2 = minimum [1; 2] = 1
let test3 = minimum [3; 2] = 2
let test4 = minimum [3; 2; 6; 4; 1; 8] = 1
テンプレート
(* 目的: 受け取った lst の中の最小値を返す関数 *)
(* minimum : int list -> int *)
let rec minimum lst = match lst with
    [] -> ()
  | first :: rest -> 0 (* minimum rest *)
実装
  • 空リストのときは max_integer(どの要素よりも大きいint)

  • first が rest の最小と同じかまたは小さいなら first を返す

(* minimum : int list -> int *)
let rec minimum lst = match lst with
    [] -> max_int
  | first :: rest ->
      if first <= minimum rest
        then first
        else minimum rest

ただし、この実装では次のような特殊な場合を区別できない

  • 入力されたリストが空

  • 要素が全部 max_int

テスト書いてみよう

これらを特殊な場合として扱うには 例外処理 (18章)を使います

問題 10.5

10.3 局所変数定義

10.2のminimum関数
(* minimum : int list -> int *)
let rec minimum lst = match lst with
    [] -> max_int
  | first :: rest ->
      if first <= minimum rest
      then first
      else minimum rest

minimum rest かぶっとるやん (´・ω・`)

そこで 局所変数

let 変数名 = 式1 in 式2

式1を値としてもつ、式2の中でのみ有効な 局所的な変数

変数の有効範囲のことを スコープ という

実演

  • トップレベルの変数と同名の局所変数

  • 入れ子の局所変数

局所変数を使って minimum を書き換え

(* minimum : int list -> int *)
let rec minimum lst = match lst with
    [] -> max_int
  | first :: rest ->
      let min_rest = minimum rest in
      if first <= min_rest
      then first
      else min_rest

問題 10.6

10.4 パターンマッチつき局所変数定義

成績がAの人、Bの人、Cの人、Dの人がそれぞれ何人いるかを4つ組にして返す関数 shukei

学生を表すレコード
type gakusei_t = {
  namae   : string;
  tensuu  : int;
  seiseki : string;
}
syukei関数
(* 目的:学生リスト lst のうち各成績の人数を集計する *)
(* syukei : gakusei_t list -> int * int * int * int *)
let rec shukei lst = match lst with
    [] -> (0, 0, 0, 0)
  | {namae = n; tensuu = t; seiseki = s} :: rest ->
      let shukei_rest = shukei rest in
      match shukei_rest with
        (a, b, c, d) ->
          if      s = "A" then (a + 1,     b,     c,     d)
          else if s = "B" then (    a, b + 1,     c,     d)
          else if s = "C" then (    a,     b, c + 1,     d)
          else                 (    a,     b,     c, d + 1)

再帰呼び出し shukei rest の実行結果を shukei_rest にとっておいて、さらに match でパターンマッチ。いそがしい。

実は局所変数定義とパターンマッチを同時に行う方法がある。

let 変数名 = 式1 in 式2

は実は

let パターン = 式1 in 式2
パターンマッチつき局所変数定義を使ったsyukei関数
(* 目的:学生リスト lst のうち各成績の人数を集計する *)
(* syukei : gakusei_t list -> int * int * int * int *)
let rec shukei lst = match lst with
    [] -> (0, 0, 0, 0)
  | {namae = n; tensuu = t; seiseki = s} :: rest ->
      let (a, b, c, d) = shukei rest in
      if      s = "A" then (a + 1,     b,     c,     d)
      else if s = "B" then (    a, b + 1,     c,     d)
      else if s = "C" then (    a,     b, c + 1,     d)
      else                 (    a,     b,     c, d + 1)

問題10.7

問題10.8

10.5 ふたつのリストを結合する関数

(* 目的: lst1 と lst2 を受け取りそれらを結合したリストを返す *)
(* append : 'a list -> 'a list -> 'a list *)
let rec append lst1 lst2 = match lst1 with
    [] -> lst2
  | first :: rest -> first :: append rest lst2

(* テスト *)
let test1 = append [] []     = []
let test2 = append [] [1; 2] = [1; 2]
let test3 = append [1; 2] [] = [1; 2]
let test4 = append [1; 2] [3; 4;] = [1; 2; 3; 4]
let test5 = ["a"; "b"; "c"; "d"; "e"] ["f"; "g"]
          = ["a"; "b"; "c"; "d"; "e"; "f"; "g"]

同じものが List.append としてOCamlに定義されている

@ というショートカットが演算子として用意されている

いずれにしろリストの結合は先にくるリストの長さに比例した実行時間がかかるので注意!

10.6 ふたつの昇順に並んだリストをマージする関数

(* ふたつの昇順に並んだリストをマージする関数 *)
(* merge : int list -> int list -> int list *)
let rec merge lst1 lst2 = match (lst1, lst2) with
    ([], [])                           -> []
  | ([], first2 :: rest2)              -> lst2
  | (first1 :: rest1, [])              -> lst1
  | (first1 :: rest1, first2 :: rest2) ->
      if first1 < first2
      then first1 :: merge rest1 lst2
      else first2 :: merge lst1 rest2

(* テスト *)
let test1 = merge [] [] = []
let test2 = merge [] [1; 2]     = [1; 2]
let test3 = merge [1; 2] []     = [1; 2]
let test4 = merge [1; 3] [2; 4] = [1; 2; 3; 4]
let test5 = merge [2; 4] [1; 3] = [1; 2; 3; 4]
let test6 = merge [1; 4] [1; 3] = [1; 1; 3; 4]

問題10.9

ふたつのリストをうけとってきたら、それらの長さが同じかどうかを判定する関数 equals_length をデザインレシピにしたがって作れ。(length は使わずに作成せよ。)

10.7 駅名・駅間リストからの情報の取得

http://pllab.is.ocha.ac.jp/~asai/book-data/metro.ml

問題10.10

問題10.11

問題10.12

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment