OCaml で Welcome to AtCoder を解いてみた

[Qiita Engineer Festa 2024（キータ・エンジニア・フェスタ 2024） - Qiita](https://qiita.com/official-campaigns/engineer-festa/2024)
において、約1ヶ月で38記事という大量の記事の投稿を要求されることがわかった。
そこで、あまりコストをかけずに記事数を稼ぐ方法を考えた結果、「Welcome to AtCoder を様々な言語で解く」ことを思いついた。
単に解くだけでなく、使用する言語仕様の解説を入れれば、記事として一応成立するだろう。

# Welcome to AtCoder

[PracticeA - Welcome to AtCoder](https://atcoder.jp/contests/abs/tasks/practice_1)

Welcome to AtCoder では、以下の形式で整数 $a$, $b$, $c$ および文字列 $s$ が入力として与えられる。

```text
a
b c
s
```

この入力をもとに、与えられた整数の和 $sum = a + b + c$ および文字列 $s$ を、以下の形式で出力することが求められる。

```text
sum s
```

# 今回用いた OCaml の機能

## 定数と関数の定義

[Expressions and Definitions](https://ocaml.org/docs/tour-of-ocaml#expressions-and-definitions)
[Functions](https://ocaml.org/docs/tour-of-ocaml#functions)
[Recursive Functions](https://ocaml.org/docs/tour-of-ocaml#recursive-functions)

```ocaml
let 名前 = 値;;
```

のように書くことで、定数を定義できる。
それぞれの定義の後に `;;` をつける。

:::note info
`let` が連続する場合など、`;;` をつけなくてもいい場合もあるようである。
しかし、後述の出力を行う場合など、`;;` をつけないとエラーになる場合もあるようである。
`;;` はつけておくのが安全だろう。
:::

```ocaml
let 名前 = 値 in 式
```

のように書くことで、「式」の中でしか使わない定数を定義できる。

:::note info
この `let … in …` 全体も式である。
:::

```ocaml
let 関数名 引数名1 引数名2 … = 式;;
```

のように書くことで、(再帰しない) 関数を定義できる。

```ocaml
let rec 関数名 引数名1 引数名2 … = 式;;
```

のように書くことで、再帰関数を定義できる。

:::note info
すなわち、再帰関数を定義する場合は、通常の `let` に続いて空白と `rec` を書く。
:::

## パターンマッチ (リストの要素の取り出し)

[Lists](https://ocaml.org/docs/tour-of-ocaml#lists)

```ocaml
match リスト with
| [] -> リストが空の場合の式
| x :: xs -> リストが空でない場合の式
```

と書くと、リストが空のときと空でないときで処理を分け、さらにリストの最初の要素を取り出すことができる。
「リストが空でない場合の式」において、`x` が「リスト」の最初の要素、`xs` が「リスト」から最初の要素を取り除いたリストである。

## 整数の処理

[OCaml library : Int](https://ocaml.org/manual/5.2/api/Int.html)

```ocaml
a + b
```

により、`a` と `b` の和を求めることができる。

```ocaml
a - b
```

により、`a` から `b` を引いた差を求めることができる。

```ocaml
a * b
```

により、`a` と `b` の積を求めることができる。

```ocaml
Int.to_string 値
```

により、「値」を十進数で表した文字列を得ることができる。

## 文字列の処理

[OCaml library : String](https://ocaml.org/manual/5.2/api/String.html)

```ocaml
a ^ b
```

により、文字列 `a` と `b` を連結した文字列を得ることができる。

```ocaml
String.make 個数 文字
```

により、「文字」を「個数」回繰り返した文字列を得ることができる。

```ocaml
String.get 文字列 位置
```

により、「文字列」の「位置」番目 (0-origin) の文字を得ることができる。

```ocaml
String.get_uint8 文字列 位置
```

により、「文字列」の「位置」番目のバイトを整数 (`int`) で得ることができる。

```ocaml
String.split_on_char 文字 文字列
```

により、「文字列」を「文字」で区切った文字列のリストを得ることができる。

```ocaml
String.fold_left 関数 初期値 文字列
```

により、「初期値」から「文字列」のそれぞれの文字について左から順に「関数」で処理した結果を得ることができる。
「関数」は、「今の値」と「処理する文字」を引数にとる。
すなわち、以下のような処理を行う。

```javascript
function fold_left(func, initial_value, str) {
    let value = initial_value;
    for (let i = 0; i < str.length; i++) {
        value = func(value, str.charAt(i));
    }
    return value;
}
```

## 入出力

[Side-Effects and the unit Type](https://ocaml.org/docs/tour-of-ocaml#side-effects-and-the-unit-type)

```ocaml
read_line ()
```

により、標準入力から1行読み込み、結果を文字列として得ることができる。
得られる文字列には、末尾に改行文字は含まれない。

:::note info
これは、`read_line` 関数に引数 `()` (unit) を渡している。
:::

```ocaml
print_endline 文字列;;
```

により、標準出力に「文字列」 (`String`) を出力し、さらに改行を出力できる。

# 提出コード

```ocaml
let str_to_int_step value ch =
  let ch_str = String.make 1 ch
  in value * 10 + (String.get_uint8 ch_str 0) - 48
;;

let str_to_int s =
  String.fold_left str_to_int_step 0 s
;;

let rec sum_list l s =
  match l with
  | [] -> s
  | x :: xs -> sum_list xs (s + (str_to_int x))
;;

let line1 = read_line ();;
let line2 = read_line ();;
let line3 = read_line ();;

let bc_str = String.split_on_char (String.get " " 0) line2;;

let a = str_to_int line1;;
let sum = sum_list bc_str a;;
let result_str = (Int.to_string sum) ^ " " ^ line3;;

print_endline result_str;;
```

[提出 #54993981 - AtCoder Beginners Selection](https://atcoder.jp/contests/abs/submissions/54993981)

# 提出コードの解説

```ocaml
let str_to_int_step value ch =
  let ch_str = String.make 1 ch
  in value * 10 + (String.get_uint8 ch_str 0) - 48
;;
```

文字列の数値への変換を一桁進める。
文字を `String.make` で文字列に変換し、さらに `String.get_uint8` で文字コードに変換する。
この桁の数値は、変換結果から `0` の文字コードを引いた値である。
1個上の桁までの変換結果に (十進数なので) 10 を掛け、さらにこの桁の数値を足すことで変換を進める。

```ocaml
let str_to_int s =
  String.fold_left str_to_int_step 0 s
;;
```

`String.fold_left` を用い、各桁の変換を行うことで、文字列をそれが表す数値に変換する。

```ocaml
let rec sum_list l s =
  match l with
  | [] -> s
  | x :: xs -> sum_list xs (s + (str_to_int x))
;;
```

数値を表す文字列のリストについて、それらを数値に変換しながら、和を求める。

```ocaml
let line1 = read_line ();;
let line2 = read_line ();;
let line3 = read_line ();;
```

入力の各行を読み込む。

```ocaml
let bc_str = String.split_on_char (String.get " " 0) line2;;
```

2行目を空白で分割する。
`String.get` を用いて文字を用意している。

```ocaml
let a = str_to_int line1;;
let sum = sum_list bc_str a;;
```

1行目の文字列を数値に変換し、それに2行目の文字列の各部分が表す数値を加算して和を求める。

```ocaml
let result_str = (Int.to_string sum) ^ " " ^ line3;;

print_endline result_str;;
```

求めた和・空白・3行目を連結し、出力する。