Rustで配列を扱う際の型変換地獄を避ける

2024/05/04に公開

はじめに

備忘録です。Rustc1.70.0で検証したやつなので、他のバージョンで動作することは保証しません。

解決したい問題

普通に書くと、負数を扱うので型変換が発生してちょっと面倒くさい。

static DI: &[isize] = &[0, -1, 0, 1];
static DJ: &[isize] = &[-1, 0, 1, 0];
fn main() {
    // ...前略
    for i in 0..h {
        for j in 0..w {
            for r in 0..4 {
                let ni = i as isize + DI[r];
                let nj = j as isize + DI[r];
                if ni >= 0 && ni < h as isize && nj >= 0 && nj < w as isize {
                    v[ni as usize][nj as usize] = 1;
                }
            }
        }
    }
}

解決法

wrapping_addを使ってあげると良い。wappring_addは桁溢れを無視して加算を行ってくれる。

static DI: &[usize] = &[0, !0, 0, 1];
static DJ: &[usize] = &[!0, 0, 1, 0];

fn main() {
    // ...前略
    for i in 0..h {
        for j in 0..w {
            for r in 0..4 {
                let ni = i.wrapping_add(DI[r]);
                let nj = j.wrapping_add(DJ[r]);
                if ni < h && nj < w {
                    v[ni as usize][nj as usize] = 1;
                }
            }
        }
    }
}

例えば、ABC302B - Find snukeのような問題では、周辺1マスのみならず、2マス、3マス先にアクセスしたい(= v[i - 2][j - 2]のようなちょっと遠いところを見たい)場合、warpping_subを使ってあげるとうまくいく。

static DI: &[usize] = &[0, !0, 0, 1];

fn main() {
    let i :usize = 5;
    // 4 3 2 1 0
    for k in 0..5 {
        let ni = i.wrapping_add(DI[1].wrapping_sub(k));
        print!("{} ", ni);
    }

    println!();

    let i :usize = 2;
    // 1 0 18446744073709551615 18446744073709551614 18446744073709551613
    for k in 0..5 {
        let ni = i.wrapping_add(DI[1].wrapping_sub(k));
        print!("{} ", ni);
    }

    println!();
}

記述量が減ってかなり楽になるのでおすすめです。

参考

Rustでグリッドグラフの探索【as usize地獄を回避する】

Discussion