【python】xlsxwriterの使い方を丁寧に

はじめに

本記事では,xlsxwriterでエクセルに数値や文字列を書き込む方法を紹介します.また,文字列の一部を色付けする,セルを結合後に書き込む,リスト形式のデータを書き込む,列の幅を調整するようなことも記載しています.

目次

xlsxwriterとは

xlsxwriterは,エクセルファイル(.xlsx)への書き込みをサポートするpythonモジュールです.指定したセルに対して,数値や文字列を書き込むことができます.他にも,一般にエクセルで動作するような式の書き込みもできます.

この記事で紹介する内容を含む公式ドキュメント:

xlsxwriter.readthedocs.io

準備

モジュールのインポート

import xlsxwriter

もしエラーが出る場合は,

pip install xlsxwriter

でモジュールをインストールします.

エクセルファイルの読み込み

.Workbook(ファイル名)でファイルを読み込みます.この後何かしら処理をしますが,最後は必ず.close()で閉じてください.閉じないと,処理が反映されません.

存在しないファイルを開こうとした場合は,自動で新規作成されます.

import xlsxwriter

book = xlsxwriter.Workbook('sample.xlsx')
# 何かしらの処理
book.close()

シートの指定

エクセルファイルを読み込んだら,.get_worksheet_by_name(シート名)でシートを指定します.

import xlsxwriter

book = xlsxwriter.Workbook('sample.xlsx')
sheet = book.get_worksheet_by_name('Sheet1')
book.close()

もしシートを新規作成する場合は.add_worksheet()を使います.

import xlsxwriter

book = xlsxwriter.Workbook('sample.xlsx')
sheet = book.add_worksheet()
book.close()

値の書き込み

セルの指定方法

数値を書き込むにしろ,文字列を書き込むにしろ,書き込むセルを指定する必要があります.指定方法は2通りあるので,はじめに説明します.

  • セル名で指定する方法

    A1C5などのように,セル名を文字列として与えます.

  • 行番号,列番号で指定する方法

    0,0のように,セル名を2つの整数で与えます.最も左上のセルを0,0だと思って,二次元配列のインデックスと思えば良いです.例えば,A10,0であり,C54,2です.

何でも書き込める.write()

xlsxwriterには,各データ型に対応する書き込みメソッドが定義されています.例えば,数値を書き込むには.write_number(),文字列を書き込むには.write_string()を使います.他にも式やURLに対応するメソッドがあります.そして,これらを統合するようなメソッドとして.write()があります..write()は引数に対応する書き込みメソッドを呼んでくれるので,実質これだけ使えば事足ります.

以下のサンプルでは,数値,文字列,式を書き込んでいます.セルも2通りで指定しています.

import xlsxwriter

book = xlsxwriter.Workbook('sample.xlsx')
sheet = book.add_worksheet()
sheet.write('A1', 5) # 数値.
sheet.write(1, 0, '5') # 文字列.(1,0)=A2
sheet.write(2, 0, "=100+10.5") # 式.(2,0)=A3
book.close()

結果は以下の通りです.同じ5でも,数値と文字列が区別されて書き込まれていることが分かります.また,式も機能していて,計算結果が表示されています.

f:id:gotutiyan:20201025003405p:plain

文字列の一部を装飾する.write_rich_string()

文字列の一部を装飾するには,.write_rich_string()を使います.これにより,太字イタリック色付けなどを混ぜて書き込めます.

.write_rich_string()は,書き込みたい文字列をカンマ区切りで3つ以上与えられます.セルの情報を含めない引数が2つ以下の場合や,空文字列を書き込もうとするとエラーになります.(空文字列の書き込みは.write_blank()という専用の関数があるので,.write_blank(),もしくは.write()で書きこむのは問題ありませんが,.write_rich_string()ではサポートしていません.)

import xlsxwriter

book = xlsxwriter.Workbook('sample.xlsx')
sheet = book.add_worksheet()
# カンマ区切りで
sheet.write_rich_string(0, 0, 'bold', 'italic', 'color')
book.close()

ここで,.add_format()を用いて作成したフォーマット変数を,装飾したい文字列の直前に書きます.フォーマット変数は,例えば以下のように作成できます..

  • 太字:bold = book.add_format({'bold': True})

  • 斜体:italic = book.add_format({'italic': True})

  • 赤字:red = book.add_format({'color': 'red'})

このようにして作られた変数を,装飾したい文字列の直前に書きます.

import xlsxwriter

book = xlsxwriter.Workbook('sample.xlsx')
sheet = book.add_worksheet()
bold   = book.add_format({'bold': True})
italic = book.add_format({'italic': True})
red = book.add_format({'color': 'red'})
sheet.write_rich_string(0, 0, 
                        bold, 'bold', 
                        italic, 'italic', 
                        red, 'color')
book.close()

この例では,以下のように装飾が行われます.

f:id:gotutiyan:20201025215804p:plain

フォーマットは他にもあります.以下に簡単な一覧を示します.

