読者です 読者をやめる 読者になる 読者になる

鉄分は大事。(特にヘム鉄)

研究とか開発とかプログラミングとか趣味とかを備忘録的な感じで気が向いたときに.

RustでAI その1 自殺しないAI

はじめに

 最近Rustが面白いなーと思っていてちょっと使ってる.もうちょい書けるようになりたくて,なんか書く題材ないかなーと探してたらCodingameでRustが使えることに気がついた.

www.codingame.com

 あんまり探索したりするゲームAIって書いたこと無かったので,ちょうど良いと思い この記事を参考にRustで書いてみた

qiita.com

 Rust初心者なのでここもっとうまく書けるよーとかってのあったら教えてください.

自殺しないAI

マクロ,定数

 標準入力からの取得,標準エラー出力への出力マクロがサンプルプログラムに記載されているのでありがたく利用する.定数は構造体みたいなのでまとめられるのかもしれないけどとりあえず列挙した.

macro_rules! print_err {
    ($($arg:tt)*) => (
        {
            use std::io::Write;
            writeln!(&mut ::std::io::stderr(), $($arg)*).ok();
        }
    )
}

macro_rules! parse_input {
    ($x:expr, $t:ident) => ($x.trim().parse::<$t>().unwrap());
}

const DX: [i32; 4] = [1, 0, -1, 0];
const DY: [i32; 4] = [0, 1, 0, -1];
const OUTPUT: [&'static str; 4] = ["RIGHT", "DOWN", "LEFT", "UP"];

const MAX_PLAYER_NUM: i32 = 4;
const COL: i32 = 30;
const ROW: i32 = 20;

構造体

 参考記事はJavaなので,Playerクラスの中にNodeクラスがあり,それぞれメソッドを持つという形になっている.

 Rustはオブジェクト指向ではなく,構造体を書き,それが関数を持つ(言い方正しいかな?)形になる.

というわけで次のような3つの構造体を作った.

  • Player : プレイヤーの状態を持つ
  • Node : あるターンのゲームの状態を持つ.前のターンのNodeへのリンクと,人数分のPlayer,今の状態のスコア,出力を持つ.
  • Game : ゲームの状態を持つ.最新のNodeへのリンクと,ゲームの参加者n,自分の番号pを持つ.

 今回,NodeのリンクはRcで持つようにした.Rc, RefCellについてはこの辺が参考になった.

mutなスマポ (BoxとかRcとかCellとか) - Qiita

#rustlang における構造体のmutabilityと`Cell/RefCell` - snyk_s log

 また,RefとかBoxとかRcとかRefCellとかわけわかめだったので Learning Rust With Entirely Too Many Linked Lists を写経した.Rustで色々なリストを実装していて,このへんのやつって実際こう使うのかーってわかって良かった.

type Link = Option<Rc<RefCell<Node>>>;

struct Game {
    head: Link,
    n: usize,
    p: usize,
}

#[derive(Eq, PartialEq, Clone)]
struct Node {
    parent: Link,
    score: i32,
    players: Vec<Player>,
    output: String,
}

#[derive(Eq, PartialEq, Clone)]
struct Player {
    x0: i32,
    y0: i32,
    x1: i32,
    y1: i32,
    locked_field: [[bool; ROW as usize]; COL as usize],
}

main

 というわけでこの3つの構造体をもとに関数を実装していった.まずはmain().

fn main() {
    let mut game = Game::new();

    loop {
        game.input();
        let ans = game.search();
        println!("{}", ans.output);
    }
}

new

 次に,各構造体のnew()関数.Nodeは,親が与えられた場合はその情報をクローンしたものを作成するようにした.情報の追加,変更は作成した後に行う.

impl Game {
    fn new() -> Self {
        Game {
            head: None,
            n: 0,
            p: 0,
        }
    }
}

impl Node {
    fn new(parent: Option<Rc<RefCell<Self>>>) -> Rc<RefCell<Self>> {
        if let Some(parent) = parent {
            // create child
            Rc::new(RefCell::new(Node {
                parent: Some(parent.clone()),
                score: parent.borrow().score,
                players: parent.borrow().players.clone(),
                output: String::new(),
            }))
        } else {
            // create the first
            Rc::new(RefCell::new(Node {
                parent: None,
                score: 0,
                players: vec![],
                output: String::new(),
            }))
        }
    }
}

impl Player {
    fn new() -> Self {
        Player {
            x0: -1, y0: -1, x1: -1, y1: -1,
            locked_field: [[false; ROW as usize]; COL as usize]
        }
    }
}

input

 入力を取得し,情報を更新する.初回のみゲームの情報と最初のNodeを作り,以後は前ターンのNodeを親としてnew_nodeを作った後にPlayer情報を更新するようにした.最後にGame::headをnew_nodeに更新する.

impl Game {
    fn input(&mut self) {
        let new_node = Node::new(self.head.clone());

        // input game data
        let mut input_line = String::new();
        io::stdin().read_line(&mut input_line).unwrap();
        if self.head.is_none() {
            // initialize game data & players
            let inputs = input_line.split(" ").collect::<Vec<_>>();
            self.n = parse_input!(inputs[0], usize);
            self.p = parse_input!(inputs[1], usize);
            new_node.borrow_mut().players = vec![Player::new(); self.n];
        }

        // input player data
        for i in 0..self.n {
            let mut input_line = String::new();
            io::stdin().read_line(&mut input_line).unwrap();
            let inputs = input_line.split(" ").collect::<Vec<_>>();

            let ref mut player = new_node.borrow_mut().players[i];
            player.x0 = parse_input!(inputs[0], i32);
            player.y0 = parse_input!(inputs[1], i32);
            player.x1 = parse_input!(inputs[2], i32);
            player.y1 = parse_input!(inputs[3], i32);
            player.locked_field[player.x0 as usize][player.y0 as usize] = true;
            player.locked_field[player.x1 as usize][player.y1 as usize] = true;
        }

        self.head = Some(new_node);
    }
}

search

 探索は, std::collections::BinaryHeap - Rust を利用して行う.処理内容は参考記事と同じになっている(たぶん)ので割愛.

 CodingameのRustバージョンが1.8.0で,RefCellにOrd,PartialOrdが実装されていない(1.10.0から)ためBinaryHeapに入れるとエラーが出る.そこで,Rc::try_unwrap(), RefCell::into_inner()を利用してRcとRefCellをはがし,Nodeを直接突っ込む.(この次のAIでは結局自分でOrd, PartialOrdを書いた)

impl Game {
    fn search(&self) -> Node {
        let mut heap = BinaryHeap::new();
        for i in 0..DX.len() {
            let next_node = Node::new(self.head.clone());
            next_node.borrow_mut().players[self.p].x1 += DX[i];
            next_node.borrow_mut().players[self.p].y1 += DY[i];
            next_node.borrow_mut().output = OUTPUT[i].to_string();
            let score = next_node.borrow().eval_player(self.p);
            next_node.borrow_mut().score += score;
            // because RefCell at 1.8.0 does not implemented Ord, push Node
            heap.push(Rc::try_unwrap(next_node).ok().unwrap().into_inner());
        }
        heap.pop().unwrap()
    }
}

eval

 評価についても参考記事と同じ.ここは割とサクッと書けた.

impl Node {
    fn eval_player(&self, p: usize) -> i32 {
        let x = self.players[p].x1;
        let y = self.players[p].y1;
        if self.players[p].can_move(x, y) {
            0
        } else {
            -1
        }
    }
}
impl Player {
    fn can_move(&self, x: i32, y: i32) -> bool {
        if 0 <= x && x < COL && 0 <= y && y < ROW && !self.locked_field[x as usize][y as usize] {
            true
        } else {
            false
        }
    }
}

最終コード

use std::io;
use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::rc::Rc;
use std::cell::RefCell;

macro_rules! print_err {
    ($($arg:tt)*) => (
        {
            use std::io::Write;
            writeln!(&mut ::std::io::stderr(), $($arg)*).ok();
        }
    )
}

macro_rules! parse_input {
    ($x:expr, $t:ident) => ($x.trim().parse::<$t>().unwrap());
}

const DX: [i32; 4] = [1, 0, -1, 0];
const DY: [i32; 4] = [0, 1, 0, -1];
const OUTPUT: [&'static str; 4] = ["RIGHT", "DOWN", "LEFT", "UP"];

// const MAX_PLAYER_NUM: i32 = 4;
const COL: i32 = 30;
const ROW: i32 = 20;

type Link = Option<Rc<RefCell<Node>>>;

struct Game {
    head: Link,
    n: usize,
    p: usize,
}

#[derive(Eq, PartialEq, Clone)]
struct Node {
    parent: Link,
    score: i32,
    players: Vec<Player>,
    output: String,
}

#[derive(Eq, PartialEq, Clone)]
struct Player {
    x0: i32,
    y0: i32,
    x1: i32,
    y1: i32,
    locked_field: [[bool; ROW as usize]; COL as usize],
}

impl Ord for Node {
    fn cmp(&self, other: &Self) -> Ordering {
        self.score.cmp(&other.score)
    }
}

impl PartialOrd for Node {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.score.cmp(&other.score))
    }
}

