ami_GS's diary

情報系大学院生の備忘録。ネットワークの勉強にハマっています。

GitHubのContributionsをCUI上で表示するツール書いた

はじめに

僕といえばGitHubへの連続コミットということもあり、タイトルの通り、GitHubのContributions(以下の部分)をCUIで表示するツールを書きました。
f:id:ami_GS:20150124101938p:plain

ちなみにGoで書きました。ソースコードは以下
ami-GS/github_squares · GitHub

細かい技術

goquery

Go言語ではスクレイピングをする際、いろいろな選択肢があるのですが(go.net/html、go-html-transform等)、goqueryというものがjQueryライクにスクレイピングを実現するということで使ってみました。

四角の色付け

1日にどれくらいコミットしたかを色付けするためには”■”に緑の色付けをします。その場合は、

fmt.Sprintf("\x1b[38;5;%dm%v\x1b[0m", color, value)

とすればおk!
256色のリストはここです。

注意点

"https://github.com/ユーザ名"にアクセスすると、その中にcontributionsの緑色の四角の情報が入っている時と入っていない時があります。
なので、四角の情報を取るときは、"https://github.com/users/ユーザ名/contributions"にアクセスするとおそらくデータの取りこぼしがありません。

結果

結果です。
f:id:ami_GS:20150625213128p:plain


ちなみに

オプションで草生やせます
f:id:ami_GS:20150625213333p:plain

GoでCUIでマインスイーパ作った

はじめに

こんにちは、Goで何か作りたいな〜と思っていたらマインスイーパができていたのでレポです。
全体のコードは長いので要点を載せて説明します。

実装方針

  1. コマンドライン入力によりマス数(行数×列数)を決定
  2. それぞれのマスは -1~18 の値を持つ (-1~8:非オープン状態、9~18:オープン状態)
  3. ランダムで地雷の位置を決定 (-1)、その周りのマスを+1する。
  4. 選択されたマスの周りに地雷がない時、地雷があるマスに隣接するマスまで一度にオープンする
  5. 操作のたびに画面をリフレッシュする

コード達

1, コマンドライン入力によりマス数(行数×列数)を決定
単純明快、パッケージを使えば実現可能です。

        var input string
        var field *Field
        var h, w, m int
set:
        fmt.Printf("Input height, width, (num of mine) (e.g : 8,8(,9))\n>> ")
        fmt.Scanln(&input)
        pos := strings.Split(input, ",") //入力を分割
        if len(pos) == 2 || len(pos) == 3 {
                w, _ = strconv.Atoi(pos[0])
                h, _ = strconv.Atoi(pos[1])
                if len(pos) == 2 {
                        // 地雷数が指定されていない場合はマス数の25%を地雷に
                        m = w * h / 4
                } else {
                        m, _ = strconv.Atoi(pos[2])
                }
                if w == 0 || h == 0 || m == 0 {
                        fmt.Println("Please input 2 or 3 numerical values (value > 0)")
                        goto set //不適切な入力の時、setまでgoto
                }
                field = NewField(byte(h), byte(w), byte(m)) //地雷原構造体を生成
        } else {
                fmt.Println("Please input 2 or 3 numerical values (value > 0)")
                goto set
        }


2, それぞれのマスは -1~18 の値を持つ

type Field struct {
           width  byte
           height byte
           state  [][]int  // 地雷原のもつ値、-1 ~ 18 が入る
}


3, ランダムで地雷の位置を決定 (-1)、その周りのマスを+1する。
実装の簡単のため、入力された行数+2、列数+2の地雷原を生成し、ダミーとして使います。

