Rustで騎士の周遊(Closed knight's tour)

エイトクイーンやったら次はこれでしょ。出発地点に戻ってくるので騎士の巡回ではなく周遊というらしい。BOARD_SIZE=6なら解が求められるけど、8では返ってこない。ロジックを少し変えないとダメ。それでもBOARD_SIZE=6ならJava版の2倍の速さで全パターン見つけ出す。

use std::time::Instant;

// チェス盤の大きさ
const BOARD_SIZE: i32 = 6;

// 次の配置場所計算用
const NEXT_X: [i32; 8] = [1, 2, 2, 1, -1, -2, -2, -1];
const NEXT_Y: [i32; 8] = [-2, -1, 1, 2, 2, 1, -1, -2];

// 見つけた解の数
static mut NUM_OF_ANSWER: i32 = 0;

fn main() {
    // チェス盤
    let mut board = [[0 as i32; BOARD_SIZE as usize]; BOARD_SIZE as usize];
    let st = Instant::now();
    board[0][0] = 1;
    tour(0, 0, 1, &mut board);

    let et = Instant::now();
    unsafe {
        println!("num of path={}, elapsed: {:?}", NUM_OF_ANSWER, (et - st));
    }
}

// ナイトを次に置く場所を探す
fn tour(y: i32, x: i32, step: i32, board: &mut [[i32; BOARD_SIZE as usize]; BOARD_SIZE as usize]) {
    let mut x2; //  let mut x2, y2; とは書けない
    let mut y2;
    // すべてのマス目を巡回した
    if step == BOARD_SIZE * BOARD_SIZE {
        // 次の移動先候補に1があればClosed knight's tour
        for i in 0..NEXT_X.len() {
            x2 = x + NEXT_X[i];
            y2 = y + NEXT_Y[i];
            // 盤面の中
            if (-1 < x2 && x2 < BOARD_SIZE) && (-1 < y2 && y2 < BOARD_SIZE) {
                // 開始位置か?
                if board[y2 as usize][x2 as usize] == 1 {
                    // 結果を出力
                    print_board(board);
                    return;
                }
            }
        }
        // Closed knight's tourではない
        return;
    }
    // 次に置く場所を探す
    for i in 0..NEXT_X.len() {
        x2 = x + NEXT_X[i];
        y2 = y + NEXT_Y[i];
        // 盤面の中
        if (-1 < x2 && x2 < BOARD_SIZE) && (-1 < y2 && y2 < BOARD_SIZE) {
            // まだナイトを置いていない
            if board[y2 as usize][x2 as usize] == 0 {
                // ナイトを置く
                board[y2 as usize][x2 as usize] = step + 1;
                // 次の場所を探す
                tour(y2, x2, step + 1, board);
                // ナイトを取る
                board[y2 as usize][x2 as usize] = 0;
            }
        }
    }
}

///    結果を整形して出力する
fn print_board(board: &[[i32; BOARD_SIZE as usize]; BOARD_SIZE as usize]) {
    unsafe {
        NUM_OF_ANSWER += 1;
        //    println!("[{}] {}", NUM_OF_ANSWER, Local::now().format("%H:%M:%S"));
    }
    let mut sb: String = "+".to_string();
    sb.push_str(&"---+".repeat(BOARD_SIZE as usize));

    println!("{}", sb);
    for y in 0..BOARD_SIZE as usize {
        print!("|");
        for x in 0..BOARD_SIZE as usize {
            print!("{:>3}|", board[y][x]);
        }
        println!("");
        println!("{}", sb);
    }
}

これも先にJava版を作ってRustに書き直した。元プログラムはこれ。

package com.example;

import java.util.Collections;
import java.util.Date;

public class KnightsTour {
    // チェス盤の大きさ
    private static final int BOARD_SIZE = 6;

    // 次の配置場所計算用
    private static final int[] nextX = { 1, 2, 2, 1, -1, -2, -2, -1 };
    private static final int[] nextY = { -2, -1, 1, 2, 2, 1, -1, -2 };

    // 見つけた解の数
    private static int numOfAnswer = 0;

    public static void main(String[] args) {
        // チェス盤
        int[][] board = new int[BOARD_SIZE][BOARD_SIZE];

        long st = System.currentTimeMillis();

        board[0][0] = 1;
        tour(0, 0, 1, board);

        long et = System.currentTimeMillis();
        System.out.printf("num of path=%,d elapsed: %,d(ms)\n", numOfAnswer, (et - st));
    }

    /**
    * ナイトを次に置く場所を探す
    * @param x
    * @param y
    * @param step
    * @param board
    */
    private static void tour(int y, int x, int step, int[][] board) {
        int x2, y2;
        // すべてのマス目を巡回した
        if (step == BOARD_SIZE * BOARD_SIZE) {
            // 次の移動先候補に1があればClosed knight's tour
            for (int i = 0; i < nextX.length; i++) {
                x2 = x + nextX[i];
                y2 = y + nextY[i];
                // 盤面の中
                if ((-1 < x2 && x2 < BOARD_SIZE) && (-1 < y2 && y2 < BOARD_SIZE)) {
                    // 開始位置か?
                    if (board[y2][x2] == 1) {
                        // 結果を出力
                        printBoard(board);
                        //System.exit(0);
                        return;
                    }
                }
            }
            // Closed knight's tourではない
            return;
        }

        // 次に置く場所を探す
        for (int i = 0; i < nextX.length; i++) {
            x2 = x + nextX[i];
            y2 = y + nextY[i];
            // 盤面の中
            if ((-1 < x2 && x2 < BOARD_SIZE) && (-1 < y2 && y2 < BOARD_SIZE)) {
                // まだナイトを置いていない
                if (board[y2][x2] == 0) {
                    // ナイトを置く
                    board[y2][x2] = step + 1;
                    // 次の場所を探す
                    tour(y2, x2, step + 1, board);
                    // ナイトを取る
                    board[y2][x2] = 0;
                }
            }
        }
    }

    /**
    * 結果を整形して出力する
    * @param board
    */
    private static void printBoard(int[][] board) {
        ++numOfAnswer;
        System.out.printf("[%,d] %tT\n", numOfAnswer, new Date());
        StringBuffer sb = new StringBuffer();
        sb.append("+");
        sb.append(String.join("", Collections.nCopies(BOARD_SIZE, "---+")));

        System.out.println(sb.toString());
        for (int y = 0; y < BOARD_SIZE; ++y) {
            System.out.print("|");
            for (int x = 0; x < BOARD_SIZE; ++x) {
                System.out.printf("%3d|", board[y][x]);
            }
            System.out.println();
            System.out.println(sb.toString());
        }

    }
}

Rustでエイト・クイーン

本業が一段落したので、またRustの勉強。本読むだけではつまらないし、経験値上がらないので、プログラム書いてみる。テーマは「エイト・クイーン」。いきなりRustで書き下ろす自信は無いので、Javaで書いてそれをRustへ書き直してみた。よって、Rustの良さを生かし切れていないのは十分承知。それでもいろいろ知見が得られた。

use std::time::Instant;

// Queenの数 = チェス盤の大きさ
const NUM_OF_QUEEN: i32 = 8;
// 見つけた解の数
static mut NUM_OF_ANSWER: i32 = 0;

fn main() {
    let mut buff = [0; NUM_OF_QUEEN as usize];
    let st = Instant::now();
    place(0, &mut buff);
    let et = Instant::now();
    println!("elapsed: {:?}", et - st);
}

/// 指定された列でQueenが置ける行を探す
fn place(col: i32, buff: &mut [i32]) {
    // N-Queen完成
    if col == NUM_OF_QUEEN {
        unsafe {
            NUM_OF_ANSWER += 1;
            println!("{}:{:?}", NUM_OF_ANSWER, buff);
        }
        return;
    }

    // Queenを置ける場所を探す
    for row in 0..NUM_OF_QUEEN {
        if check_can_place_queen(col, row, buff) {
            //  おける
            buff[col as usize] = row;
            //  次の列へ
            place(col + 1, buff)
        }
    }
}

/// col, rowで指定された盤面の場所にQueenが置けるかチェックする
fn check_can_place_queen(col: i32, row: i32, buff: &[i32]) -> bool {
    // 左端列は自由における
    if col == 0 {
        return true;
    }

    // この列の左側の配置状況から判断する
    for i in 0..col {
        // 同じ行に配置済
        if buff[i as usize] == row {
            return false;
        }
        // 左上斜め方向に配置済
        if buff[i as usize] == (row - col + i) {
            return false;
        }
        // 左下斜め方向に配置済
        if buff[i as usize] == (row + col - i) {
            return false;
        }
    }
    // ここに配置できる
    return true;
}
  • 外部変数(グローバル変数)が使いにくい。シングルスレッドで実行するから競合する恐れは無いんだけど、コンパイラはそれを知らないからunsafeを要求する。

  • 配列の添字はusize。i32ではダメ。as usizeが必要。

  • 再帰呼び出しでbuffをcloneして渡すのかと思ったけど、&mutでいけた。所有権とか、参照の規則から見て良いんだろうか。

ちなみに最初に作ったJavaプログラムはこんな感じ。

package com.example;

public class NQueen {
    // Queenの数 = チェス盤の大きさ
    private static final int NUM_OF_QUEEN = 8;
    // 見つけた解の数
    private static int numOfAnswer = 0;

    public static void main(String[] args) {
        int[] buff = new int[NUM_OF_QUEEN];
        long st = System.currentTimeMillis();
        place(0, buff);
        long et = System.currentTimeMillis();
        System.out.printf("time=%,d(ms)\n", et - st);
    }

    /**
    * 指定された列でQueenが置ける行を探す
    * @param col
    */
    private static void place(int col, int[] buff) {
        if (col == NUM_OF_QUEEN) {
            // N-Queen完成
            ++numOfAnswer;
            System.out.printf("%,d:[", numOfAnswer);
            for (int i = 0; i < NUM_OF_QUEEN; i++) {
                if (i > 0) {
                    System.out.print(", ");
                }
                System.out.print(buff[i]);
            }
            System.out.println("]");
            return;
        }
        // Queenを置ける場所を探す
        for (int row = 0; row < NUM_OF_QUEEN; row++) {
            if (checkCanPlaceQueen(col, row, buff)) {
                // 配置できる
                buff[col] = row;
                // 次の列へ
                place(col + 1, buff);
            }

        }
    }

    /**
    * col, rowで指定された盤面の場所にQueenが置けるかチェックする
    * @param col
    * @param row
    * @param buff
    * @return
    */
    private static boolean checkCanPlaceQueen(int col, int row, int[] buff) {
        // 左端列は自由における
        if (col == 0) {
            return true;
        }

        // この列の左側の配置状況から判断する
        for (int i = 0; i < col; i++) {
            // 同じ行に配置済
            if (buff[i] == row) {
                return false;
            }
            // 左上斜め方向に配置済
            if (buff[i] == (row - col + i)) {
                return false;
            }
            // 左下斜め方向に配置済
            if (buff[i] == (row + col - i)) {
                return false;
            }
        }
        // ここに配置できる
        return true;
    }

}

実践Rustプログラミング入門

さくっと読了。

実践Rust入門[言語仕様から開発手法まで](2回目)

2回目読了。本業が忙しいから大分時間がかかた。

  • プログラミング言語Rust 公式ガイドを熟読したから、この本に書いてあることがわかる、という感じ。1冊目がこれだとかなり厳しと思う。
  • 大きめのサンプル(ToyVecとか)を作りながら重要な概念を説明してるけど、「こういう場合、~を使います」あるいは「こうします」的な書き方で「なぜこれが必要なのか?」の説明が少なめ。
  • プログラミング言語Rust 公式ガイドよりもテクニカルなことが説明されているんだけど、それがサンプルコードの機能説明の中に埋まっていて、あとから探すときに少々不便。
  • あと日本語的に、、、な文章も散見される。編集さん、もうちょっと頑張って欲しかった。

さて次は↓これだ。

プログラミング言語Rust 公式ガイド(2回目)

写経しつつ2回目読了。

  • コードを打ち込んで試すだけでなく、ノートに手書きで書き写しながらやってみた。またサンプルコードをあれやこれやいじってみた。おかげでかなり理解が進んだ気がする。
  • 型変換のおかげでメソッド仮引数には&を書かなくて済むんだけど「このメソッドは所有権をとるの?それとも参照?」と悩む。まぁAPI Docを見れば解決するんですけどね。でもDerefを使って推移的にされる場合は、?となる。慣れと言われればそうだけど、はじめは理屈がわからなくて悩む。
  • String(文字列)とstr(スライス)が、まだしっくりこない。これも型変換で&str型の引数にStringを渡せるせいかも。
  • Result, Optionは便利。
  • 所有権、ライフタイムは、Java以上に堅牢なプログラムを作るのに役立ちそう。

ということで、再度↓にチャレンジする。

Wikipediaへの寄付

最近アクセスするたびにバナーが表示されるので「そろそろ来るか?」と思ってたら、創設者からのメールが届いた。1,560円でいいらしかったけど、Wikipediaにはほぼ毎日お世話になっているので3,000円寄付した。これで今年も2%の特別な読者になったわけだ。

Git本

これから急いでGitを学びたいなら、この本(以下「わかばちゃん」)がお薦め。

漫画と図解でほんとわかりやすい。何よりもたとえが秀逸。例えばステージングすることを「撮影台に載せる」とかね。そしてgitコマンドではなく、SourceTreeでgit操作を説明しているのがイイ。

Git入門書には「いちばんやさしい」を標榜しているいちばんやさしいGit&GitHubの教本 人気講師が教えるバージョン管理&共有入門 (「いちばんやさしい教本」シリーズ)もあるけど、この本は基本gitコマンドで各種操作を説明している。いまどきコマンドでやるの?SourceTreeとかGUI操作が主流じゃね?gitコマンドを学びたい人にはいいけど、ソース管理するためのgitなんだから、本末転倒な気もする。 そう考えるとお薦めできない。

SourceTree前提ならGitが、おもしろいほどわかる基本の使い方33 改訂新版もあるけど、わかばちゃんに比べ説明が難しい。同じことを説明しているんだけど、わかばちゃんの方がずっとわかりやすい。読むならわかばちゃんの次にした方がいいと思う。

もっとも現場ではSourceTreeじゃなくてEclipseのEGitとか、Visual Studio Codeのgitプラグインを使う場合も多いので、もうちょっと勉強が必要になることも多いけど、「forkしてcloneしてbranch切ってpullしてcommitしてpushしてpull requestしてmergeしてもらう」という一連の流れが理解でき、現場でもみんなの会話について行けるようになると思う。とりあえずこれを読めばGitは大丈夫じゃね?と思う内容になってます。