impl Game {
    fn new() -> Self {
        Game {
            head: None,
            n: 0,
            p: 0,
        }
    }

    fn input(&mut self) {
        let new_node = Node::new(self.head.clone());

        // input game data
        let mut input_line = String::new();
        io::stdin().read_line(&mut input_line).unwrap();
        if self.head.is_none() {
            // initialize game data & players
            let inputs = input_line.split(" ").collect::<Vec<_>>();
            self.n = parse_input!(inputs[0], usize);
            self.p = parse_input!(inputs[1], usize);
            new_node.borrow_mut().players = vec![Player::new(); self.n];
        }

        // input player data
        for i in 0..self.n {
            let mut input_line = String::new();
            io::stdin().read_line(&mut input_line).unwrap();
            let inputs = input_line.split(" ").collect::<Vec<_>>();

            let ref mut player = new_node.borrow_mut().players[i];
            player.x0 = parse_input!(inputs[0], i32);
            player.y0 = parse_input!(inputs[1], i32);
            player.x1 = parse_input!(inputs[2], i32);
            player.y1 = parse_input!(inputs[3], i32);
            player.locked_field[player.x0 as usize][player.y0 as usize] = true;
            player.locked_field[player.x1 as usize][player.y1 as usize] = true;
        }

        self.head = Some(new_node);
    }

    fn search(&self) -> Node {
        let mut heap = BinaryHeap::new();
        for i in 0..DX.len() {
            let next_node = Node::new(self.head.clone());
            next_node.borrow_mut().players[self.p].x1 += DX[i];
            next_node.borrow_mut().players[self.p].y1 += DY[i];
            next_node.borrow_mut().output = OUTPUT[i].to_string();
            let score = next_node.borrow().eval_player(self.p);
            next_node.borrow_mut().score += score;
            // because RefCell at 1.8.0 does not implemented Ord, push Node
            heap.push(Rc::try_unwrap(next_node).ok().unwrap().into_inner());
        }
        heap.pop().unwrap()
    }
}