func NewField(width, height, mineNum byte) *Field {
        field := &Field{width, height, [][]int{}}
        field.state = make([][]int, height+2)
        for i := 0; i < int(height)+2; i++ {
                field.state[i] = make([]int, width+2)
        }

        //生成した地雷の場所を一旦保持
        var pos [][2]byte = make([][2]byte, mineNum)
        // [0 width*height) をランダムに並び替え
        idx := rand.Perm(int(width * height))
        for i := 0; i < int(mineNum); i++ {
                //地雷数分 idx の先頭から取り出す
                pos[i] = [2]byte{(byte(idx[i]) / width) + 1, (byte(idx[i]) % width) + 1}
                // 地雷の周りを+1する
                field.state[pos[i][0]-1][pos[i][1]-1] += 1
                field.state[pos[i][0]-1][pos[i][1]] += 1
                field.state[pos[i][0]-1][pos[i][1]+1] += 1
                field.state[pos[i][0]][pos[i][1]-1] += 1
                field.state[pos[i][0]][pos[i][1]+1] += 1
                field.state[pos[i][0]+1][pos[i][1]-1] += 1
                field.state[pos[i][0]+1][pos[i][1]] += 1
                field.state[pos[i][0]+1][pos[i][1]+1] += 1
        }
        for i := 0; i < int(mineNum); i++ {
                //地雷を置く(-1)
                field.state[pos[i][0]][pos[i][1]] = -1
        }
        return field
}

4, 選択されたマスの周りに地雷がない時、地雷があるマスに隣接するマスまで一度にオープンする
RecursiveOpen(row, column)の中身は大したこと無いのに長ったらしいので省略します。

func (self *Field) Open(row, column byte) {
        // オープン => マスに +10
        self.state[row][column] += 10
}

func (self *Field) Choose(row, column byte) (gameover bool) {
        // ダミーマスの関係上、1オリジン
        gameover = false
        if 0 == self.state[row][column] {
                // 周りに地雷がない時、再帰的にオープンする
                self.RecursiveOpen(row, column)
        } else if 0 < self.state[row][column] && self.state[row][column] <= 8 {
                //周りに地雷がある時、そこだけをオープンする
                self.Open(row, column)
        } else if self.state[row][column] == -1 {
                //地雷がある時、全てをオープンし、ゲームオーバーフラグを返す
                self.AllOpen()
                return true
        }
        return
}

5, 操作のたびに画面をリフレッシュする
操作(どこをオープンするかの入力)ごとにFieldString()を呼び出し、画面をリフレッシュします。
"\x1b[2J"という文字列が画面をクリアしてくれるので、それを利用します。

func (self *Field) FieldString() string {
        // headerに列数を入力
        header := " "
        for len(header) < int(math.Log10(float64(self.height)))+2 {
                header += " "
        }
        for c := 0; c < int(self.width); c++ {
                tmp := fmt.Sprintf(" %d", c+1)
                for len(tmp) < 4 {
                        tmp += " " // TODO: here should be optimized
                }
                header += tmp
        }

        // 行数とマスを入力
        field := fmt.Sprintf("%s\n", header)
        for r := 1; r < int(self.height)+1; r++ {
                // 行数
                tmp := fmt.Sprintf("%d", r)
                for len(tmp) < int(math.Log10(float64(self.height)))+2 {
                        tmp += " "
                }
                field += tmp

                // マス
                for c := 1; c < int(self.width)+1; c++ {
                        if -1 <= self.state[r][c] && self.state[r][c] <= 8 {
                                field += CLOSED // "[ ]"
                        } else if self.state[r][c] == 10 {
                                field += OPENED // "___"
                        } else if 10 < self.state[r][c] {
                                // _1_ ~ _8_
                                field += OPEN_NUM[self.state[r][c]-11]
                        } else if self.state[r][c] == 9 {
                                field += MINE // "_*_"
                        }
                        field += " "
                }
                if r < int(self.height) {
                        field += "\n"
                }
        }
        // "\x1b[2J"で画面をリフレッシュ(クリア)
        return fmt.Sprintf("\x1b[2J%s>> ", field) 
}

最後に

