サイバーエージェントのCA Tech Dojo-サーバーサイド(Go)編-に参加しました

f:id:wada0421514:20210403130834p:plain

CA Tech Dojoについて

3週間でゲームAPIを開発します

参加者4名に対して3名のメンターさんが付いてくれます

www.cyberagent.co.jp

参加までの流れ

  1. 書類選考
  2. 人事の方との面接
  3. 予習

1. 書類選考

割愛

2. 人事の方との面接

質問の例は以下です

  • 自己紹介
  • 業種とその理由
  • 職種とその理由
  • 開発経験
  • 自分の強みと弱み
  • インターン参加後のイメージ
  • 逆質問

3. 予習

面接で教えていただいた以下のリンクを参考に予習しました

やったこと

3週間でRESTのゲームAPIを開発しました。

基礎課題と応用課題があります。僕は基礎課題1週間半、応用課題1週間半かかりました。

開発したのは以下のようなゲームのサーバサイドです。

基礎課題

基本的な処理を実装しました

  • ユーザの管理
  • インゲームの結果を記録
  • ランキングを表示
  • ガチャを引く
  • コレクション一覧を表示

応用課題

基礎課題が終わった時点で、ゲームは仕様通りに動くようになります。

応用課題では、各々がやりたいことをやります。

僕がやったことは以下です。

  • gomock, sqlmockを使って単体テスト
  • Github Actionsによる単体テストの自動実行
  • Zapを使ったアプリケーションのロギング
  • サーバのアプリケーションをDocker上で実行
  • Wireによる初期化関数の自動生成
  • マスタ系データをRedisでオンメモリ化
  • gRPCを利用した通信に変更

技術面の学び

たくさんあります。今回は割愛します。他の記事で書きたいと思います。

コードはGitHubに上げてあります。

github.com

意識面の学び

様々な選択肢を検討して丁寧に実装する

  • 使えそうなフレームワークはあるか?どのフレームワークが適切か?
  • 実行時間をもっと早くできないか?メモリに無駄はないか?
  • コードは見辛くないか?
  • バグは起こらないか?起こりにくい実装になってるか?

大きな問題は細かく分割して取り組む

  • 一気に作らない
  • PRは小さく出す

発表は具体例・図・画像を入れると効果的

同期の記事

note.com note.com hiyoko-coder.hatenablog.com ariku1021.hatenablog.com

成績優秀者に選ばれて表彰されました

横浜国立大学 情報工学専攻では成績優秀者3名が表彰されます

そのうちの1人に選ばれました

表彰について

画像左が大学からいただいた表彰状で、画像右が電子情報工学会からいただいた表彰状です

電子情報工学会の方では副賞として1万円いただきました

成績について

横浜国立大学はMAX4.5で成績をつけます

画像は、僕の成績と、横浜国立大学 理工学部4年生の成績分布です

成績分布の赤色部分が僕なので、理工学部4年生 約700名のうちの上位7名であることがわかります。

近所のスーパーの野菜価格を簡単に比較できるWEBサービスの開発

キャベツ・レタス・トマトなど、価格の変動が大きい野菜について、

近所のスーパー5店舗、オンラインスーパー3店舗、去年の平均価格、今後の価格の見通し、などを比較することができます

使用言語・技術

以下で説明しています。

github.com

外出目的・帰宅予定時刻を管理するWEBサービスの開発

概要

玄関に端末が設置してあります

  1. 名前を選択します

  2. 外出目的を選択します

  3. 帰宅予定時刻を選択します

  4. 結果がLINEグループに送信されます

使用言語・技術

項目 言語・技術
フロントエンド HTML, CSS
バックエンド Ruby on Rails
サーバ Heroku

実装の手順

  1. Ruby on Rails, HerokuでWebアプリを実装

    RailsのViewに複数のsubmitボタンを設置して押されたボタンの値を取得する - Qiita

  2. Ruby on Rails, LINEBot APIでボタン入力内容をLINEグループに送信

    【Rails】LINEBotでPush送信 - Qiita

ベイジアンネットで遅刻の原因推定

[やること]
ベイズの定理、条件付確率を使ったベイジアンネットで、遅刻の原因を推定します

[ベイジアンネットについて]
事象をノード、条件付確率をエッジにして、ネットワークを構築して、原因推定、意思決定モデルなどを行う手法です。
参考文献[1]がわかりやすいです。

[結果1]
条件付確率からベイジアンネットを構築しました
以下がそれぞれの事象の説明です
B:BeLate:遅刻する
O:OverSleep:寝坊する
Ti:Tired:昨日疲れてた
S:StayUpLate:昨日夜更かしした
Tw:TweetALot:昨日のツイート数が多い