impl Node {
    fn new(parent: Option<Rc<RefCell<Self>>>) -> Rc<RefCell<Self>> {
        if let Some(parent) = parent {
            // create child
            Rc::new(RefCell::new(Node {
                parent: Some(parent.clone()),
                score: parent.borrow().score,
                players: parent.borrow().players.clone(),
                output: String::new(),
            }))
        } else {
            // create the first
            Rc::new(RefCell::new(Node {
                parent: None,
                score: 0,
                players: vec![],
                output: String::new(),
            }))
        }
    }

    fn eval_player(&self, p: usize) -> i32 {
        let x = self.players[p].x1;
        let y = self.players[p].y1;
        if self.players[p].can_move(x, y) {
            0
        } else {
            -1
        }
    }
}

impl Player {
    fn new() -> Self {
        Player {
            x0: -1, y0: -1, x1: -1, y1: -1,
            locked_field: [[false; ROW as usize]; COL as usize]
        }
    }

    fn can_move(&self, x: i32, y: i32) -> bool {
        if 0 <= x && x < COL && 0 <= y && y < ROW && !self.locked_field[x as usize][y as usize] {
            true
        } else {
            false
        }
    }
}


fn main() {
    let mut game = Game::new();

    loop {
        game.input();
        let ans = game.search();
        println!("{}", ans.output);
    }
}

というわけで

 とりあえず自殺しないAIを書いた.記事が思ったより長くなったので次のAI(先読み,Chokudaiサーチ)は別に書く.

 Rustを書いていると,ここでは参照だけもらってーとか,いまこれをborrow_mut()したらだめとか,この変数はここで消えるからこれの参照持ったままじゃだめだとか,色々注意しながら書かなきゃいけないので疲れる面白い.変な構造にしてるとまともにコード書き進められなかったりして,ゆるふわな言語になれていると辛い面白い.逆にRustに慣れるとC/C++とか書いているときにメモリリークとか気がつきやすくなったりするかも.

 ただ,色々仕組みがあってすごい安全なのはわかるんだけど,初心者だとコンパイラに注意されても何が悪いのかわからなかったり,じゃあどう書けば・何を使えばいいんだよってのでハマるので,そのへんもっと情報が豊かになったらいいなーと思った.

 でもやっぱりなんとなく書いてて面白いです.

情報系の大学生の自分が2016年に読んだ本のまとめ

はじめに

 2016年に読んだ本を備忘録として書いていく.2015年の記事はこれ.

brookbach.hatenablog.com

 以下のようにざっくりとしたジャンルにわけて本の簡単な感想を書く.

  • 知恵・知識
  • 情報系
  • 小説
  • 漫画

知恵・知識

アイデアのつくり方

アイデアのつくり方

 旅行に行く飛行機の中で2時間ぐらいで読めた.情報収集→整理→思考→ひらめき→出力っていう流れを踏むこと,言われてみれば確かにって感じだけど実践するのは難しい.

 あと,ひらめいたものを実際に作ると思ってたよりしょぼくて落胆するってのはあるあるだと思った.

理科系の作文技術 (中公新書 (624))

理科系の作文技術 (中公新書 (624))

 論文とかの文章の書き方についての本.最初に出版されたのが結構前(1970年ぐらい)なので,ちょっと情報が古いかも.でも基本は変わってない.

 ちゃんと文章を書き始める前に読んだのであんまり内容を覚えてない,今読んだら違う感想持つかも

 標識,説明書,ポスターなど多くの人が見るものに対して,どういう形のものがわかりやすいのか,わかりにくいものはなぜわかりにくいのかをきちんと解説している.外野からみたらすごいわかりにくくても作ってる人にはそれわかんないんだよね

本を読む本 (講談社学術文庫)

本を読む本 (講談社学術文庫)

 何か読むもの,特に本を対象とした読み方についての本.流し読み,点検読書,分析読書,シントピカル読書っていう流れに沿って本の読み方を解説しており,各段階において,どういうことに意識しながら読んでいけば良いのかが書いてある.

 無意識にやっていることもあったけど,この本に書いてある手法は難しい本,論文とかを読むときの助けになると思う.また,良い読み方って良い書き方でもあるんだなーと思った.  定期的に本を読むようになっていて良いタイミングで読めた.それと,小説については世界に入りこむことが大事って書いてあってちょっと意外だった.

ご冗談でしょう、ファインマンさん〈上〉 (岩波現代文庫)

ご冗談でしょう、ファインマンさん〈上〉 (岩波現代文庫)

 ファインマンという物理学者のエッセイ本で,難しい物理の話ではなく,この人の自伝的な本.歴史的な話とか,知識に関する話とかが書いてあって読んでてためになるし面白かった.通しての感想は,この人知的好奇心がすごいなーと思った.

 行動経済学の本.机の上で考えてみると不合理なのに,実験してみるとなぜか人々はそう動いてしまうんだよねって話.無料でアメを配るのと1円で配るのとでは人が持って行く数が大きく違ったり,冷静なときに予想する自分が興奮しているときの状態と,実際に自分が興奮しているときの状態は全然違ったりとか,あー言われてみればそういうことあるわっていう話がたくさん書いてある.

 読んだ後,ものを見る視点が少し変わった気がする.それが幸せかどうかはわからない.

ゼロからトースターを作ってみた結果 (新潮文庫)