これでコアな部分は完成しました!
あとは全体のコードを参照してください!プルリクエスト大歓迎です!
以下が 15行,15列,地雷数25%の時のゲームオーバー画面になります。
f:id:ami_GS:20150531235443p:plain

文字列の数式を計算するやつ

はじめに

ひょんなことから文字列の数式(四則演算)を計算することになり、Pythonで実装したので載せておきます。(floatは計算できません!!)

コード

import os
import re

ZERO = ord("0")
NINE = ord("9")

def paren(st):
    if st[0] == "(":
        ans, idx = first(st[1:])
        return ans, idx+2
    elif ZERO <= ord(st[0]) <= NINE:
        i = 1
        while i < len(st) and ZERO <= ord(st[i]) <= NINE:
            i += 1
        return int(st[:i]), i
    return 0, 0

def second(st):
    ans, idx = paren(st)

    i = idx
    while i < len(st):
        if st[i] == "*":
            tmp, idx = paren(st[i+1:])
            ans *= tmp
            i += idx+1
        elif st[i] == "/":
            tmp, idx = paren(st[i+1:])
            ans /= tmp
            i += idx+1
        else:
            return ans, i
    return ans, i

def first(st):
    ans, idx = second(st)

    i = idx
    while i < len(st):
        if st[i] == "+":
            tmp, idx = second(st[i+1:])
            ans += tmp
            i += idx+1
        elif st[i] == "-":
            tmp, idx = second(st[i+1:])
            ans -= tmp
            i += idx+1
        else:
            return ans, i
    return ans, i

def calc(s):
    if s.count("(") != s.count(")") or re.search("[^\+\-\*\/()0-9]", s):
        return "ERROR"
    else:
        return first(s)[0]

数式において、優先順位は
括弧 > 掛け算割り算 > 足し算割り算
です。
それぞれの処理を順に paren、second、first 関数にまかせています。

calc(s)

最初に数式が入る関数。
括弧の数が合わなかったり、四則演算に相応しくない文字が含まれている場合はERRORを返し、適切な文字列である場合はfirst関数に渡します。

paren(st)