f:id:wada0421514:20200329222528p:plainf:id:wada0421514:20200329222531p:plain

[結果2]
遅刻した時に、昨日夜更かしした確率を計算します
P(S=1|B=1)です

P(S=1|B=1) = P(S=1,B=1)/P(B=1)です。

まず、分母P(B=1)から計算します
P(B=1) = P(B=1|Ov=1)P(Ov=1)+P(B=1|Ov=0)P(Ov=0)

P(B=1)の計算には、P(Ov=1)の値が必要です。
P(Ov=1)=P(Ov=1|Ti=[1,0],S=[1,0])P(Ti=[1,0])P(S=[1,0])
=0.8*0.5*0.4+0.5*0.4*0.5+0.6*0.6*0.5+0.4*0.6*0.5
=0.16+0.1+0.18+0.12
=0.56

分母P(B=1)が求まります。
P(B=1)=0.9*0.56+0.1*0.44=0.548

分子P(S=1,B=1)を計算します
P(S=1,B=1)
=P(S=1,B=1,Ti=[1,0],Ov=[1,0])
=P(B=1|Ov=[1,0])P(S=1,Ti=[1,0],Ov=[1,0])
=P(B=1|Ov=[1,0])P(Ov=[1,0]|Ti=[1,0],S=1)P(Ti=[1,0])P(S=1)
=0.9*0.8*0.4*0.5+0.8*0.6*0.6*0.5+0.4*0.2*0.4*0.5+0.4*0.4*0.6*0.5
=0.352

遅刻した時に、昨日夜更かしした確率が求まります。64%です。
P(S=1|B=1)=0.352/0.548=0.64

[結果3]
まず、寝坊した時に、夜更かしした確率を計算します。
その後、「昨日のツイート数が多い」という事象が加わった場合を計算します
P(S=1|O=1)、P(S=1|O=1,Tw=1)です

P(S=1|Ov=1)=P(S=1,Ov=1)/P(Ov=1)です。

分母P(Ov=1)は結果2で求まっています。
P(Ov=1)=0.56

分子P(S=1,Ov=1)を計算します
P(S=1,Ov=1)=P(Ov=1|S=1,Ti=[1,0])P(S=1)P(Ti=[1,0])
=0.8*0.5*0.4+0.6*0.5*0.6
=0.16+0.18
=0.34

寝坊した時に、夜更かしした確率が求まりました。61%です。
P(S=1|Ov=1)=0.34/0.56
=0.61

次に、「昨日のツイート数が多い」という事象が加わった場合を計算します

P(S=1|Tw=1,Ov=1)=P(S=1,Tw=1,Ov=1)/P(Tw=1,Ov=1)です。

分母P(Tw=1,Ov=1)を計算します
P(Tw=1,Ov=1)
=P(Tw=1,Ov=1,Ti=[1,0],S=[1,0])
=P(Ov=1|Ti=[1,0],S=[1,0])P(Tw=1,Ti=[1,0],S=[1,0])
=P(Ov=1|Ti=[1,0],S=[1,0])P(Ti=[1,0])P(Tw=1|S=[1,0])P(S=[1,0])
=0.8*0.4*0.7*0.5+0.5*0.4*0.5*0.5+0.6*0.6*0.7*0.5+0.4*0.6*0.5*0.5
=0.348

分子P(S=1,Tw=1,Ov=1)を計算します
P(S=1,Tw=1,Ov=1)
=P(S=1,Tw=1,Ov=1,Ti=[1,0])
=P(Ov=1|Ti=[1,0],S=1)P(Ti=[1,0])P(Tw=1|S=1)P(S=1)
=0.8*0.4*0.7*0.5+0.6*0.6*0.7*0.5
=0.238

「昨日のツイート数が多い」という事象が加わった場合の、寝坊した時に、夜更かしした確率が求まりました。68%です。少し確率が上がっています。直感と一致する結果です。
P(S=1|Tw=1,Ov=1)=0.238/0.348
=0.68

[今後の展望]
ベイジアンネットによる計算を自動化したいです

[参考文献]
[1]ベイジアンネットワーク
https://www.sist.ac.jp/~kanakubo/research/reasoning_kr/bayesiannetwork.html

最適なラーメンハシゴルート

[やること]
駅とラーメンのお店をネットワークで表現して、一番満足度の高いラーメンハシゴルートを計算します。
知識グラフを扱う練習が目的です。

[結果1]
青ノードが駅、赤ノードがラーメンのお店です。実際にあるお店です。
赤ノードの大きさが、ラーメンのお店の個人的なスコアです
エッジの数字はラーメンの値段、電車賃などを表します
f:id:wada0421514:20200329201246p:plain

