《プログラミングの基礎》をReScriptで読む 第10章

[《プログラミングの基礎》](https://www.amazon.co.jp/dp/4781911609/ "Amazonへのリンク")はとても良い本です。
サンプルプログラムをReScriptで書いて、プログラミングの基礎とReScriptを学びます。
# 環境
* macOS Sonoma(バージョン 14.4.1)
* ReScript 11.1.0-rc.8
* @rescript/core 1.2.0
# 第10章
OCamlの局所変数定義`let in`は、ReScriptでは`{}`でいけます。
```ReScript:Ex10_1.res
// 目的：昇順に並んでいる lst の正しい位置に n を挿入する
// insert : int list -> int -> int list
let rec insert = (lst, n) => switch lst {
| list{} => list{n}
| list{first, ...rest} =>
    if first < n {list{first, ...insert(rest ,n)}}
    else {list{n, ...lst}}
}

// テスト
Console.log(insert(list{}, 3) == list{3})
Console.log(insert(list{1}, 3) == list{1, 3})
Console.log(insert(list{3}, 1) == list{1, 3})
Console.log(insert(list{1, 3, 4, 7, 8}, 5) == list{1, 3, 4, 5, 7, 8})
```
```ReScript:Ex10_2.res
open Ex10_1 //insertの定義

// 目的：受け取った整数のリスト lst を昇順に並べる
// ins_sort : int list -> int list
let rec ins_sort = (lst) => switch lst { 
| list{} => list{} 
| list{first, ...rest} => insert(ins_sort(rest), first) 
}

// テスト
Console.log(ins_sort(list{}) == list{})
Console.log(ins_sort(list{1}) == list{1})
Console.log(ins_sort(list{3, 1}) == list{1, 3}) 
Console.log(ins_sort(list{1, 3}) == list{1, 3})
Console.log(ins_sort(list{5, 3, 8, 1, 7, 4}) == list{1, 3, 4, 5, 7, 8}) 
```
```ReScript:Ex10_3.res
type gakusei_t = {
  namae: string,
  tensuu: int,
  seiseki: string
}

let gakusei1 = {namae: "nakamura", tensuu: 90, seiseki: "A"}
let gakusei2 = {namae: "miyahara", tensuu:80, seiseki: "A"} 
let gakusei3 = {namae: "sato", tensuu:75, seiseki: "B"}
let gakusei4 = {namae: "idehara", tensuu: 70, seiseki: "B"}
let gakusei5 = {namae: "tsubata", tensuu: 65, seiseki: "C"}
let gakusei6 = {namae: "asai", tensuu:60, seiseki: "C"}
 
let lst1 = list{}
let lst2 = list{gakusei2}
let lst3 = list{gakusei3, gakusei4}
let lst4 = list{gakusei4, gakusei3}
let lst5 = list{gakusei4, gakusei1, gakusei6, gakusei5, gakusei2, gakusei3}
 
// 目的：昇順に並んでいる lst の正しい位置に gakusei を挿入する
// gakusei_insert : gakusei_t list -> gakusei_t -> gakusei_t list
let rec gakusei_insert = (lst, gakusei0) => switch lst {
| list{} => list{gakusei0}
| list{{namae: n, tensuu: t, seiseki: s}, ...rest} =>
  switch gakusei0 {
  | {namae: _, tensuu: t0, seiseki: _} =>
      if t < t0 {list{{namae: n, tensuu: t, seiseki: s}, ...gakusei_insert(rest, gakusei0)}}
      else {list{gakusei0, ...lst}}
  }
}

// テスト
Console.log(gakusei_insert(list{}, gakusei2) == list{gakusei2})
Console.log(gakusei_insert(list{gakusei4}, gakusei3) == list{gakusei4, gakusei3})
Console.log(gakusei_insert(list{gakusei3}, gakusei4) == list{gakusei4, gakusei3})
Console.log(gakusei_insert(list{gakusei6, gakusei5, gakusei3, gakusei2, gakusei1}, gakusei4) == list{gakusei6, gakusei5, gakusei4, gakusei3, gakusei2, gakusei1})

// 目的：受け取った学生のリスト lst を点数の順に並べる
// gakusei_ins_sort : gakusei_t list -> gakusei_t list
let rec gakusei_ins_sort = (lst) => switch lst {
| list{} => list{}
| list{first, ...rest} => gakusei_insert(gakusei_ins_sort(rest), first)
}

// テスト
Console.log(gakusei_ins_sort(lst1) == list{})
Console.log(gakusei_ins_sort(lst2) == list{gakusei2})
Console.log(gakusei_ins_sort(lst3) == list{gakusei4, gakusei3})
Console.log(gakusei_ins_sort(lst4) == list{gakusei4, gakusei3})
Console.log(gakusei_ins_sort(lst5) == list{gakusei6, gakusei5, gakusei4, gakusei3, gakusei2, gakusei1})
```
```ReScript:Ex10_4.res
// 人に関する情報を格納するレコード
open Ex08_3
open Ex08_4

// person_t 型のリストの例
let lst1 = list{}
let lst2 = list{person1}
let lst3 = list{person1, person2}
let lst4 = list{person2, person1}
let lst5 = list{person3, person1, person2}
 
// 目的：昇順に並んでいる lst の正しい位置に person を挿入する
// person_insert : person_t list -> person_t -> person_t list
let rec person_insert = (lst, person0) => switch lst {
| list{} => list{person0}
| list{{name: n, shincho: s, taiju: t, tsuki: ts, hi: h, ketsueki: k}, ...rest} =>
  switch person0 {
  | {name: n0, shincho: _, taiju: _, tsuki: _, hi: _, ketsueki: _} =>
    if n < n0 {list{{name: n, shincho: s, taiju: t, tsuki: ts, hi: h, ketsueki: k}, ...person_insert(rest, person0)}}
    else {list{person0, ...lst}}
  }
}
 
//テスト
Console.log(person_insert(list{}, person1) == list{person1})
Console.log(person_insert(list{person2}, person1) == list{person2, person1})
Console.log(person_insert(list{person1}, person2) == list{person2, person1})
Console.log(person_insert(list{person2, person1}, person3) == list{person3, person2, person1})

// 目的：受け取った人のリスト lst を名前の順に並べる
// person_ins_sort : person_t list -> person_t list
let rec person_ins_sort = (lst) => switch lst {
| list{} => list{}
| list{first, ...rest} => person_insert(person_ins_sort(rest), first)
}

//テスト
Console.log(person_ins_sort(lst1) == list{})
Console.log(person_ins_sort(lst2) == list{person1})
Console.log(person_ins_sort(lst3) == list{person2, person1})
Console.log(person_ins_sort(lst4) == list{person2, person1})
Console.log(person_ins_sort(lst5) == list{person3, person2, person1})
```
```ReScript:Ex10_5.res
type gakusei_t = {
  namae: string,
  tensuu: int,
  seiseki: string
}

// 学生のデータの例
let gakusei1 = {namae: "nakamura", tensuu:90, seiseki: "A"}
let gakusei2 = {namae: "miyahara", tensuu:80, seiseki: "A"}
let gakusei3 = {namae: "sato", tensuu:75, seiseki: "B"}
let gakusei4 = {namae: "idehara", tensuu:70, seiseki: "B"}
let gakusei5 = {namae: "tsubata", tensuu:65, seiseki: "C"}
let gakusei6 = {namae: "asai", tensuu:60, seiseki: "C"}
 
// 学生のリストの例
let lst1 = list{gakusei2}
let lst2 = list{gakusei3, gakusei4}
let lst3 = list{gakusei4, gakusei3}
let lst4 = list{gakusei4, gakusei1, gakusei6, gakusei5, gakusei2, gakusei3}
 
// 目的：学生リスト lst の中から最高得点の人を返す
// gakusei_max : gakusei_t list -> gakusei_t
let rec gakusei_max = (lst) => switch lst {
| list{} => {namae: "", tensuu: Js.Int.min, seiseki: ""}
| list{{namae: n, tensuu: t, seiseki: s}, ...rest} =>
    switch gakusei_max(rest) {
    |  {namae: _, tensuu: t_max, seiseki: _} => 
      if t_max < t {{namae: n, tensuu: t, seiseki: s}}
      else {gakusei_max(rest)}
    }
}
 
// テスト
Console.log(gakusei_max(lst1) == gakusei2)
Console.log(gakusei_max(lst2) == gakusei3)
Console.log(gakusei_max(lst3) == gakusei3)
Console.log(gakusei_max(lst4) == gakusei1)
```
```ReScript:Ex10_6.res
type gakusei_t = {
  namae: string,
  tensuu: int,
  seiseki: string
}

// 学生のデータの例
let gakusei1 = {namae: "nakamura", tensuu:90, seiseki: "A"}
let gakusei2 = {namae: "miyahara", tensuu:80, seiseki: "A"}
let gakusei3 = {namae: "sato", tensuu:75, seiseki: "B"}
let gakusei4 = {namae: "idehara", tensuu:70, seiseki: "B"}
let gakusei5 = {namae: "tsubata", tensuu:65, seiseki: "C"}
let gakusei6 = {namae: "asai", tensuu:60, seiseki: "C"}
 
// 学生のリストの例
let lst1 = list{gakusei2}
let lst2 = list{gakusei3, gakusei4}
let lst3 = list{gakusei4, gakusei3}
let lst4 = list{gakusei4, gakusei1, gakusei6, gakusei5, gakusei2, gakusei3}
 
// 目的：学生リスト lst の中から最高得点の人を返す
// gakusei_max : gakusei_t list -> gakusei_t
let rec gakusei_max = (lst) => switch lst {
| list{} => {namae: "", tensuu: Js.Int.min, seiseki: ""}
| list{{namae: n, tensuu: t, seiseki: s}, ...rest} => {
    let rest_max = gakusei_max(rest)
    switch rest_max {
    | {namae: _, tensuu: t_max, seiseki: _} =>
  	  if t_max < t {{namae: n, tensuu: t, seiseki: s}} else {rest_max}
    }
  }
}

// テスト
Console.log(gakusei_max(lst1) == gakusei2)
Console.log(gakusei_max(lst2) == gakusei3)
Console.log(gakusei_max(lst3) == gakusei3)
Console.log(gakusei_max(lst4) == gakusei1)
```
```ReScript:Ex10_7.res
// 人に関する情報を格納するレコード
open Ex08_3
open Ex08_4

// person_t list 型のデータの例
let lst1 = list{}
let lst2 = list{person1}
let lst3 = list{person2}
let lst4 = list{person1, person2, person3}
let lst5 = list{person2, person1, person1}
 
// 目的：人のリスト lst のうち各血液型の人が何人いるかを集計する
// ketsueki_shukei : person_t list -> int * int * int * int
let rec ketsueki_shukei = (lst) => switch lst {
| list{} => (0, 0, 0, 0)
| list{{name: _, shincho: _, taiju: _, tsuki: _, hi: _, ketsueki: k}, ...rest} => {
    let (a, o, b, ab) = ketsueki_shukei(rest)
    if k == "A" {(a + 1, o, b, ab)}
    else if k == "O" {(a, o + 1, b, ab)}
    else if k== "B" {(a, o, b + 1, ab)}
    else {(a, o, b, ab + 1)}
  }
}

// テスト
Console.log(ketsueki_shukei(lst1) == (0, 0, 0, 0))
Console.log(ketsueki_shukei(lst2) == (1, 0, 0, 0))
Console.log(ketsueki_shukei(lst3) == (0, 0, 1, 0))
Console.log(ketsueki_shukei(lst4) == (1, 1, 1, 0))
Console.log(ketsueki_shukei(lst5) == (2, 0, 1, 0))
```
```ReScript:Ex10_8.res
open Ex08_3
open Ex08_4

// person_t list 型のデータの例
let lst1 = list{person1}
let lst2 = list{person2}
let lst3 = list{person1, person2, person3}
let lst4 = list{person2, person1, person1}

// 目的：人のリスト lst のうち各血液型の人が何人いるかを集計する
// ketsueki_shukei : person_t list -> int * int * int * int
let rec ketsueki_shukei = (lst) => switch lst {
| list{} => (0, 0, 0, 0)
| list{{name: _, shincho: _, taiju: _, tsuki: _, hi: _, ketsueki: k}, ...rest} => {
    let (a, o, b, ab) = ketsueki_shukei(rest)
    if k == "A" {(a + 1, o, b, ab)}
    else if k == "O" {(a, o + 1, b, ab)}
    else if k== "B" {(a, o, b + 1, ab)}
    else {(a, o, b, ab + 1)}
  }
}

// 目的：人のリスト lst のうち最多の血液型を返す
// saita_ketsueki : person_t list -> string
let saita_ketsueki = (lst) => {
  let (a, o, b, ab) = ketsueki_shukei(lst)
  let saidai = Math.Int.max(Math.Int.max(a, o), Math.Int.max(b, ab))
  if saidai == a {"A"}
  else if saidai == o {"O"}
  else if saidai == b {"B"}
  else {"AB"}
}

// テスト
Console.log(saita_ketsueki(lst1) == "A")
Console.log(saita_ketsueki(lst2) == "B")
Console.log(saita_ketsueki(lst3) == "A")
// 同点のときは A, O, B, AB の順に見つかったものが返されてしまう
Console.log(saita_ketsueki(lst4) == "A") 
```
```ReScript:Ex10_9.res
// 目的：lst1 と lst2 の長さが等しいかどうかを判定する
// equal_length : 'a list -> 'a list -> bool
let rec equal_length = (lst1, lst2) => switch (lst1, lst2) {
| (list{}, list{}) => true 
| (list{}, list{_, ..._}) => false 
| (list{_, ..._}, list{}) => false
| (list{_, ...rest1}, list{_, ...rest2}) => equal_length(rest1, rest2)
}

// テスト
Console.log(equal_length(list{}, list{}) == true)
Console.log(equal_length(list{}, list{1, 2}) == false)
Console.log(equal_length(list{1, 2}, list{}) == false)
Console.log(equal_length(list{1, 3}, list{2, 4}) == true)
Console.log(equal_length(list{"a", "b", "c", "d"}, list{1, 3}) == false)
Console.log(equal_length(list{"a", "b", "c", "d"}, list{1, 3, 2, 4}) == true)
```
```ReScript:Ex10_10.res
open Ex08_5 //ekimei_t の定義
open Ex09_9 //global_ekimei_list の定義

// 目的：ローマ字の駅名を漢字に直す
// romaji_to_kanji : string -> ekimei_t list -> string
let rec romaji_to_kanji = (r0, ekimei_list) => switch ekimei_list {
| list{} => ""
| list{{kanji: k, kana: _, romaji: r, shozoku: _}, ...rest} =>
    if r0 == r {k} else {romaji_to_kanji(r0, rest)}
}

// テスト
Console.log(romaji_to_kanji("myogadani", global_ekimei_list) == "茗荷谷")
Console.log(romaji_to_kanji("shibuya", global_ekimei_list) == "渋谷")
Console.log(romaji_to_kanji("otemachi", global_ekimei_list) == "大手町")
```
```ReScript:Ex10_11.res
open Ex08_7   // ekikan_t の定義
open Ex09_10  // global_ekikan_list の定義

// 目的：ふたつの駅の間の距離を求める
// get_ekikan_kyori : string -> string -> ekikan_t list -> float
let rec get_ekikan_kyori = (eki1, eki2, lst) => switch lst {
| list{} => Float.Constants.positiveInfinity
| list{{kiten: k, shuten: s, keiyu: _, kyori: r, jikan: _}, ...rest} =>
   if (eki1 == k && eki2 == s) || (eki1 == s && eki2 == k) {r}
   else {get_ekikan_kyori(eki1, eki2, rest)}
}

// テスト
if %raw(`require.main === module`) {
  Console.log(get_ekikan_kyori("茗荷谷", "新大塚", global_ekikan_list) == 1.2)
  Console.log(get_ekikan_kyori("茗荷谷", "池袋", global_ekikan_list) == Float.Constants.positiveInfinity)
  Console.log(get_ekikan_kyori("東京", "大手町", global_ekikan_list) == 0.6)
}
```
```ReScript:Ex10_12.res
open Ex08_5   // ekimei_t の定義
open Ex08_7   // ekikan_t の定義
open Ex09_9   // global_ekimei_list の定義
open Ex09_10  // global_ekikan_list の定義

// 目的：ローマ字の駅名を漢字に直す
// romaji_to_kanji : string -> ekimei_t list -> string
let rec romaji_to_kanji = (r0, ekimei_list) => switch (ekimei_list) {
| list{} => ""
| list{{kanji: k, kana: _, romaji: r, shozoku: _}, ...rest} =>
    if r0 == r {k} else {romaji_to_kanji(r0, rest)}
}

// 目的：ふたつの駅の間の距離を求める
// get_ekikan_kyori : string -> string -> ekikan_t list -> float
let rec get_ekikan_kyori = (eki1, eki2, lst) => switch lst {
| list{} => Float.Constants.positiveInfinity
| list{{kiten: k, shuten: s, keiyu: _, kyori: r, jikan: _}, ...rest} =>
   if (eki1 == k && eki2 == s) || (eki1 == s && eki2 == k) {r}
   else {get_ekikan_kyori(eki1, eki2, rest)}
}

// 目的：ふたつの駅の間の距離を文字列で表現する
let kyori_wo_hyoji = (romaji1, romaji2) => {
  let kanji1 = romaji_to_kanji(romaji1, global_ekimei_list)
  if kanji1 == "" {romaji1 ++ " という駅は存在しません"}
  else{
    let kanji2 = romaji_to_kanji(romaji2, global_ekimei_list)
    if kanji2 == "" {romaji2 ++ " という駅は存在しません"}
    else {
      let kyori = get_ekikan_kyori(kanji1, kanji2, global_ekikan_list)
	    if kyori == Float.Constants.positiveInfinity { 
	      kanji1 ++ "と" ++ kanji2 ++ "はつながっていません"
      } else {
        kanji1 ++ "から" ++ kanji2 ++ "までは " ++ Js.Float.toString(kyori) ++ " キロです"
      }
    }
  }
} 

// テスト
Console.log(kyori_wo_hyoji("myougadani", "shinotsuka") == "myougadani という駅は存在しません")
Console.log(kyori_wo_hyoji("myogadani", "shinotsuka") == "茗荷谷から新大塚までは 1.2 キロです")
Console.log(kyori_wo_hyoji("myogadani", "ikebukuro") == "茗荷谷と池袋はつながっていません")
Console.log(kyori_wo_hyoji("tokyo", "ootemachi") == "ootemachi という駅は存在しません")
Console.log(kyori_wo_hyoji("tokyo", "otemachi") == "東京から大手町までは 0.6 キロです")
```