目的 .add_format()の中身
太字 {'bold': True}
下線 {'underline': True}
斜体 {'italic': True}
フォント指定 {'font_name': 'フォント名'}
文字サイズ {'font_size': 整数}
文字色(色名指定) {'color': 'red'}, {'color': 'blue'}など
文字色(#000000指定) {'color': '#FF0000'}, {'color': '#0000FF'}など

(上記以外にも指定できます.詳しくはこちら:https://github.com/jmcnamara/XlsxWriter/blob/master/xlsxwriter/format.py

中央揃えなどを実現するセルのフォーマット

.write_rich_string()では,引数の一番最後にフォーマット変数を与えたとき,それをセルに対するフォーマットだと認識します.例えば,中央揃えにしたいセルがあるとき,

import xlsxwriter

book = xlsxwriter.Workbook('sample.xlsx')
sheet = book.add_worksheet()
center = book.add_format({'align': 'center'})
sheet.write_rich_string(0, 0, 'This is ', 'center ', 'string', center)
book.close()

というように,.write_rich_string()の最後にフォーマット変数を指定します.セルに対するフォーマットは,以下のようなものがあります.

目的 add_format()の中身
(横方向の)中央揃え {'align': 'center'}
左揃え {'align': 'left'}
右揃え {'align': 'right'}
セルの色(色名指定) {'bg_color': 'red'}
セルの色(#000000指定) {'bg_color': '#FF0000'}
枠線 {'border': 整数}
(縦方向の)中央揃え {'valign': 'vcenter'}
文字列を折り返して全体を表示 {'text_wrap': True}

(上記以外にも指定できます.詳しくはこちら:https://github.com/jmcnamara/XlsxWriter/blob/master/xlsxwriter/format.py

セルを結合して書き込む.merge_range()

特定の範囲のセルを結合して書き込むには,.merge_range()を使います.例えば,A1からC5までを結合して書き込む場合は,

import xlsxwriter

book = xlsxwriter.Workbook('sample.xlsx')
sheet = book.add_worksheet()
sheet.merge_range('A1:C5', 'string') # セルを文字列で指定
# sheet.merge_range(0, 0, 4, 2, 'string') # セルをindexで指定
book.close()

と書けます.セルの情報は文字列でも整数でも指定できます.結合後のセルのフォーマットも指定できます(前述した,引数の一番最後にフォーマット変数を書く方法で).

f:id:gotutiyan:20201025215711p:plain

リストのデータを特定のセルから行/列方向に書き込む

複数のデータがリストとして存在しているとします.これを特定のセルを始点として書き込む方法です.

行方向に書き込む.write_row()

以下の例では,C2のセルから右方向にデータを書き込みます.

import xlsxwriter

book = xlsxwriter.Workbook('sample.xlsx')
sheet = book.add_worksheet()
data = ['A', 'B', 'C']
sheet.write_row('C2', data) # セルを文字列で指定
# sheet.write_row(1, 2, data) # セルをindexで指定
book.close()

f:id:gotutiyan:20201025215536p:plain

列方向に書き込む.write_column()

以下の例では,C2のセルから下方向にデータを書き込みます.

import xlsxwriter

book = xlsxwriter.Workbook('sample.xlsx')
sheet = book.add_worksheet()
data = ['A', 'B', 'C']
sheet.write_colmun('C2', data) # セルを文字列で指定
# sheet.write_column(1, 2, data) # セルをindexで指定
book.close()

f:id:gotutiyan:20201025215533p:plain

セルの幅を指定

.set_column()を使用して,特定の範囲のセルの幅を設定できます.以下の例では,A列からC列の幅を20ポイントに設定します.

import xlsxwriter

book = xlsxwriter.Workbook('sample.xlsx')
sheet = book.add_worksheet()
sheet.set_column('A:C', 20) # セルを文字列で指定
# sheet.set_column(0, 2, 20) # セルをindexで指定
book.close()

f:id:gotutiyan:20201025215530p:plain

おわりに

xlsxwriterを用いてエクセルファイルに値を書き込む方法を紹介しました.何かご指摘などありましたら,コメントにお願いいたします.

【Processing】当たり判定を”色”で検出するテクニック

はじめに

Processingによる当たり判定の検出は,座標を用いて行われることがほとんどだと思います.しかし,座標の当たり判定を書くのは結構大変です.大変さの要因として,

  • 「当たる側」と「当てられる側」の座標を両方考慮しないといけないこと
  • 座標の大小関係を考慮する必要があること

があります.

一方で,色による当たり判定は簡単です.この判定方法の利点は,

  • 「当たる側」の座標だけを考慮すれば良いこと
  • 座標の大小関係を考慮する必要がないこと

です.一方で,条件式の数が増えがちという欠点はあります.

隙があるので少し自分語りをすると,このテクニックは,僕が学部一年の時に作ったゲームに広く使用しました.一つの適用例として,序盤で挙げさせていただきます(せ,宣伝ではないです).壁や床を黒で統一し,主人公(の青い四角)との当たり判定を色によって行っています.

https://www.konan-u.ac.jp/faculty/ii/public/prog1/showcase/2017/1/index.html

本記事では,座標の当たり判定をざっとおさらいしつつ,色による当たり判定を紹介します.

目次

まずは座標を使った当たり判定

まずはおさらいも兼ねて,代表的な座標による当たり判定に触れておきましょう.

分かりやすさのため,以下のようなゲーム(のようなもの)を考えます.2つの四角があって,白い四角はマウスで操作できます.また,赤い四角と少しでも重なるとき,Collisionという文字列が上部に表示されます.

f:id:gotutiyan:20201015010444g:plain

int x1,y1,w1,h1,x2,y2,w2,h2;
void setup(){
  size(500, 500);
  textSize(30);
  x1 = y1 = 200;
  w1 = h1 = w2 = h2 = 100;
}

void draw(){
  background(255);
  fill(255, 0, 0);
  rect(x1, y1, w1, h1);
  x2 = mouseX;
  y2 = mouseY;
  fill(255);
  rect(x2, y2, w2, h2);
  if(rect_collision_with_pos(x1, y1, w1, h1, x2, y2, w2, h2)){
    fill(0);
    text("Coliision", 200, 100);
  }
}

boolean rect_collision_with_pos(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2){
  return(x2 <= x1+w1 && 
         x1 <= x2+w2 && 
         y2 <= y1+h1 && 
         y1 <= y2+h2);
}

当たり判定は以下の関数が担っています.

boolean rect_collision_with_pos(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2){
  return(x2 <= x1+w1 && 
         x1 <= x2+w2 && 
         y2 <= y1+h1 && 
         y1 <= y2+h2);
}

この関数では,四角形と四角形の当たり判定を実現していますが,条件式が複雑です.慣れたらすぐに書けるのですが,変数名や不等号の向きを間違えるリスクが高いです.

色による当たり判定

次に色による当たり判定を紹介します.その前に,色を扱うためのメソッドを一つ紹介します.

get()メソッド

get()メソッドは,指定した座標の色を取得します.

get(x座標, y座標)

返り値はcolor型です.

例えば,

// マウスの座標が赤の時に何かしたい
if(get(mouseX, mouseY) == color(255,0,0)){
  // 処理
}

のように使えます.

実装

先ほどの例において,当たり判定の関数を書き換えてみます.

int x1,y1,w1,h1,x2,y2,w2,h2;
void setup(){
  size(500, 500);
  textSize(30);
  x1 = y1 = 200;
  w1 = h1 = w2 = h2 = 100;
}

void draw(){
  background(255);
  fill(255, 0, 0);
  rect(x1, y1, w1, h1);
  x2 = mouseX;
  y2 = mouseY;
  fill(255);
  rect(x2, y2, w2, h2);
  if(rect_collision_with_color(x2, y2, w2, h2)){
    fill(0);
    text("Coliision", 200, 100);
  }
}

boolean rect_collision_with_color(int x, int y, int w, int h){
  color red = color(255, 0, 0);
  int eps = 1;
  return(get(x-eps, y-eps) == red || // 左上の頂点の色
         get(x-eps, y+h+eps) == red || // 左下の頂点の色
         get(x+w+eps, y-eps) == red || // 右上の頂点の色
         get(x+w+eps, y+h+eps) == red); // 右下の頂点の色
}

この例では,以下に青色の点で示した座標の色を見て,当たり判定を検出します.頂点の座標は四角の枠線に重なるので,枠線の色を見てしまわないように,epsという変数を使って外側に1ピクセルずらしています.

f:id:gotutiyan:20201014232840p:plain

実行すると,座標の時とほぼ同じように当たり判定が検出できることが分かります.

ただし,自分より小さい物体の検出が苦手という欠点があります.例えば,辺の真ん中に小さい物体が突っ込んでくるようなことを考えると,4つの頂点を見るだけでは検出できません.

f:id:gotutiyan:20201014233145p:plain

このような場合に備えるために,色を見る座標を増やしてみます.例えば,辺の真ん中も見るようにしましょう.

boolean rect_collision_with_color(int x, int y, int w, int h){
  color red = color(255, 0, 0);
  int eps = 1;
  return(get(x-eps, y-eps) == red || // 左上の頂点の色
         get(x-eps, y+h+eps) == red || // 左下の頂点の色
         get(x+w+eps, y-eps) == red || // 右上の頂点の色
         get(x+w+eps, y+h+eps) == red || // 右下の頂点の色
         get(x+w/2, y-eps) == red || // 上の辺の真ん中
         get(x+w+eps, y+h/2) == red || // 右の辺の真ん中
         get(x+w/2, y+h+eps) == red || // 下の辺の真ん中
         get(x-eps, y+h/2) == red); // 左の辺の真ん中
}

この例では,以下の8点を見ていることになります.

f:id:gotutiyan:20201015000556p:plain

ここまですれば,ほとんどの場合に対応できるでしょう.座標での当たり判定よりも条件式の数は増えますが,一つ一つの条件式はずっと簡単に書けます.座標の大小を考慮する必要はありませんし,ある一つの物体の座標だけを考えれば実装できます.この点をどこまで細かく設定するかは,求める当たり判定の精度や,対象とする物体の大きさによります.場合によっては,辺の1/4地点も必要になるかもしれません.

実用例:床に着地させる

せっかくなので,より実用的な例を一つ載せたいと思います.

アクション系のゲームで,ある床があって,そこに着地させたいことがあると思います.仮にキャラの当たり判定領域を四角形だとして,床の色が黒だとすると,四角形と黒色の当たり判定として書けます.

int x, y, w, h;
void setup(){
  size(500, 500);
  textSize(30);
  x = 100;
  y = 0;
  w = h = 100;
}

void draw(){
  background(255);
  fill(0);
  rect(0, 400, width, 100); // 床のつもり
  fill(255);
  rect(x, y, w, h);
  // 当たり判定が検出されない間,落ち続ける
  if(!rect_collision_with_color(x, y, w, h)){
    y += 5; // 重力のつもり
  }
}

boolean rect_collision_with_color(int x, int y, int w, int h){
  color black = color(0, 0, 0);
  int eps = 1;
  return(get(x-eps, y-eps) == black || // 左上の頂点の色
         get(x-eps, y+h+eps) == black || // 左下の頂点の色
         get(x+w+eps, y-eps) == black || // 右上の頂点の色
         get(x+w+eps, y+h+eps) == black || // 右下の頂点の色
         get(x+w/2, y-eps) == black || // 上の辺の真ん中
         get(x+w+eps, y+h/2) == black || // 右の辺の真ん中
         get(x+w/2, y+h+eps) == black || // 下の辺の真ん中
         get(x-eps, y+h/2) == black); // 左の辺の真ん中
}

当たり判定が検出されない間落ち続けて,検出されると止まれば良いので,if分には当たり判定の関数を否定するものを入れます.中の処理にy座標を足すことを書けば,床への着地が実現できます.

f:id:gotutiyan:20201015003307g:plain

他の図形への応用

本記事では,四角形同士の当たり判定を扱いましたが,おそらく,他の図形でもできると思います.結局,色を見たい点をいくつか置いて,当たり判定の輪郭を作るようなイメージ(Unityでいうコライダー的なもの)なので,複雑な図形同士でもできると思います.

おわりに

本記事では,当たり判定を色で検出する話を書きました.うまく使えば,表現の幅を簡単に広げられる気がするので,ぜひ試してみてください.

ICPC 2019 国内予選B 「スクリーンキーボード」解説

問題: https://onlinejudge.u-aizu.ac.jp/challenges/sources/ICPC/Prelim/1633

C++で解説しています.

問題の言い換え

h\times wのマス目があり,各マスに文字が割り当てられている.最も左上のマスを(0, 0)とする.あなたは(0, 0)におり,1回の移動で上下左右の1マスしか移動できない.いま,ある文字列が与えられる.あなたは文字列の先頭の文字から順に,その文字が書かれたマス目に移動しなければならない.文字列の最後の文字に移動し終わったとき,(移動したマス目の総数)+(文字数)を求めよ.

解法

問題の言い換えは少々飛躍&雑ですが...

キーボードという設定ですが,マス目の上に文字が乗っていると考えて問題ありません.また,リモコンの説明が色々ありますが,結局,上下左右1マスにしか移動できないということです.

全ての文字についてOKボタンは押さないといけないので,一旦OKボタンは無視して計算して,最後に文字数を足します.(OKボタンは必ず文字数だけ押されるので.)

大まかな実装としては,事前に[文字] = (x, y)となるような辞書を構築しておいて,今いる座標と次の文字の座標とのマンハッタン距離を足していくようなものが簡単だと思います.辞書は,pythonならdict()C++ならmapunordered_mapに相当します.また,マンハッタン距離は,x方向の差分とy方向の差分を単に足したものです.

もう少し細かいところを書きます.まず,キーボードの情報を格納する型についてです.この情報は,h\times w個のchar型変数だと思っても良いですが,h個のstringだと思っても差し支えないです(各文字列の文字数がw).つまりvector<vector<char>>vector<string>かということですが,後者の方が分かりやすいと思います.それから,(x, y)のような座標の情報は,int型変数を2つ持つ構造体で表現するか,#include <utility>で使えるpair<int,int>で表現すると良いと思います.

これらを踏まえると,以下のようなコードになります.

#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <utility>
using namespace std;

int main(){
    int h,w;
    while(cin >> h >> w){
        if(h+w == 0)break;
        vector<string> chars(h);
        for(int i=0;i<h;i++){
            cin >> chars[i];
        }
        // [文字] = 座標の辞書 
        unordered_map<int, pair<int,int>> char2point;
        // 辞書を構築
        for(int i=0;i<h;i++){
            for(int j=0;j<w;j++){
                char2point[chars[i][j]] = pair<int,int>(i, j);
            }
        }
        // 文字列を先頭から見ていき,移動したマスを数える.
        string s;
        cin >> s;
        int ans = 0;
        pair<int,int> now_point = pair<int,int>(0, 0);
        for(int i=0;i<s.size();i++){
            pair<int,int> point = char2point[s[i]];
            ans += abs(now_point.first - point.first);
            ans += abs(now_point.second - point.second);
            now_point = point; // 座標を更新
        }
        cout << ans + s.size() << endl;
    }
    
}

ICPC 2019 国内予選A 「期末試験の成績」 解説

問題: https://onlinejudge.u-aizu.ac.jp/challenges/sources/ICPC/Prelim/1632

C++で解説しています.

問題の言い換え

整数がMN列で並んでいる.列の合計値の最大値を求めよ.

解法

各列の合計を計算して,最大値を出力します.計算方法はいくらでもありますが,二次元配列の舐め方を考慮して2通り紹介します.

二次元配列を横に舐める方法

横に舐めるので,入力と同じようにfor文を回して処理します.列を逐次的に処理できないので,列の合計値を保存する配列を作る必要があります.

max_element()は配列の最大値を求める関数です.(#include <algorithm>が必要)

#include <iostream>
#include <vector>
using namespace std;

int main(){
    int n,m;
    while(cin >> n >> m){
        if(n+m == 0)break;
        vector<vector<int>> v(m, vector<int>(n, 0));
        vector<int> col_sum(n, 0);
        // 入力
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                cin >> v[i][j];
            }
        }
        // 列の合計値を計算
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                col_sum[j] += v[i][j];
            }
        }
        // 最大値を出力
        cout << *max_element(col_sum.begin(), col_sum.end()) << endl;
    }
}

二次元配列を縦に舐める方法

この方法では列を逐次的に処理できるため,列の合計値を保存する配列が必要ありません.単に前の列から合計を計算して,答えの変数を更新します.

この場合,二次元配列を「縦方向に舐める」感覚が必要になります.C/C++において二次元配列の入力を受けるときには,

for(int i=0;i<m;i++){
    for(int j=0;j<n;j++){
        // [i][j]で参照

のようにするのが一般的ですが,これは二次元配列を横方向に舐めています.ある行を固定して,列を回します.

一方で,縦方向に回すときには,列を固定して行を回します.コードで言うと,

for(int i=0;i<n;i++){
    for(int j=0;j<m;j++){
        // [j][i]で参照

みたいな感じです(もちろん,この実装は一例です).

以上を踏まえると,以下のように書けます.

#include <iostream>
#include <vector>
using namespace std;

int main(){
    int n,m;
    while(cin >> n >> m){
        if(n+m == 0)break;
        vector<vector<int>> v(m, vector<int>(n, 0));
        // 入力
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                cin >> v[i][j];
            }
        }
        int ans = 0;
        for(int i=0;i<n;i++){
            int score = 0; // i列目の合計が入る
            for(int j=0;j<m;j++){
                score += v[j][i];
            }
            // 最大値を更新
            ans = max(ans, score);
        }
        cout << ans << endl;
    }
}

ICPC 2003国内予選B 「Get Many Persimmon Trees」

問題URL: https://onlinejudge.u-aizu.ac.jp/challenges/sources/ICPC/Prelim/1125?year=2003

問題の言い換え

W,縦H個のマス目がある.ここにN個の点を打つ.i個目の点は座標(x_i, y_i)である.点の座標は重複しない.この下で,横S,縦Tの領域で囲める点の数の最大値を出力せよ.

解法

まずは点の情報を保存します.これは0で初期化されたH \times Wの二次元配列に対して,(x_i, y_i)の要素を+1することで得られます.例えば,サンプルの最初のデータセットでは

0 0 0 0 0 0 0 1 0 0
0 1 0 1 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 1
0 0 0 0 0 1 0 1 0 0
0 1 0 1 0 0 1 0 0 0
0 0 0 0 0 0 0 0 1 0
0 1 0 0 0 1 0 0 0 0
0 0 1 1 0 0 1 0 0 0

のような二次元配列を構築します.N=16なので,1も16個あります.

次に,答えの最大値をどう探索するかを考えます.とりあえず,横S,縦Tの領域の点の数を求めることを考えると,2重ループでできることが分かります.

for(int i=0;i<T;i++){
 for(int j=0;j<S;j++){
   // 二次元配列[i][j]の 1 or 0 を見る
 }
}

ただし,横S,縦Tの領域は様々に取れるので,これは全通り見てやる必要があります.よって今回は,領域の左上の座標を全探索することにします.4乗オーダーになりますが,W,Hが100以下であることを考慮すると,十分高速です.

for(int y=0;y<h-t+1;y++){ // 左上のy座標
  for(int x=0;x<w-s+1;x++){ // 左上のx座標
    for(int i=0;i<T;i++){
      for(int j=0;j<S;j++){
        // 二次元配列[y+i][x+j]の 1 or 0 を見る
      }
    }
  }
}

左上の座標によっては範囲が W,Hをはみ出すこともあり得ますが,そのような場合は考慮しなくて良いです.(はみ出したときの点の数は,はみ出さないように調整したときの点の数には勝てないため)

以上を考慮すると,全体としては次のように書けます.

#include <iostream>
#include <vector>

using namespace std;
int main(){
    int n;
    while(cin >> n){
        if(n == 0)break;
        int w, h;
        cin >> w >> h;
        vector<vector<int>> v(h, vector<int>(w, 0));
        # 点を打つ
        for(int i=0;i<n;i++){
            int x, y;
            cin >> x >> y;
            x--; y--;
            v[y][x]++;
        }
        int s, t;
        cin >> s >> t;
        int ans = 0;
        # 左上の座標を全探索
        for(int y=0;y<h-t+1;y++){
            for(int x=0;x<w-s+1;x++){
                # S,Tの領域の点の数を数える
                int count = 0;
                for(int i=0;i<t;i++){
                    for(int j=0;j<s;j++){
                        count += v[y+i][x+j];
                    }
                }
                # 最大値を更新
                ans = max(ans, count);
            }
        }
        cout << ans << endl;
    }
}

NAIST受験記2020(2021年春入学・1回目)

本記事は1年前の情報です

(2021年7月21日に追記)この記事は2021年度・春入学の受験記です.既に2022年度・春入学の第一回入試は終了しており,この記事も1年前の古い情報になりました.NAISTの入試形式が変わらない限りは本記事の情報も生き続けますが,基本的には最新の情報を見るのがベターです.そこで,以下に2022年度入学の受験者が新たに書いてくれた記事を列挙しておきますので,まずはそちらを見ることをお勧めします.この一年間,本記事は2000回以上クリックされており,実際に20人以上の方から連絡をいただきました.何かの役に立てていれば嬉しく思います.引き続き,本記事もよろしくお願いします.

受験記

NAIST受験記 2022年春入学(第1回) | MASAPAGE

NAIST情報科学領域合格体験記(2022年度春入学第1回)|駄文駄文ぴじょん|note

NAIST合格体験記2021(2022春学期第一回)|だぶ|note

NAIST受験記2022①<対策編>|Yuki Yamaoka|note

最強のNAIST情報科学領域合格体験記【2022年度春入学第1回】|Shota Ozaki Org

小論文

GitHub - mlieynua/naist_essay: NAISTの入試で提出した小論文

naist_essay/NAIST_essay.pdf at main · dovedovepigeon/naist_essay · GitHub

はじめに

本記事は,NAIST博士前期課程2021年度春学期入学・第1回目入試の受験記です.情報科学領域志望です.僕自身,他の方の受験記をかなり参考にさせていただいたので,今後受験される方の一助になればと思って書くことにしました.

今回の入試はコロナの影響でオンライン形式となりました.よって,本記事に受験地の様子や前泊に関する記述はありません.

忙しい方へ3行で

  • 結果:TOEIC 525点,数学1完,面接はほとんど詰まらずで合格
  • 対策:数学はマセマ,小論文は約2週間前から準備,面接は自問自答してイメトレ
  • 試験内容:数学は解析が易,代数が難(個人の感想です),面接は専門的なことをだいぶ聞かれた

目次

対策編

GPA

(GPAの項目は2021/5/25に追記されました) 入試の項目には書類審査が50点分もあります.小論文は90点のほうに行くこともあり,書類審査では主に学部の成績が見られているのかなあと勝手に思っているので,GPAを書いておきます.僕は甲南大学出身で,出願時まで(3年次まで)の平均GPAが約3.5でした(最大値が4.0).他にも何かしらで見られている要素があるのかもしれませんが,参考程度ということで.

研究室の調査と決定

希望研究室の調査は重要です.NAISTの面接は,出願時に希望した研究室の先生が行います.よって,希望研究室の研究内容と自分のしたい研究内容がマッチするかという点は,よく把握するように努めました.具体的には,先生に自分の研究したいことを相談したり,研究室の過去の論文を見たりしました.

相談の機会としては,3月と5月のオープンキャンパスを利用しました.3月のものは現地開催で,興味のある研究室を中心に訪問しました.ただし,第1希望の研究室は教授が変わる関係で動いていないようだったので,訪問できませんでした.5月のものはオンライン開催で,第1希望の研究室の先生と話すことができました.研究したい分野を伝えて,入学後も取り組めそうか確かめました.

小論文

小論文は出願の2週間くらい前から書き始めました.小論文では,

  • これまでの修学内容

  • 入学後に取り組みたい研究内容

の2点を,A4 2ページに収める必要があります.今回は前者を1/4ページ,後者を残り7/4ページという配分で書きました.卒業研究の進捗があまりないため前者に書くことがなく,記述量が偏りました.

また,記述内容として,修学内容には

  • 今の専門分野に興味を持ったきっかけ

  • 既に学会発表した研究の簡単な内容

  • 卒業研究の簡単な内容

を書いて,入学後の研究内容には

  • 先行研究の紹介,近年の動向など

  • 上記の動向を踏まえた上での課題設定

  • 課題を解決するためのざっくりした研究計画

を書きました.研究内容については,一般的な論文の流れそのものだと思います.ただし,小論文の採点者が自分と同じ専門分野の人だとは言い切れないため,分野に精通していない人でも通じるような内容を心がけました.これは近年の動向の記述を増やすことで実現できると思います.

小論文はそもそもテーマ決めに苦労するんですが,卒論のテーマが良い感じの人はそれを拡張する形で書けば良いと思います.「今卒論としてこういうことをやっていて,その続きで修士もやりたい」というのはある意味王道で,説得力があります.一方,NAIST修士から分野が変わるような人も歓迎するみたいな雰囲気が強いと思うので,思い切って卒論とは全く異なるテーマを採用するのもアリです.ただしその場合は「修士からその研究をやりたいと思っているのに,なぜ今,卒論としては別のことをやっているのか」という問いに明確に答えられる必要があると思います(今配属済みの研究室が分野違いなので...という話になることが多そうですが).筆者の話をすると,僕は卒論と同じ分野ですがテーマとしては異なるものを採用しました.テーマを決めるにあたっては学部の研究室の先生に相談しようかとも思いましたが,結局他人から与えられたテーマには情熱を持てない気がしたため,自分で一から考えたものを採用しました(精神論っぽいのですが,やはりその研究をどれくらい考え抜けるかに関わってくると思います).

添削は学部の研究室の先生にしていただきました(希望研究室の修士や博士の人にも頼もうかと思いましたが,コメントもらって修正するまでの時間が微妙になかったのでしなかった).

NAISTの小論文は公開されている方もいます.他の方の受験記ブログや,GitHubで「NAIST」で検索するといくつか見つかります.僕の小論文も新規性がなくなったと判断すれば一般公開するつもりです.(でも,TwitterにDMをいただければお送りいたします)

ごつちやん (@gotutiyan_kapi) | Twitter

面接

とにかくイメトレしました.お風呂に浸かりながら,自分が試験官になったつもりでひたすら自問していました.やればやるほど,自分の知識の穴が見つかります.その穴を文献を読んで埋めることの繰り返しです.

主な内容は,

  • 提案手法のメリットとデメリット説明できる?

  • 引用している文献の内容は簡単に説明できる?

  • (国際会議で行われたコンペの話を書いていたので)そのコンペの上位システムの手法は?コンペで提供されたコーパスと,その簡単な特徴は?

  • 卒業研究で対象としている課題は説明できる?大まかなアプローチが決まっていればその簡単な説明もできる?

  • なぜNAISTを目指すのか?お前が研究したい分野で有名な研究室は他の大学にもあるぞ?なぜ自大の院には進まない?

  • 卒業後の進路どうするの?

などなどです.ひたすら自問自答しました.

また,例年の面接では,最初に小論文を3分でプレゼンする試問がありましたが,オンライン面接では行わないと明記されていました.とはいえ,自分の小論文の要点を簡潔に話せることはとても重要だと考えて,3分で話す練習もしていました.多分,どこかで役に立ったのだと思います.

英語

TOEICのスコアで出しました.そこまで英語が得意ではないのと,コロナの影響で続々と試験が中止になったことから,2019/12月時点の成績だった525点で出しました.

他の方の受験記ブログを見ると,なんだかんだで600点を超えている人が多く,客観的には少し不安な点数だったかもしれません(合格者平均も670点程度.公式QAより).一方で,出願直前期のTOEICたちが続々と中止になっていたことから,その時期で追い込む予定だった受験者の点は伸びていないと予想し,楽観的でした.

数学

数学の出題範囲は教科書で指定されています.教科書で勉強するつもりはありませんでしたが,範囲の確認は行わないといけないので,以下のリンクから目次だけ確認しました.

範囲がわかれば勉強するしかありません.他の方の受験記では揃ってマセマを使っていたので,僕も微積線形代数のマセマを買ってきてやりました.ただ,微積のマセマはsin^{-1}などの三角関数逆関数を含む問題が多く,NAISTの傾向とは合わないように思いました.NAIST三角関数逆関数が出た例は見たことがないです.(出ないことを保証する記述ではないです.)

過去問もそれなりに公開している人がいるので,見つけ次第全て解きました. 例えば,以下のリポジトリは問題の年代こそ古い(~2012年)ですが,力試しにはちょうど良いと思います.確率統計の問題もありますが,今は出ないので無視してOKです.

GitHub - tubutubucorn/naist-exam

さらに,数学の問題を載せているブログでいうと

2019年NAIST受験記(情報科学専攻) - うしのおちちの備忘録

https://sumansonian.com/naist-exam-graduate-school/

院試受験記 (NAIST) - S 研究室

NAISTを受験した話 | MATHFREAK

あたりが見つかります.見つけた問題は片っ端から解きました.

もっと問題に触れたい場合は,色々な大学の院試過去問を漁るのも良いかもしれません.

試験日当日編

入試はWeb会議システムWebexで実施されました.事前に受験票と一緒に入室時刻が記載された紙も届くので,指定の時間に入室します.入室後は,注意事項の確認と,受験番号を聞かれるなどの簡単な質問がありました.専願か併願かも聞かれました.それから15分程度待った後に,数学の試験が始まりました.

数学

従来の数学の試験は,4問中2問を選ぶ形式で,10分間の下見,10分間の解答です.一方で,オンライン試験における数学は,2問出題で,下見なし,10分間の解答です.つまり,どの問題を解くか選ぶ必要がない代わりに,問題を見た瞬間に説明を始めないといけませんでした.説明できる時間はあくまで10分と変わりなしなので,ほとんど制限時間が半分になったようなものです.このような背景から,個人的には問題はそれなりに易化すると思っていました.しかし実際には,従来とほとんど変わらない難易度の問題のように思いました.(自分が解けなかった問題は難しく見える・難しいと思いたい,というバイアスがかかっているかもしれません.)

結果としては,解析は完答,代数は口頭で解法を伝えるにとどまりました.

僕が解いた問題はその著作権を考慮し,正確な数字を載せることは避けます(問題に著作権があるのかどうか分かりませんが...).小論文と同様,DMをいただけると個人的な範囲で共有いたします.

  • 解析

    小問集合でした.1問目で単に三角関数の絶対値の積分をさせて,2問目で三角関数のグラフが絡む領域の面積を求めさせる問題でした.こういうのは2問目の計算に1問目の結果を使うはずですが,繋がりが分からなかったので気にせず解いてしまいました.

  • 代数

    問題文が長くて詳細を覚えてないです.長いので,貴重な10分の一部を読むのに取られて大変でした.なんか線型独立なベクトルが3つあって,そのうち2つを足し引きして新たなベクトルを3つ作ります.このとき,新たなベクトルはなんと線型従属だというんですね.これを証明する感じでした.加えて,独立と従属を入れ替えたバージョンも証明しろという内容です.

知り合いにもう一人受験した人がいたので問題を聞くと,

  • 解析: n^{3}e^{-n}の無限級数に関する問題

  • 代数:正方行列A,Bがあるとき, ABBAのトレース(対角成分の和)に関する問題

だったそうです(著作権に配慮して表現を調整しています).

面接

数学の試験を終えると,2,3分開けて面接が始まります.試験官は3人で,希望研究室の先生が中央にいらっしゃいました.基本的に,希望研究室の先生が小論文を中心とした専門的なことを聞いて,左右の先生が,志望動機に関わることや,卒業研究についてざっくり聞いてくる感じでした.

質問の内容は大体以下の通りです(もちろん,本当はもっと丁寧な表現で質問してくださっています).

  • 提案手法に関する専門的な質問

  • 専願ということだけど,自大の院には進まないの?

  • 修士卒業後のキャリアはどう考えてる?

  • 卒業研究はどんなことをしてるの?

  • プログラミング言語は何が打てる?どんなものを実装した?

専門的な内容では,小論文の手法についてしっかり議論する感じでした.個人的にはオーバーフォッティングやスケーリングというような英語的な表現に馴染みがなかったので,その表現で質問された時に焦りました.また,聞いたことあるけど仕組み知らない系の用語が絡む質問があり,素直に「深く知らないので答えられません」という回答をしました.分からない質問に対しての回答は,「分からないことを分かっている」アピールをするとポジティブに捉えて,素直に分かりませんと言おうと決めていました.

また,面接で最も注意したのは,「聞かれていることを正確に把握すること」です.何を聞かれているか理解して,ねじれのない回答をするように努めました.希望研究室の先生が直接面接する意味を考えると,「ちゃんと話できて,研究の議論できます!」とアピールすべきだと考えたからです.

合格発表

PDFを開く勇気が必要でした.思い切って開くと自分の番号が書いてありました.

格通知メールが来るとか来ないとかの話が毎年どこからか出ますが,少なくとも情報科学領域は来ません.バイオか物質のどちらかであれば,合格者ガイダンスなどあるようなので,その日程の通知を兼ねて送られる可能性はあると思います.仮に来てもそれはそれで問題ないので良いとして,来ないことに関しても問題ないことを明記しておきます.

2週間くらいで通知の封筒が来ました(お盆挟んだので,本当はもうちょっと早いんだと思います).これで本当に,合格したことを実感できます.

点数

(点数の項目は2021/5/25に追記されました) 点数開示をしまして,満点200点,合格最低点134点,僕の点数が138点とのことでした.科目ごとの点は分かりませんが,結構ギリギリだったようです.推測するとすれば,英語10点(満点30),数学20点(満点30),書類審査40点(満点50),小論文・面接68点(満点90)くらいなのではないでしょうか.

おわりに

NAIST受験に関する一連の流れについて記しました.小論文や面接対策として,提案手法について本当に色々なことを考えました.よく考えたことで合格につながったのはもちろん嬉しいですし,個人的に研究に対する思考力が上がったような気がして,受験することを通して成長できたような気がします.

受験を終えてひと段落ですが,今からが本番ですよね.

何かご質問などあれば,この記事のコメントか,twitterの@gotutiyan_kapi までDMなどいただければ回答いたします..

ごつちやん (@gotutiyan_kapi) | Twitter

【python】コマンドライン引数を扱うargparseを丁寧に

はじめに

本記事では,argparseを用いてコマンドライン引数を便利に使う方法を紹介します.

公式ドキュメントはこちら:argparse --- コマンドラインオプション、引数、サブコマンドのパーサー — Python 3.10.6 ドキュメント

目次

argparseは何ができるか

コマンドライン引数を簡単に参照できるようになります.コマンドライン引数は,実行時に与える情報のことです.例えば,以下の例ではファイルのパスをコマンドライン引数で与えています.

python sample.py --in_file desktop/hoge.txt

argparseのインポート

インポートは一行です.

import argparse

argparseの最小限サンプル

まずは最小限のサンプルをみて,構成を確認します.

基本的に,

  1. 引数を解析するためのArgumentParserインスタンスを作成
  2. 必要なオプションを.add_argument()で追加
  3. .parse_args()で引数の解析結果を取得.

という構成です.1.と3.はいつも同じなので,役割は一旦気にせず,そういうものだと思っても良いです.

# コード (sample.py)
import argparse

parser = argparse.ArgumentParser() # 1.インスタンスの作成
parser.add_argument('--in_file') # 2.必要なオプションを追加
args = parser.parse_args() # 3.オプションを解析
print(args.in_file)
# with open(args.in.file) as fp: みたいに使ったりとか

このようなプログラムを実行する場合,プログラム名をsample.pyだとすると

python sample.py --in_file desktop/sample.txt
# 出力: desktop/hoge.txt

のように実行できます.add_argument()'--in_file'の引数を追加しているので,実行時に指定できるようになりました.また,プログラム上ではargs.in_fileのように,変数名として使用できています.今回はそのまま出力しているので,ファイルパス(だと思っている文字列)が出力されます.プログラムは一切書き換えずに入力を変えられるのが嬉しいポイントです.

引数の一覧を表示する: --help

--helpもしくは-hコマンドライン引数の一覧を確認できます.--helpはデフォルトで使えるため,add_argument()で追加する必要はありません.

主に他人が作ったプログラムを使おうとするときに,そもそもどんな引数を取るか知るために使います.

# コード (sample.py)
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--sample1')
parser.add_argument('--sample2')
parser.add_argument('--sample3')
args = parser.parse_args()
# 実行
python sample.py -h
# 出力(使える引数が表示される)
usage: sample.py [-h] [--sample1 SAMPLE1] [--sample2 SAMPLE2] [--sample3 SAMPLE3]

optional arguments:
  -h, --help         show this help message and exit
  --sample1 SAMPLE1
  --sample2 SAMPLE2
  --sample3 SAMPLE3

add_argument()の詳細設定

引数の説明文を追加する(help='')

help=文字列で,設定したコマンドライン引数の説明文を追加できます.

# コード (sample.py)
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--in_file', help='入力ファイルのパスです.')
args = parser.parse_args()

こうしておくと,--helpで引数の一覧を表示したときに説明文が表示されます.

usage: sample.py [-h] [--in_file IN_FILE]

optional arguments:
  -h, --help         show this help message and exit
  --in_file IN_FILE  入力ファイルのパスです.

引数を必須にする(required=True)

required=Trueを設定すると,その引数は必須になります.

# コード (sample.py)
import argparse

parser = argparse.ArgumentParser() 
parser.add_argument('--in_file', required=True)
args = parser.parse_args()
print(args.in_file)

この引数を無視して実行しようとすると,

# 実行
python sample.py

エラーが出ます.

# 出力
usage: sample.py [-h] --in_file IN_FILE
sample.py: error: the following arguments are required: --in_file

引数の型を決める(type=)

type=で引数の型を変換してくれます.コマンドライン引数はデフォルトで文字列として扱われますが,整数や実数で扱いたいとき,これを使います.

# コード (sample.py)
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--str_arg')
parser.add_argument('--int_arg', type=int)
args = parser.parse_args()
print('str_arg:', args.str_arg, type(args.str_arg))
print('int_arg:', args.int_arg, type(args.int_arg))
# 実行
python3 sample.py --str_arg 10 --int_arg 10
# 出力
str_arg: 10 <class 'str'>
int_arg: 10 <class 'int'>

bool値で設定する(action='store_true')

action='store_true' で,引数をbool値で設定することができます.

よく使われる場面として,計算の途中経過を出力するかどうかを指定する--verboseが挙げられます(verboseは「冗長な」という意味).使い方は,--verbose を指定するかしないかです.

同じような設定にaction='store_false'もありますが,ややこしくなるだけなので基本的には指定すればtrueになるこちらを使いましょう.

# コード (sample.py)
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--verbose', action='store_true')
args = parser.parse_args()
if args.verbose:
    print('途中経過が出力されるよ')
else:
    print('途中経過は出力されないよ')
python sample.py --verbose
# 途中経過が出力されるよ
python sample.py
# 途中経過は出力されないよ

引数として許される値を設定する(choices=)

引数によっては,許す値の候補を提示したいときがあります.例えば,果物の名前を指定する引数に Apple, Orange は許したいが,それ以外の果物,例えばGrapeは許さない,というような場合です.このようなとき,choices=[許す値のリスト] とすることで実現できます.

# コード (sample.py)
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--fruit', choices=['Apple', 'Orange'])
args = parser.parse_args()
print(args.str)
# 実行例1 (適切な値を指定)
python3 sample.py --fruit Apple
# Apple
# 実行例2(不適切な値を指定)
python3 sample.py --fruit Grape
usage: sample.py [-h] [--fruit {Apple,Orange}]
sample.py: error: argument --fruit: invalid choice: 'Grape' (choose from 'Apple', 'Orange')

引数のデフォルト値を設定する(default=)

引数のデフォルト値を default=値で設定できます.プログラムを実行する際にオプションで指定されなかったとき,ここで設定した値が使われます.

# コード (sample.py)
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--x', default=10)
args = parser.parse_args()
print(args.x)
# 実行例1(デフォルト値を使用)
python3 sample.py
# 10
# 実行例2(オプションで指定されたので,その値を使用)
python3 nlp.py --x 20
# 20

一つの引数に与えられる数を設定する(nargs=)

nargs=を指定することで,一つの引数に複数個の値を与えられます.プログラム上ではリストになります.nargs=には引数として受け付けたい数を直接入れることができます.個数が任意の場合には,'*''+'の記号が指定できます.'*'の記号は0個以上,'+'の記号は1個以上の引数が与えられることを示します(正規表現を知っている人は飲み込みやすいですね).

  • nargs='*': 0個以上を許容します.
# コード (sample.py)
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--numbers', nargs='*')
args = parser.parse_args()
print(args.numbers)
# 実行例1(3個与えてみる)
python3 sample.py --numbers 1 2 3
# 出力
['1', '2', '3']
# 実行例2(0個与えてみる)
python3 sample.py --numbers
# 出力
[]
  • nargs='+': 1個以上を許容します.
# コード (sample.py)
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--numbers', nargs='+')
args = parser.parse_args()
print(args.numbers)
# 実行例1(3個与えてみる)
python3 sample.py --numbers 1 2 3
# 出力
['1', '2', '3']
# 実行例2(0個与えてみる)
python3 sample.py --numbers
# 出力(エラー)
usage: sample.py [-h] [--numbers NUMBERS [NUMBERS ...]]
sample.py: error: argument --numbers: expected at least one argument
  • nargs='具体的な数': 指定した個数ちょうどだけ許容します.
# コード (sample.py)
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--numbers', nargs=2) # 2個だけ許容
args = parser.parse_args()
print(args.numbers)
# 実行例(2個与えてみる)
python3 sample.py --numbers 1 2
# 出力
['1', '2']
# 実行例1(3個与えてみる)
python3 sample.py --numbers 1 2 3
# 出力(エラー)
usage: sample.py [-h] [--numbers NUMBERS NUMBERS]
sample.py: error: unrecognized arguments: 3

実は他にも2種類を指定できますが,割愛します.

(詳細: https://docs.python.org/ja/3/library/argparse.html#nargs