[結果2]
以下のプログラムで、以下の設定で、スコアが一番高いルートを探索しました
設定:東京駅スタート、ラーメン2店舗ハシゴが上限
結果:['TOKYO', 'Rokurinsha', 'SHINJUKU', 'Konjiki']、1980円

[プログラム]

import networkx as nx
import matplotlib.pyplot as plt
import itertools

class Noodle():

    def __init__(self):
        #ネットワーク読み込み
        self.G = nx.read_edgelist('nodelist.txt', nodetype=str)
        nx.set_node_attributes(self.G, name='score', values=200)
        #スコア
        self.G.nodes["Rokurinsha"]["score"]=850
        self.G.nodes["Konjiki"]["score"]=900
        self.G.nodes["Ozeki"]["score"]=800
        self.G.nodes["TOKYO"]["score"]=0
        self.G.nodes["SHINJUKU"]["score"]=0
        self.G.nodes["SHIBUYA"]["score"]=0
        #コスト
        self.edge_labels={}
        self.edge_labels[("SHINJUKU","TOKYO")]=200
        self.edge_labels[("TOKYO","SHINJUKU")]=200
        self.edge_labels[("SHIBUYA","TOKYO")]=200
        self.edge_labels[("TOKYO","SHIBUYA")]=200
        self.edge_labels[("SHIBUYA","SHINJUKU")]=160
        self.edge_labels[("SHINJUKU","SHIBUYA")]=160
        self.edge_labels[("TOKYO","Rokurinsha")]=830
        self.edge_labels[("TOKYO","Hyottoko")]=670
        self.edge_labels[("TOKYO","Oboroduki")]=850
        self.edge_labels[("TOKYO","Hachigo")]=850
        self.edge_labels[("SHINJUKU","Gonokami")]=800
        self.edge_labels[("SHINJUKU","Funji")]=700
        self.edge_labels[("SHINJUKU","Konjiki")]=950
        self.edge_labels[("SHINJUKU","Sho")]=700
        self.edge_labels[("SHINJUKU","Hayashida")]=800
        self.edge_labels[("SHIBUYA","Kiraku")]=700
        self.edge_labels[("SHIBUYA","Hayashi")]=800
        self.edge_labels[("SHIBUYA","Ozeki")]=830

    #ネットワークをプロットする
    def plot(self):
        #色、サイズなど
        pos = nx.spring_layout(self.G, k=0.8)
        nx.draw_networkx_edges(self.G, pos, edge_color='y')
        c = ['blue' if n.isupper() else 'red' for n in self.G.nodes()]
        score_size = [1000 if n.isupper() else self.G.nodes[n]["score"] for n in self.G.nodes()]
        nx.draw_networkx_nodes(self.G, pos, node_color=c, alpha=0.5, node_size=score_size)
        nx.draw_networkx_edge_labels(self.G, pos, edge_labels=self.edge_labels, font_size=24)
        nx.draw_networkx_labels(self.G, pos, font_size=24)
        plt.figure(figsize=(10, 8))
        plt.axis('off')
        plt.show()

    #ルートのコスト計算
    def costscore_cal(self,route):
        score = 0
        cost = 0
        for a in range(len(route) - 1):
            if route[a] == route[a + 1]:
                continue
            else:
                if not (route[a].isupper()) and route[a + 1].isupper():
                    continue
                else:
                    cost = cost + self.edge_labels[(route[a], route[a + 1])]
                    score = score + self.G.nodes[route[a + 1]]["score"]
        return score, cost

    #店の一番近い駅
    def store2station(self,storename):
        station = {'Rokurinsha': 'TOKYO', 'Hyottoko': 'TOKYO', 'Oboroduki': 'TOKYO', 'Hachigo': 'TOKYO',
                   'Gonokami': 'SHINJUKU', 'Funji': 'SHINJUKU', 'Konjiki': 'SHINJUKU', 'Sho': 'SHINJUKU',
                   'Hayashida': 'SHINJUKU',
                   'Kiraku': 'SHIBUYA', 'Hayashi': 'SHIBUYA', 'Ozeki': 'SHIBUYA'}
        return station[storename]

    #全ルート生成
    def allroutes(self, noodle_count):
        noodle_shop = ('Rokurinsha', 'Hyottoko', 'Oboroduki', 'Hachigo',
                       'Gonokami', 'Funji', 'Konjiki', 'Sho', 'Hayashida',
                       'Kiraku', 'Hayashi', 'Ozeki')
        all_routes = ()
        for a in range(noodle_count):
            all_routes = all_routes + tuple(itertools.permutations(noodle_shop, a + 1))

        return all_routes

    #一番スコアが高いルート探索
    def allroutes_costscore_cal(self,allroutes, start_station):
        result_route = []
        for noodle_route in allroutes:
            list_kari = []
            for a in noodle_route:
                list_kari.append(start_station)
                list_kari.append(self.store2station(a))
                list_kari.append(a)
                list_kari.append(self.store2station(a))
            result_route.append(list_kari)

        result_score = []
        result_cost = []

        for a in result_route:
            score, cost = self.costscore_cal(a)
            result_score.append(score)
            result_cost.append(cost)

        result_route=result_route[result_score.index(max(result_score))]
        result_cost=result_cost[result_score.index(max(result_score))]

        return list(dict.fromkeys(result_route)),result_cost