ゼロからトースターを作ってみた結果 (新潮文庫)

 イギリスの大学生が,ゼロからトースターを作る話.鉄とか銅を鉱石取ってくるところから始めて面白い.ゼロからだと,プラスチックの方が鉄より作るの難しいっていうのは値段と比例してなくて面白いと思った(civilizationでもプラスチックの方が金属より後だしね).身の回りにあるものを実際に作ってみろって言われたら難しいんだなーと.

パパラギ はじめて文明を見た南海の酋長ツイアビの演説集 (SB文庫)

パパラギ はじめて文明を見た南海の酋長ツイアビの演説集 (SB文庫)

  • 作者: エーリッヒ・ショイルマン,Erich Scheurmann,岡崎照男
  • 出版社/メーカー: SBクリエイティブ
  • 発売日: 2009/02/18
  • メディア: 文庫
  • 購入: 1人 クリック: 14回
  • この商品を含むブログ (11件) を見る

 南海の酋長が,文明社会(西洋文化)に触れて考えたことを書いた本.物,お金,家とかを持って自慢げにしてるけどおまえらそんなの間違ってるぜ,っていっている.この人の言っていることはすごいわかるし,この人の過ごしているような環境で生きていた方が幸せなのではとは思う.でも,ここまで進んでしまった今の社会ではそれはもう難しいんだよなぁと思った.

情報系

コンピュータネットワークセキュリティ

コンピュータネットワークセキュリティ

 学科の授業の教科書として購入,主にネットワークにおいて,どんな攻撃があるのか,それに対してどんな防御方法があるのかをさらっと解説している.教科書っぽい感じはあるけど割と読みやすかった.

 ちょくだいさんというAtcoderの社長が何年か前に書いた競技プログラミングの入門本.競プロ強くなりたいなーと思ってとっかかりとして読んだ.強くなりたい

 競プロは本読むより問題解いた方が強くなるだろうなーと思ってる.これを読むよりAtCoderのABCを解きつつ解説読んだ方がためになるかも

UNIXという考え方―その設計思想と哲学

UNIXという考え方―その設計思想と哲学

 UNIXについて,どういう考え方でこのOSができているのか,いくつかのポイントに分けて簡潔にまとまっている.自分がちょっとしたプログラムを書くときとかに参考になることが書かれていて,読んでいてためになった.

[24時間365日] サーバ/インフラを支える技術 ?スケーラビリティ、ハイパフォーマンス、省力運用 (WEB+DB PRESS plusシリーズ)

[24時間365日] サーバ/インフラを支える技術 ?スケーラビリティ、ハイパフォーマンス、省力運用 (WEB+DB PRESS plusシリーズ)

nginx実践入門 (WEB+DB PRESS plus)

nginx実践入門 (WEB+DB PRESS plus)

Redis入門 インメモリKVSによる高速データ管理

Redis入門 インメモリKVSによる高速データ管理

 ISUCON本戦に参加するにあたり読んだ本たち.この中では大規模サービス技術入門が一番ためになった.どうしても大規模なアプリの,しかもインフラに触る機会って学生だとほとんどないから,ISUCONに参加したり本を読んだりしたことでなんとなく理解できて良かった.働くようになって実際にそういうのを作るようになったらまた読みたい.

暗号技術入門 第3版 秘密の国のアリス

暗号技術入門 第3版 秘密の国のアリス

 シーザー暗号から,楕円曲線暗号まで,それがどういうアルゴリズムで動いていて,どう作るのか,どう破られるのかしっかり書いてある本.サイモン・シン先生が書いた暗号解読は歴史,物語的な面に重点をおいていたが,この本は技術面に重点をおいている.両方読むと知識がマージされていいと思った.

 ポール・グレアムさんが書いたハッカーに関するエッセイ集.ハッカーとしての心構えとか,ソフトに関すること,デザインに関することとか幅広い分野の話が書いてあって面白い.熱いLisp推し. ここ http://practical-scheme.net/wiliki/wiliki.cgi?naoya_t%3Aポール・グレアムのエッセイと和訳一覧 にこの人の書いたエッセイの日本語訳がまとまっているのでこれを読んで主も白かったらこの本も面白いと思う.

小説

カラマーゾフの兄弟〈上〉 (新潮文庫)

カラマーゾフの兄弟〈上〉 (新潮文庫)

 去年の暮れぐらいから読み始めて3月ぐらいに読み終わった.出てくる登場人物がみな一癖あるんだけど,ああこういう人いるなぁって感じの人ばかりで面白かった.自分は次男に一番共感したかなー.

車輪の下 (新潮文庫)

車輪の下 (新潮文庫)

「詩人になれないのなら,何にもなりたくない」とかいう超名言を残したヘルマンヘッセが書いた半自伝小説.これは主人公にシンパシーを感じて,もし自分も大学を退学してたらこんなふうになってたかなーと思った.

 こういう歴史の教科書にでてくるような,名前は知ってるけど読んだことが無いっていう本を少しづつ読んでいきたいと思っている.

青の炎 (角川文庫)

青の炎 (角川文庫)

 主人公は名門高校に通っていて,美人な母と妹,居候同然の養父と一緒に住んでいる.ミステリーものなんだけど心理描写多め.嫌な父親から母と妹を守るために,青年の熱い思いがどんどん燃え上がっていく.そして……

空飛ぶ馬 (創元推理文庫―現代日本推理小説叢書)

