2020/03/28

RustのsubsliceでちょこっとL99問題を試してみる

OCamlと比較してみる

L99問題は以前Common Lispの記事で少し取り上げました。今回はOCamlのL99を元にRustで少しやってみようかと思います。
まず第一問目は、「リストの最後の要素を返す関数と作成する」という問題です。OCamlでは以下のようになります。

let rec last = function
  | [] -> None
  | [x] -> Some x
  | _ :: tl -> last tl
;;
OCamlでは再帰関数にはrecをつけます。上記のコードはfunctionを使ってちょっと省略した書き方になっていますが、以下のコードと同じです。

let rec last list =
  match list with
  | [] -> None
  | [x] -> Some x
  | _ :: tl -> last tl
;;
ではRustで同じように作成してみるとどうなるでしょうか?

fn last(list: &[i32]) -> Option<i32> {
    match list {
        [] => None,
        [x] => Some(*x),
        [_, tl @ ..] => last(tl),
    }
}
こんな感じになるのではないかと思います。
第2問目。「リストの最後の2つの要素を返す関数を作成する」という問題。まずはOCamlのコードから

let rec last_two = function
  | [] | [_] -> None
  | [x; y] -> Some (x, y)
  | _ :: tl -> last_two tl
;;
リストが空か要素が一つの場合、2つの要素を返すことができないのでNoneとなります。要素が2つの場合は、ぴったしなのでそれを返します。それ以上要素がある場合は再帰的に処理を繰り返します。これをRustでやってみますと

fn last_two(list: &[i32]) -> Option<(i32, i32)> {
    match list {
        [] | [_] => None,
        [x, y] => Some((*x, *y)),
        [_, tl @ ..] => last_two(tl)
    }
}
第3問目。「k番目の要素を返す関数を作成する」という問題。通常リストは0番から始まりますが、この関数の場合は、1から始めるというものです。
OCamlでのコードは以下のようになります。

let rec at k = function
  | [] -> None
  | hd :: tl -> if k = 1 then Some hd else at (k - 1) tl
;;
リストが空だと当然None。kが1の場合は最初の要素なので、hdを返す。再帰的に残りのリストを探していきます。ざっくり言うとリストをどんどん削って行く感じです。tlがあり、かつkが1でないかぎり、kをデクリメントし、削り続け、最終的にkが1になった時点で削るのをやめ、先頭の要素を返す。といった感じでしょうか。説明下手ですみません。頭の中ではイメージできているんですが……
Rustでやってみます

fn at(list: &[i32], k: usize) -> Option<i32> {
    match list {
        [] => None,
        [hd, tl @ ..] => {
            if k == 1 {
                Some(*hd)
            } else {
                at(tl, k - 1)
            }
        }
    }
}
第4問目。「リストの要素の数を返す関数」の作成。これはlenを使えばよいわけですが、今回はないものとして考えます。
OCamlのコードは以下のようなもの

let length list =
  let rec aux n = function
    | [] -> n
    | _ :: tl -> aux (n + 1) tl in
  aux 0 list
;;
これをRustでやってみると

fn length(list: &[i32]) -> usize {
    fn aux(n: usize, l: &[i32]) -> usize {
        match l {
            [] => n,
            [_, tl @ ..] => aux(n + 1, tl)
        }
    }
    aux(0, list)
}
関数内関数という形になってしまいました。果たして正解なのだろうか?(動作は正常)

まとめ

Rustのsubsliceは、個人的には非常に便利なもののように思えます。上記のコードをご覧になって、「同じやんかいさ」と思った方のいらっしゃるかと思います。私はその一人です。関数型言語をもっと勉強すべきなのかな?と感じです。といっても、ここ2ヶ月、父の入院(糖尿・高血圧・脳梗塞・視野欠損)、母の尻もちと慢性的なすべり症による腰痛、新型コロナウィルス、といったことが一気に重なり、私はフル稼働(大半は家事全般)でほとんど自分の時間がない状態。なんとか時間を作らねば……

合わせて読みたい記事