first() -> second() と入力がたらい回しにされて一番最初に処理が行われる関数。
”(”が最初にある場合は”(”の1つ先以降をfirstに渡します(新たな式として処理)。
数字が最初にある場合は数字が続くまでインデックスを進め、intに変換します。
返り値は
(計算値,進んだインデックス)
になります。

second(st)

parenの次に処理される関数
parenで得られた値ansに対し、次にある値を掛けるか割るかします。
返り値はparenと同様です。

first(st)

secondの次に処理される関数
上記同様、こちらは足すか引くかします。
返り値も同様です。


最後に

インデックスの管理が大変だった・・・
"+("や"-("を直接paren内でやろうとしてめっちゃ詰まった・・・
(firstで+,-の後をsecondに渡せば完了)


楽天のインターンに行っていた話

はじめに

こんにちは、ami_GSです。
8/18 ~ 9/12 (1ヶ月)の日程で楽天インターンに行っていた話をまとめていなかったので書こうと思います。
こちら同様、あまり詳しい内容を書いて問題にしたくはないのでかる〜〜く書きます。

どうして楽天インターン

こちらのインターン詳細ページにある、”プライベートクラウドサービスディベロッパー”というポジションのインターンに参加しました。
応募当初は個別ページに詳細が書いてあったのですが、もう閉じられているようですね。

応募した理由としては、

  1. クラウド(低レイヤ技術)に興味があった
  2. 英語を活かしたエンジニア業を体験したかった
  3. 募集要項の、望ましいスキルにPythonがあった(気がする)
  4. お昼ごはんが美味しそうだった

等があげられます。



何したの?

クラウドプラットフォームと言えば、AWSGCP、Azure等がありますが、
それらの機能をオープンソースで実現するOpenStackというプロジェクトのバグ修正をしました。

OpenStackはPythonで書かれているため取っ付きやすかったです。コード全体はメチャクチャでかくて全てを把握するのは難しいですが・・・


詳しく書いていいのかわからないので避けますが、僕の本名でOpenStackのコミュニティを漁るとバグ修正のやりとりが見つかるので見てみるといいでしょう。

その他にはChef、Serverspecを書いてサーバ構築をしたりもしました。これもOpenStackに関する事ですね。



で、どうだった?

簡単にまとめると、

クラウド技術への興味が強まった

最初はGCPインスタンスで遊ぶ程度の知識でしたが、その中身のコードを弄ってみてどんどん興味が湧いた記憶があります。OpenStackは機能毎にプロジェクトが別れており、その機能について調べるのも楽しかったです。コード自体が比較的読みやすいってのも良いですね。

メッチャ調べた

トラブルの連続でめちゃめちゃ調べました・・・。
OpenStackのインスコ方法、ネットワーク周り、当時最新のWindowsVirtualBoxのバグ、ubuntu最新版?のgitのバグ等々・・・いやぁ〜大変でしたね〜〜(´・ω・`)。
メッチャ調べた分、とてもいい勉強になったと思います。

そこそこ英語話した

募集要項には、
英語 ◎、日本語 ☓
と書いてあり不安だったのですが、僕のチームは日本語メインで話していました。
もちろん外国のエンジニアさんもいたのですが、彼らは日本語を話せるタイプだったので日本語がメインでした。
もちろん英語も使いました。Chefのプロフェッショナルの方や、同時にインターンで入った中国の方とは英語で話しましたね。あとはアメリカの方と1対1でお昼ごはんを食べる時も英語でした。

「英語が社内公用語」と言うイメージが強かったのですが、やはり部署、チームに依るらしいです。

最近以下の記事を見つけましたが英語を必須としているのは、目標へ向かって努力できる優秀な人材を集めるためというのが大きいのだと思います。(僕の憶測です)
楽天社員だったけど質問ありますか? ぶる速-VIP

エンジニアさんのレベルが総じて高い

詳しく書いていいかわからないので避けますが、自分の知らない便利技術を沢山教えてもらいました。
例えばtmux、screen、git便利コマンドなどなど。。。
これらは今でも活用していて、本当にいい勉強になったと思います。

お昼ごはん美味しかった

これはデカイ!
また、その日のメニューのカロリーやタンパク質、脂質が書いてあり、女性やビルダーには結構良い環境だと思います!



総括

メンターさん、技術指導をしてくれた方達に面白い人が多くて楽しかったです。
インターン後に開催されたRakuten Technology Conference 2014に遊びに行った時も僕を覚えていてくれて嬉しかったですね。

ただ、このインターンで一番大事なのは自主性だと感じました。
自分で発見したトラブル(上記)を解決したり、ある問題について自身の解決法を考え、議論する等が重要でした。まぁこれが楽しいんですけどね。

matplotlibでboxplotの調べたことまとめ

はじめに

久しぶりです。研究やら授業でてんてこ舞いでした。

研究のためにboxplotで試行錯誤した結果のまとめを書こうと思います。



グループ化、及びbox内の塗りつぶし

普通にプロットすると、全部同じ色になってしまいます。
1つのグループに対し、複数のデータ群がある場合、同じ色だけで表現するのは非常に見難くなります。
なので、グループ毎にbox内を塗りつぶしてプロットしてみましょう。

今回は、3つの国にある、バナナ(黄色)ときゅうり(緑)の長さを例にしましょうかね、値は適当です。

from matplotlib import pyplot as plt
from scipy import stats

data = []
with open('./lengthData.txt', 'r') as f:
    for line in f.readlines():
        data.append(map(int, line.split())) # テキストデータから数値に変換してdataに格納

groupData = []
for i in range(3):
    tmpData = []
    for j in range(2):
        tmpData.append(data[i*2+j])  #(例えば) 3行2列の配列を作ります
    groupData.append(tmpData) #3サンプルそれぞれが2つのデータ群を持つ配列の完成

for i in range(3):
    #添字iでグループ指定、patch_artistはbox内の塗りつぶしです。
    #1グループの描画
    bp = plt.boxplot(groupData[i], positions=[2*i+1,2*i+2], widths=0.8, patch_artist=True) 
    bp['boxes'][0].set_facecolor('yellow') #黄色(バナナ)
    plt.setp(bp['medians'][0], color='red', linewidth=2) #メディアン
    plt.setp(bp['caps'][0], linewidth=1.5) #はこ髭の上限の線の太さ
    plt.setp(bp['caps'][1], linewidth=1.5) #はこ髭の下限の線の太さ
    plt.setp(bp['whiskers'][0], linewidth=2) #boxから伸びる上の点線の太さ
    plt.setp(bp['whiskers'][1], linewidth=2) #boxから伸びる下の点線の太さ

    bp['boxes'][1].set_facecolor('green') #緑(きゅうり)
    plt.setp(bp['medians'][1], color='red', linewidth=2)
    plt.setp(bp['caps'][2], linewidth=1.5) #同上(添字に注意!)
    plt.setp(bp['caps'][3], linewidth=1.5)
    plt.setp(bp['whiskers'][2], linewidth=2)
    plt.setp(bp['whiskers'][3], linewidth=2)

レジェンドを付ける

boxplotではデフォルトでレジェンドを付けることは出来ません。
(デフォルトのプロットの色が全部同じだから)
せっかく前節で色を付けたので、レジェンドを付けてみましょう。

banana, = plt.plot([1,1], 'y-', linewidth=10) #実際に線をプロットします。
cucumber, = plt.plot([1,1], 'g-', linewidth=10)

plt.legend((banana, cucumber), ('banana', 'cucumber')) #レジェンドの表示
banana.set_visible(False) #プロットした線を見えなくします。
cucumber.set_visible(False)

一度プロットしてからレジェンドを表示し、プロットした線を見えなくする、という手順です。




細かい設定

その他、プロットを見やすくするための設定です。

plt.ylim(0,40)
plt.yticks([i*20 for i in range(5)], ['0','10','20','30','40'], fontsize=17)
plt.ylabel('Length', fontsize=18)
plt.xticks([i for i in range(8)], ['','JP', 'JP', 'US', 'US', 'UK', 'UK', ''], fontsize=20)
plt.xlabel('Country', fontsize=18)
plt.grid()


以上の設定でプロットしたものが以下になります。
f:id:ami_GS:20140704103028j:plain



Pythonでしゃくとり法

はじめに

こんにちは、なかなか研究が軌道に乗らなくてツラいマンです。

今回はPythonでしゃくとり法を使う場面があったので載せていこうかなと。

しゃくとり法とは

与えられた配列の中から、ある条件を満たす最大の範囲を見つけるアルゴリズムです。

「左端(left)と右端(right)に配列の添字を入れ、rightをインクリメントしつつ、条件を満たさない要素を含んでしまった時に条件を満たさない要素が外にでるまでleftを進める」

という感じ?わかりにくいっすね

使用例

B: 細長いお菓子 - AtCoder Regular Contest 022 | AtCoder
を使います。

問題の解き方として、leftとrightの間に、同じ数字が入らないよう調整、その長さを取る。
という感じです

以下コード

N = int(raw_input())
A = map(int, raw_input().split())

left = 0 #左端初期値
ans = 1 #解初期値(0だとAの要素が1つだけの時にうまくいかない)
#rightはleftの1つ右からスタート
for right in range(1,N):
    #rightをインクリメントした後、同じ要素が含まれていた場合leftをインクリメント
    while A[right] in A[left:right]:
        left += 1
    ans = max(ans, right-left+1) #大きい方に更新
print ans


こんな感じです。
rightを進めてleftを縮める動きがしゃくとり虫っぽいからしゃくとり法なんですね、多分。

ちなみに

この解法だと最後のテストケースでTime Limite Exceededがでてしまい99点止まりです。
これを抜ける方法募集してます!(切実)


最後に

絶賛競技プログラミング練習中です。
アルゴリズムに苦手意識があるので覚えられる解法は全部覚える勢いで行っちゃいましょうね〜〜。

matplotlibの複数subplotをアニメーションで動かすヤツ

はじめに

こんにちは、GWですが周りの人の予定が合わなくて引きこもってるマンです。

題名の通り、matplotlibでの複数subplotをアニメーションで動かしていこうと思います。
研究で複数チャンネルの信号をリアルタイムで見たいと思い、書いてみたものです。

matplotlibとは?

簡単に言うとMATLABのplot系と同じような処理ができるようになるライブラリです。
データはnumpyの型をそのまま使えます。
少々速度に問題がありそうですが、MATLABが無かったり、サクッとプロットを出したい人には便利かも。

コード

matplotlibでアニメーションをする際には、matplotlib.animation.FuncAnimation()関数を使います。
引数にジェネレータ関数を取るので若干複雑(に見える)かも知れません。


コードは以下のとおり。

from matplotlib import animation
from matplotlib import pyplot as plt
import numpy as np

channelNum = 3       #チャンネル数
samplingrate = 100   #サンプリングレート
sample = 10          #1回のループで取るサンプル数
XMIN, XMAX = 0, 2    #x軸(時間を表す)
YMIN, YMAX = -1, 1   #y軸
INTERVAL = 1000/samplingrate*sample  #アニメーションさせる速さ(ms)
TIME = np.linspace(XMIN, XMAX, samplingrate*(XMAX-XMIN)) #x軸にサンプル点を置いておく

fig = plt.figure()
ax = []
plots = []
ydata = []

for i in range(channelNum):
    tmpax = fig.add_subplot(channelNum,1,i) #チャンネル数だけサブプロット作成
    tmpax.set_xlim(XMIN,XMAX)
    tmpax.set_ylim(YMIN, YMAX) #x,y軸設定
    ax.append(tmpax)
    ydata.append(np.zeros(0))
    plots.append(tmpax.plot(np.zeros(0), ydata[i])[0]) #チャンネルごとのプロット領域にデータをセット

def gen():
    while True:
        data = np.random.rand(sample, channelNum)*2-1  #チャンネルごとにsample分だけランダムな値を取る
        if ydata[0].shape[0] + len(data[:, 0]) >= len(TIME):
            initData() #プロットが右端まで達したらデータ初期化
        yield data

def initData():
    for i in range(channelNum):
        ydata[i] = np.zeros(0) #空にする

def updataData(data):
    for i in range(channelNum):
        ydata[i] = np.append(ydata[i], data[:, i])
        plots[i].set_data(TIME[:ydata[i].shape[0]], ydata[i])

    return plots  #データが格納された配列を返す

if __name__ == "__main__":
    #updateDataに第3引数のジェネレータ(gen)から返されたデータが引数として渡される
    #Macの場合はblit=Falseでないと動かない?
    ani = animation.FuncAnimation(fig, updataData, gen, blit=False, interval=INTERVAL)
    plt.show()

このような感じになります。

channelNum=6でプロットした物が以下になります。
f:id:ami_GS:20140504153901p:plain

アニメーションするのは1つで結構という方はchannelNumを1にしてください。
channelNumが3くらいであれば問題なく動くのですが、10くらいの大きさになると結構遅くなるので注意。

最後に

もしチャンネル数を多くした時の速度が気になるのであれば、tkinter, pyside等のGUIを使ってキャンバスに書き込んだほうが速いと思います。

今回はmatplotlibの紹介ということで許してちょんまげ!