空飛ぶ馬 (創元推理文庫―現代日本推理小説叢書)

 いわゆる人の死なない推理小説.女子大生の主人公がなんかしら面白い題材を見つけて,それを知り合いの落語家に話に行く,という流れで短編集みたいになっている.平和でまったりした雰囲気だけど推理要素もちゃんとあって面白い.

 途中まで読んで何年か積んでたんだけど今年読んだ.なんで積んでたのかなーと考えたんだけど,推理自体は面白いんだけど主人公の自分語りがちょっと苦手だったからだと思う.

極大射程 上 (扶桑社ミステリー)

極大射程 上 (扶桑社ミステリー)

 凄腕の孤独なスナイパーが,謎の組織から謎の依頼をされ,気がついたら暗殺容疑で指名手配されたので謎を解明しつつ復讐していく話.ハードボイルド・・・って感想を持った.主人公が罠をかいくぐりつつかっこよく立ち向かっていって面白い.

 映画も見たんだけど,原作では頼りになる相棒がへっぽこになってたり,主人公が銃撃と爆発で無双していたりと,ミステリー&ハードボイルドな原作が完全にアメリカンアクション映画になっていて完全に別物.でも嫌いじゃない

推定無罪〈上〉 (文春文庫)

推定無罪〈上〉 (文春文庫)

 弁護士の主人公が殺人の容疑を掛けられて,しかも被害者は実は主人公の浮気相手で……という話.雰囲気大人向け逆転裁判って感じで面白かった.推理物は面白いんだけど感想があまり書けなくてつらい,読んだ人同士で話したい

すばらしい新世界 (講談社文庫)

すばらしい新世界 (講談社文庫)

 完全なる階層社会,高度な機械文明のなかで生きる人の話を書いたディストピア小説.ディストピア小説のはずなんだけど,なんかこの小説の世界も悪くない,むしろ今よりましな部分もあるんじゃないかなとか思ってしまった.ただそれは階層の上の方の意見なのかな

漫画

 今年読んだ(読みはじめた)漫画.個々に感想を書くのがめんどかったので,ざっくりとしたジャンルにわけてジャンル毎に簡単なコメントを書く.

読んだ中では,

が大ヒットだった.

登場人物が男女問わずかわいい・いやされる

恋は光 1 (ヤングジャンプコミックスDIGITAL)

煩悩寺 1<煩悩寺> (コミックフラッパー)

金の彼女 銀の彼女(1) (月刊少年マガジンコミックス)

からかい上手の高木さん 1 (ゲッサン少年サンデーコミックススペシャル)

ストレッチ(1) (ビッグコミックススペシャル)

亜人ちゃんは語りたい(1) (ヤングマガジンコミックス)

ごちそうは黄昏の帰り道 1 (マーガレットコミックスDIGITAL)

かぐや様は告らせたい?天才たちの恋愛頭脳戦? 1 (ヤングジャンプコミックスDIGITAL)

シャーリー 1巻<シャーリー> (ビームコミックス(ハルタ))

エマ 1巻<エマ> (ビームコミックス(ハルタ))

乙嫁語り 1巻<乙嫁語り> (ビームコミックス(ハルタ))

つぐもも : 1 (アクションコミックス)

恋は雨上がりのように(1) (ビッグコミックス)

甘々と稲妻(1) (アフタヌーンコミックス)

ふらいんぐうぃっち(1) (週刊少年マガジンコミックス)

NEW GAME! 1巻 (まんがタイムKRコミックス)

 あーこの人たち本当にかわいいなーとか思いながら読んでいた.かわいいなーとか思って読んでるけど,ちゃんと内容も濃くて面白いのが多い.ひとまとめにしてしまったけどそれぞれ違った良さがあるので片っ端から読むと良いです

おっさん・おねえさんがかっこいい

ディメンション W 1巻 (デジタル版ヤングガンガンコミックスSUPER)

無限の住人(1) (アフタヌーンコミックス)

新装版 無限の住人(1) (KCデラックス アフタヌーン)

最強伝説 黒沢 1