if __name__=='__main__':

    n=Noodle()
    #2店舗はしごが限界
    allroutes=n.allroutes(2)
    #東京駅スタート
    result_route,cost=n.allroutes_costscore_cal(allroutes,"TOKYO")
    #結果:['TOKYO', 'Rokurinsha', 'SHINJUKU', 'Konjiki']
    print(result_route)
    #結果:1980円
    print(str(cost)+"円")
    n.plot()

[参考文献]
[1]Pythonでネットワークを分析・可視化しよう!必要手順まとめ
https://www.sejuku.net/blog/91371

[2]【Python】NetworkX 2.0の基礎的な使い方まとめ
https://qiita.com/kzm4269/items/081ff2fdb8a6b0a6112f

カオスの軌跡のプロット

[やること]
微分方程式で表現されるカオスをPythonで解いて、軌跡をプロットします。

[カオスについて]
カオスは式で明確に定義されているのに、初期値の選び方で将来の状態が予測できない現象のことです。
参考文献[1][2]がわかりやすいです

[結果1]
ローレンツ方程式

f:id:wada0421514:20200329140013p:plain

p,r,b=10,28,8/3

初期値 x,y,z=1,1,1
f:id:wada0421514:20200329135713p:plain
初期値 x,y,z=1.0001,1,1
f:id:wada0421514:20200329135717p:plain
初期値 x,y,z=1.001,1,1
f:id:wada0421514:20200329135721p:plain
初期値 x,y,z=1.01,1,1
f:id:wada0421514:20200329135727p:plain

横軸に時間、縦軸にxを取ったプロットです。
初期値が少し変わるだけで軌跡が大きく変わることがわかります。

f:id:wada0421514:20200329140511p:plain


[結果2]
レスラー方程式
f:id:wada0421514:20200329140830g:plain

a,b,c=0.2,1.0,5.7

初期値 x,y,z=1.0,0.0,0.0
f:id:wada0421514:20200329140958p:plain
初期値 x,y,z=8.01,0.0,0.0
f:id:wada0421514:20200329141003p:plain
初期値 x,y,z=8.02,0.0,0.0
f:id:wada0421514:20200329141007p:plain

[プログラム]
微分方程式を解くライブラリodeintについては、参考文献[3]がわかりやすいです

import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

#レスラー方程式
def rossler(var,t,a,b,c):
    x_next=-var[1]-var[2]
    y_next=var[0]+a*var[1]
    z_next=b*var[0]-(c-var[0])*var[2]
    return np.array([x_next,y_next,z_next])

#ローレンツ方程式
def lorentz(var,t,p,r,b):
    x_next=-p*var[0]+p*var[1]
    y_next=-var[0]*var[2]+r*var[0]-var[1]
    z_next=var[0]*var[1]-b*var[2]
    return np.array([x_next,y_next,z_next])

if __name__ == "__main__":

    #レスラー方程式のパラメータと初期値
    #abc=(0.2,1.0,5.7)
    #xyz_first=[1.0,0.0,0.0]

    #ローレンツ方程式のパラメータと初期値
    prb=(10,28,8/3)
    xyz_first=[1.01,1,1]

    #ステップ数
    t=np.linspace(0,1000,100000)

    #微分方程式を解く
    #var=odeint(chaos,xyz_first,t,args=abc)
    var=odeint(lorentz,xyz_first,t,args=prb)

    #グラフの枠を作っていく
    fig = plt.figure()
    ax = Axes3D(fig)

    #軸にラベルを付ける
    ax.set_xlabel("X")
    ax.set_ylabel("Y")
    ax.set_zlabel("Z")

    #プロットして表示
    ax.plot(var[:, 0], var[:, 1], var[:, 2], marker="o", linestyle='None')
    plt.show()


[参考文献]
[1]カオスアトラクタとは
http://www.gifu-nct.ac.jp/elec/deguchi/sotsuron/hisaki/node9.html#eqresura
[2]カオス
http://www.aoni.waseda.jp/yuuka/Sim/Chaos.html
[3]Python運動方程式を解く(odeint)
https://qiita.com/binaryneutronstar/items/ad5efa27fd626826846f