024号文書

主にプログラミング

PythonistaがRustはじめました#004 -- if式とwhileループ

宣言厨のwotsushiです。 今日は泣く泣くwhileで手続き的なコードを書いたお話です。

ABC116-B

入出力が以下の問題です。

  • 入力: 正の整数s
  • 出力: sを初項とするコラッツ数列について、過去に出現した値が初めて再び出現する項の番号

https://atcoder.jp/contests/abc116/tasks/abc116_b

HashSet

過去に出現した値を集合型のデータで管理してみます。 他の言語同様、Rustにも集合型のデータ構造はあります。 今回は、HashSet を使います。

使い方ですが、以下のような感じです。

use std::collections::HashSet;

fn main() {
    // 空集合を生成
    let mut s = HashSet::new();
    // 1を追加する
    s.insert(1);
    // 1が含まれるかを表す真偽値を得る
    let x = s.contains(&1);

Pythonではset型はインポートせずに使えますが、Rustでは最初にインポートする必要があることに注意です。 HashSetと言うくらいなので、追加・存在判定はO(1)でできると思います(どこに書いているのだろう)。 また、存在判定するcontainsは引数に & をつける必要があります。 これ(参照型?)についてはいずれ記事を書こうと思います。

mutableな変数

今まで解いた問題はletで変数を定義していました。 しかしletで定義した変数はimmutableになってしまいます。 私は宣言厨なので、基本的にはletで事足りるのですが、今回の問題のように手続き的に解きたい場合は困ります。 このような場合、 let mut で変数を定義するとmutableな変数を定義できます。

let mut a = 1;
// やっぱ2にする
a = 2;

mutableだよ!というのを強調することを強制するよい仕様ですね。

if式

Pythonとは異なり、Rustではifは式です。 たしかRubyもifは式だったと思います。 したがって、以下のような書き方ができます。

let a = if x % 2 == 0 {
    a / 2
} else {
    3 * a + 1
};

セミコロンはaの右辺の最後に記述することに注意です。 セミコロンは文の末尾を表すシンボルであり、 a / 23 * a + 1 は式なのでセミコロンは書いてはいけないのです。

ところで、Rustの式指向の考えは好感が持てますね。 Pythonもif式がほしいなあ(いつも三項演算子のifで無理やりそれっぽく書いています)。

whileループ

サブタイトルになっているwhileループですが、とくに説明することはないです。 他言語とほとんど同じだからです。

// aが3未満の間、aに1を加え続けるコード
while a < 3 {
    a += 1;
}

以上より、問題を解くコードは以下となります。

fn main() {
    // 入力
    let s = get!(i64);

    let mut a = s;
    let mut m = 1;
    let mut dp = HashSet::new();
    while !dp.contains(&a) {
        dp.insert(a);
        a = if a % 2 == 0 {
            a / 2
        } else {
            3 * a + 1
        };
        m += 1;
    }
    let ans = m;

    // 出力
    println!("{}", ans);
}

うーん、whileを使ったコードはあまり満足できないですね... 次回は、ここまで説明をサボってきた変数の所有権について向き合いたいと思います。 ではでは。