波よ聞いてくれ(1) (アフタヌーンコミックス)

 あーこのおっさん本当にかっこいいなーとか思いながら読んでいた.かっこいいなーとか思って(ry

SF・IT・近未来

僕だけがいない街(1)<僕だけがいない街> (角川コミックス・エース)

惑星のさみだれ (1) (ヤングキングコミックス)

レベルE 上 (ジャンプコミックスDIGITAL)

AIの遺電子 1 (少年チャンピオン・コミックス)

王様達のヴァイキング 1 (ビッグコミックス)

SFは小説,漫画どちらも好き.

そのほか

聲の形(1) (週刊少年マガジンコミックス)

幽☆遊☆白書―完全版 (1) (ジャンプ・コミックス)

RAVE(1) (週刊少年マガジンコミックス)

おやすみカラスまた来てね。 1 (ビッグコミックス)

 どれも面白かったです(適当).

来年に向けて

 あいかわらず「本 おすすめ」とかで出てきた本を中心に読んでる.でも本ってデータ構造的には森になってて,一つの本を読むとそれに関連した本がたくさん出てくるので少しずつ色んな本に手を出していきたい.

 本はAmazonで中古で紙の本を買うことが多い.あとはBookoffに行って買ったりとか.逆に漫画はKindleで買ったり漫画喫茶で一気読みすることが多い.

 1年で100冊本を読む(漫画除く)目標だったんだけど,結局30冊ぐらいしか読んでない. 1~3月,10~12月はコンスタントに読めてたんだけど,4~6は院試,7~9は研究とかバイトとかで余裕が無かった.言い訳

 1年で100冊の目標は結構難しいので2017年はとりあえず60冊を目標にする. それと,本を読んだときの感想とか半年ぐらいたつと忘れるので都度ツイートしようかなと思った.

読みたい本のリスト

http://amzn.asia/7Ao7Szh

ISUCON6本戦に参加してきたよ

10月22日に行われたISUCON6本戦の参加記.

f:id:brookbach:20161031223735j:plain

結果

惨敗

スタートラインに立って,一歩踏み出したぐらいで終わった感じ.

初期実装より低い点,というか最終的にはFailだった.

 

本番の流れ

じっさいどんな感じでやっていったかを書きます.時系列はわりと適当.

10:00~12:00 起動

予選と同様に1クリックでAzureにデプロイ,アプリの起動確認・・・の予定が,起動確認に2時間かかる.

 

レギュレーションに書いてあった,

アプリケーションは各サーバの port 443 を経由してアクセスできます。

を正しく読み取れず,http://[IP]:443でアクセスしようとする→できないを繰り返す.

 

他のチームがデプロイに失敗してたりしたので,再デプロイしてみたりするもできず

 

2時間ぐらいたって,mayokoが「https」でアクセスしたらいけることに気づく

 

12:00~13:00 環境の確認

自分も含めてチームメンバー全員,「docker?聞いたことはあるような...?」「react?知らない子ですね?」という状態だった.

「Docker 入門」「React とは」「React チュートリアル

とかでググりながら何がどうなってるのかを確認する.

 

14:00~15:00 自分:dockerをやめる,他の二人:アプリのコードを見て明らかに悪いところの修正

というわけで実際に環境を見ていろいろ調べるも,どこに設定ファイルがあるかも分からないし,どうやってそれぞれの環境にアクセスするのかもわからずという状態.

 

ググってみたりwebapp/以下を見て,Dockerfileなどに書いてある処理を順に実行していったら環境が作成できそうだったので,思い切ってDockerをやめようと決断し,環境を作成し始める.

また,その間にメンバーにアプリの分析とかを頼む.

 

15:00~16:00 細々と修正

15:00過ぎぐらいにDockerから環境をとりだし終わりアプリの起動に成功する.

そこから自分もアプリを見て,構成とかボトルネックを調べる.

 

しかし,予選と違って明らかなボトルネックとなる場所がRuby側に無く途方に暮れる.

 

絵をリアルタイムで共有するところも,なんかアホなことやってるのは分かるんだけどそれをどう修正したらいいのか分からず,変えるとしたら全体的に変えなきゃいけない雰囲気を感じるも実装するには時間が足りなさそうと思ったり

 

それでもメンバーがちょこちょこ修正してくれたのをマージして少しスコア上がる.

 

この時間がISUCONやってるなーって感じがして,少しだけどスコアも上がって一番楽しかった.

 

細々としたところを修正した後で,アプリはメンバーに任せてサーバーを複数台に分散しようとする.

 

16:00~18:00 複数台構成にする→単機に戻す→Fail

複雑な構成にするスキルも時間も無かったので,少し考えてDB1台,Webアプリ3台,nginx1台の構成にしようと決める.

 

DBのリモートアクセスとかを手探りで設定したり,他のサーバーにアプリの実行環境を作成したり.

 

なんとか作成できたので,ベンチを回してみたところ,初期実装よりスコアが下がる.

この時点で17時ぐらい. 

時間も無く,しょうがないので単機にしようと決め,再起動しても動くようにして再起動試験を行うが,アプリが起動しない.

 

systemctlでの自動起動がうまくいってなかったりしたのでinit.d/以下に起動スクリプトおいて無理矢理起動するようにしたら今度は静的ファイルが無かったりしてベンチに落ちて途方に暮れる.

 

結局時間内に問題を解決できずFail.

 

反省

こんな感じで本番は終わった.もうちょい具体的に,いくつか色々考えたこととか反省点とかを箇条書き的に書いていく.

起動確認 

443=https, 冷静に考えてみたらそれはそうって感じだけど本番中はまったく気づかなかった.

懇親会のときにレギュレーションが不親切だったかも,とは言われたがWeb詳しくないとはいっても常識的に考えてそうだよなーと思ったので気づかなかった僕たちがアホだった.

最後の方になって,あと2時間あったらなーと死ぬほど思ったのでここで時間をロスしたのが非常に辛かった.

 

docker分離

Dockerを調べながらそのまま行くべきだったのか,分離して正解だったのかはわからないが,他にも分離しているチームはあったので悪くはなかったはず.

 

他のチームは,MySQLだけ取り出してたりとかしているところもあり,なるほどなーと思った.

 

DockerもReactも全く知らない状態で不完全とはいえアプリを取り出して動かせたところはだれかにほめてほしい

 

複数台構成

やろうとしてた構成は悪くなかったはず.

単機より点数出なかったのは,最適化が全然されてなかったせいで帯域とかがボトルネックになってしまったのかなーと思っている.

ここの分析をするべきだったが,それができなかったのを後悔している.

 

時間も経験も無いなかで一応複数台構成で不完全とはいえ動かせたところはだれかにほめてほしい

 

再起動

Failした直接の原因は,bundle.jsが無かったからだった.

Dockerからアプリを分離した時点で一回再起動してちゃんと動くか確認するべきだった.

 

役割分担

チームのスキル的に,自分がアプリを見つつインフラもやり,二人にはアプリの修正をお願いして,やってもらったのを確認しながらマージしていくって感じだった.

このスタイルで予選はなんとかなったが,本戦はそれじゃどうにもならなかった.

 

自分が諸々修正している間にアプリを見てもらったけど,結果的に他の二人に(たぶん)暇をさせてしまっていた.*1

 

せめて誰かDocker, Reactのどっちか使ったことあればなーとか,せっかく3人チームだったのにうまく動くことができずもったいなかったなーとか色々後悔はあるが,経験,実力的に今回の問題だとこうなるのは必然だったかなーとも思う.

 

その他諸々 

・ローカルの開発環境を作らず,本番をそのままいじっていた

・過去問をもっとやっておくべきだった

・もっとwebapp以下のコードをじっくり考察するべきだった 

・チーム練習ちゃんとやっておくべきだったなー

・発想自体はあってるんだけど実装に結びつかないみたいなのが多々あった

 

まとめ

というわけで,今回のISUCON6本戦は非常に悔しい結果に終わりました.

 

それでも,初参加で予選を突破し,本戦に参加できて,普段なかなかいじれないような構成のアプリをいじれて,非常に勉強になったし楽しかった.

 

来年も,もしあればぜひまた参加して賞金をもらいたいです.*2

 

謝辞

最後になりますが,ISUCON運営企業,スタッフのみなさん,お疲れ様でした&ありがとうございました.とても楽しいイベントでした.

 

それと,急な誘いと無茶ぶりにもかかわらず一緒に参加してくれたmayokoとoshiumiにもめっちゃ感謝です.ありがとうございました.

 

*1:お絵かきアプリだったので,リンゴとかみかんとか書いてた

*2:oshiumiが就職してしまうので新しくチームメンバー探さなきゃ・・・?

ISUCON6予選に参加して学生枠7位で通過しました

きっかけ

参加しようと思ったきっかけは8月の終わりに見たpixivの記事

インターンで知り合った仲間と一緒にISUCONを戦ってみませんか? - pixiv inside

インターン生向けのISUCON CM - Qiita

 

 去年, 一昨年ぐらいからこの時期にISUCONやってるのは知ってて, でも自分のスキルじゃなぁとか一緒に参加してくれる人がいないなーとか考えてて面白そうだと思いつつスルーしてた

 

 今年は上の記事を見て, せっかくだから参加しようと思い, 誰か誘ってみることにした.  去年ぐらいからいくつかインターンにいってて, 一番長く(2週間)やったインターンのメンバーが今もtwitterで交流あったので, 思い切って誘ってみた

 

 二人とも, 興味はあるけどwebは全然詳しくないよーとのことだったので, なんとかなるよとか適当なことをいって参加決定した.

 ちなみにみんな学年は同じだが大学はバラバラ, 得意分野もmayokoが競プロでoshiumiがゲームプログラムで自分が広く浅くやってたりでバラバラ

前日まで

 上記の感じから, ミドルウェアとかデータベースまわりは自分がやり, アプリの遅い部分についてみんなで考えながらやっていくのが良いと思った. また, 相談して言語はRubyで行くことにした

 

ので, ちょろちょろと測定の仕方とかを去年の過去問とかブログから情報収集した

 

 学生枠なら, 必要最低限の設定と運営側が用意した一番のボトルネックを改善できれば通過できるんじゃ無いかなーと見積もっていて, これは実際正解だった

 

 1週間前ぐらいからmatsuuさんが作成Azureに過去問をワンクリックでデプロイする神ツールを使って練習してた

github.com

 

 他のチームメンバーはRubyは軽く使ったことあるぐらいだったので, App.rbのコードリーディングを行い, とりあえずどんな感じかわかるようにした.

 

 3日前ぐらいから, 測定ツールの結果から改善するところをみつけて, 解説を見ながらどうすればいいか理解する練習をした.

 

前日はpixivのisuconを立てて練習した.  ボトルネック見つける→どう改善すればいいか考える, っていう流れを練習できたので良かった

本番

 8時半ぐらいにヒカリエに集合して軽く打ち合わせ, 大体の流れとか, インターンの景品もらってない話とか, TGSどうだったかとかそんな話をしてた

 

10時~

Azureにデプロイ, 初期設定を行う

デプロイ後, 初期設定を行い, その間にメンバーにアプリを見ていてもらった

なんか2つあるっぽいけどなにこれ?どこ見れば良いの?みたいな声が飛んでいた

とりあえずベンチを通し, 0点だったけどPASSはしてたのでたぶんどっかが遅すぎて点数とれてないんだろうなと思った

 

その後, 諸々のログ設定を行う.  練習はしといたんだけど, スロークエリログが生成されなかったりstackprofのログが生成されなかったりして詰まってた. 

 

11時~

11:20分頃にnginxのアクセスログとstackprofがとれたのでSlackに流す, ルートページがめちゃ重でやばいと認識, さらにhtmlifyが9割ぐらい時間消費してたのでここやーと見つける

 

その後, コードを一通りながめて何やってるか理解, SELECT * 消しとかクエリ大文字にしたりとかをチームメンバーに頼み, nginxの静的ファイル配信をちゃちゃっと書く. この時点でベンチ回したら正の点が出て一安心した

 

htmlifyどうするよみたいなのを相談し, とりあえず何やってるか理解しようと思いpryを使って一行ずつ追っていった

 

キーワード見つけてハッシュしてリンクに置き換えをやっているので, リンクに置き換えた後のテキストをカラムに保存してしまえばいいのでは, となり実装を開始

 

12時 ~

12:40分頃に, 置換したテキストをカラムに保存する実装が完成, デプロイ

ベンチを回してみたところ, 12000点ぐらい出て暫定五位になった

方針間違って無くて安心, ここでもう終わりで良いよとかみんなでいってた

 

13時~

スロークエリログは結局あきらめてmyprofilerで簡単なログを取ることにした, データベース小さかったので今回はあんまりデータベースメインじゃ無いんだなとか思ってこれ以上ここで頑張るのをやめた

 

キーワードが追加されたときに, 他のエントリの置換されたテキストを更新しなきゃいけないことに気づく

どうするか相談して, 置換されたときのエントリの総数を保存しておくことにする

 

14時~

とりあえず15000点ぐらいで安定したので, タスクを振り分けながら自分は諸々のconfファイルを拾ってきた秘伝のタレで置き換える作業を始める

 

その後ベンチを回してみたところ, ある時点でユニコーンワーカーが落ちてFAILするという現象にぶち当たる.

 

助けてくれーと叫びつつ原因を探り, ユニコーンUnix domain socketで接続しているのが原因と特定, いろいろいじってみても治らなかったのであきらめてホストとポートで接続するように切り戻し

 

16時〜

htmlifyについて, 自分たちの実装だと初期状態で置換されたテキストを生成する必要があったので, /initializeでそれを行う処理を書いたが5秒以内に終わらずあきらめる

 

17時~

再起動テストを行い, 18000点ぐらい出るのを確認して引き上げ作業を行う

 

ログの設定消し忘れてアプリが落ちたりして, 助けてくれーと叫びながら5分前ぐらいになんとか引き上げ終わる

 

置換されたテキストを生成するため, 終了するまでブラウザからページの更新を行う

 

 

反省点

・htmlifyの解決方法についてもっと考えるべきだった, 他のチームはキャッシュを利用したり, そもそもの置換アルゴリズムを変えていたりしていて, 自分たちのチームはその発想は無かった.

 

mysqlの設定がきちんとうまく反映されなかった. 結局あやふやなままスルーした.

 

・confファイルの設定で時間を取られすぎた, 想定では秘伝のタレをコピペして終わりにするつもりだったのでかなり焦った.  ここで時間を取られてhtmlifyに集中できなかったのが悔やまれる.

 

・合わせて, そのせいであまりタスクを抽出できず, 後半に他のメンバーがやることあんまり無い状態になってしまっていた. 

 

良かった点

・自分が詰まっている間に他のメンバーが諸々を改善してくれていた, チームプレイできて良かった

 

・つまったとき, 悩んだときに, ここは諦めようとか, 妥協しようとかの判断ができた.

 

・一応最低限の改善ができて, 予選を通過することができた. 2万点だと学生枠としてはギリギリかなーと思っていて, 結果が出たときは嬉しかった

 

感想

初めてのISUCONで, とても楽しかった. 予選通過できて良かった.

 

参加したいけど一緒に参加してくれる人がいないみたいなつぶやきは結構あって, その中で自分は一緒に参加してくれる人がいて良かった.

(興味はあるけど・・・みたいな人は多いので, 迷ってる人は勇気を出して誘ってみるといいと思う. 基本的に誘うより誘われたい人の方が多いし, スキルが足りなくても協力すればなんとかなると思う)

 

本戦はさらにグレードアップしたアプリに対して闘うことになると思う,

一ヶ月あるので, 今回の反省を活かし, 足りないところは勉強して, 練習して, 本戦ではもっと良い点が取れるようにしたい.  目指せ100万円

 

 

Sentabe:センサデータベースを作った

概要

ソースコードはこちら

GitHub - keigo-brook/Sentabe: Sensor data base

 

ネットワークに接続されたセンサか ら, 情報を定期的に取得するアプリケ ーションを作成した. また, 情報を取得する期間を, サーバー側で変更できるようにした.

 

アプリケーションのクラス構造は Figure 1 のようになっている.また, Figure 2にアプリケーションのフローを示す.

 

f:id:brookbach:20160715204400p:plain

Figure 1

 

f:id:brookbach:20160715204411p:plain

Figure 2

 

流れの説明

はじめに, Raspberry Pi にてデータを 取得し, それをサーバーに POST する. つぎに, POST リクエストを受け取った サーバーは, この情報を保存する.
さらに次回のデータを取得する時間 (between time)を計算する.
そして, HTTP レスポンスとともにこ の between time を Raspberry Pi に返す.

Between time を受け取った Raspberry Pi は, この情報を元に次にデ ータを取得する時間をセットする.

 

 

サーバサイドはRailsで構築, ViewにはAdminLTEを利用, 

クライアント側なんでもいいけどPythonでとりあえずこんな感じ

Sentabe/sentabe_client.py at master · keigo-brook/Sentabe · GitHub

で書いた

 

感想

作りたかったものに対して大きく堅く作りすぎた気がして少し反省している

もっと適当に作って良かった気がする

 

期間を変更するアルゴリズムについては, 現在は別スレッドを立てて時間が来たらそれを起動するようにしている

 

サーバー側は時間を送るだけなのでクライアント側でやり方変えることができる

 

HTTPで通信するので, 結局レスポンスを返さなければならなくて, だから時間を一緒に返しちゃっても良かったけど, UDPを使って送り続ける感じにもできるなーとか

 

センサ側から送るけど, サーバ側でセンサから取得する形にもできるかなとか

 

あと, いつものことながら, 作り終わると作ったものが小さく見えて本当にこれ使うかな?とか考えてしまったりとか

 

使いながら改良していこうと思う

 

修士に進むことになり, もうちょい技術的にスキルアップしたいなーと考えたので現在職探しをしている.  Railsはそこそこ使えると思うけど, ちょっと違うこともやってみたいなーとか考えている.