強化学習 - Tic-Tac-Toe
強化学習 - Tic-Tac-Toe
三目並べ、マルバツゲーム、Tic-Tac-Toeというそうです。強化学習(Q-Learning)のまとめとしてチャレンジしてみました。Googleで「Tic-Tac-Toe」と検索すると三目並べで遊べます。
先に結果
ランダムな相手(後手)には80%近い確率で勝てるようになりました。でも実際に対戦してみると「ちょっとかしこいかな?」くらいの印象です。今日の勢いで作ったのでプログラムの細かいところに不備があるかも?しれません。。あまり参考にならないかも。
設定など
- アルゴリズム
- Q-Learning
- 報酬
- 勝ち:1
- 負け:-1
- 引き分け:0
- 引き分けも多いので、報酬としてプラスマイナスがあるのも良いのかも。
- 状態
- 3**9 = 19683とおり
- 行動
- 9マスあるので9とおり
- キーボード入力時は0〜8
- 既に入力済みの場所を選択した場合は、ランダムで配置するようにしています。
- このせいで勝率はやや落ちるかも。
- 9マスあるので9とおり
- パラメータなど
- 学習回数(エピソード数):2,000,000くらい(他のサイトを見るともっと少なく良さそう)
- 学習率:0.1
- gamma:0.9
所感
- マルバツゲーム本体のプログラムと、強化学習プログラムのインタフェース設計に少し迷った。
- OpenAI Gymのインタフェースに習って、マルバツゲームにstep関数を実装した。
- 学習後の勝率が60%で停滞した。
- εの更新式を間違っていたため、Qテーブルの値に依存した学習を繰り返していた模様
- そもそもマルバツゲーム本体のロジックに不備があったり。
- テストコードを書こう。
- Qテーブルの初期値も迷った。
- -1〜1のようなランダムな値も試したが、あまり効果はなさそうだったのでとりあえず0にした。
- 常に先手("x")をAIとした。
- 学習済みのAIを後手で起動すると精度が下がる。
作成したファイル
まずは強化学習用のプログラムです。
- tictactoe.py
- マルバツゲーム本体プログラム
- train_tictactoe.py
- Q-Learningで学習するプログラム
- ランダムな打ち手(後手)と繰り返し対決します。
- 学習後Qテーブルの内容をnumpyのファイルとして出力します。
以下は学習済みプログラムを動作させるためのプログラムです。
- play_test_tictactoe.py
- 学習済みのQテーブルを使って検証するプログラム
- play_tictactoe.py
- キーボード入力で対決できるプログラム
tictactoe.py
- マルバツゲーム本体プログラム
- 先手、後手ともにランダムで動作します。
import numpy as np class TicTacToe: def __init__(self, printable=False): self.reset(printable) def show(self): if self.printable: print(self.board[0], "|", self.board[1], "|" ,self.board[2]) print("----------" ) print(self.board[3], "|", self.board[4], "|" ,self.board[5]) print("----------" ) print(self.board[6], "|", self.board[7], "|" ,self.board[8]) def reset(self, printable=None) : self.board = [" ", " ", " ", " ", " ", " ", " ", " ", " "] self.player1 = "x" self.player2 = "o" self.player = self.player1 self.done = False if printable is not None: self.printable = printable return self.getObservation() def put(self, number) : if number < 0 or 8 < number: return False if self.board[number] != " ": return False self.board[number] = self.player return True def judge(self): if self.win() or self.draw(): self.done = True return self.done def win(self): return self.player == self.board[0] and self.player == self.board[1] and self.player == self.board[2] \ or self.player == self.board[3] and self.player == self.board[4] and self.player == self.board[5] \ or self.player == self.board[6] and self.player == self.board[7] and self.player == self.board[8] \ or self.player == self.board[0] and self.player == self.board[3] and self.player == self.board[6] \ or self.player == self.board[1] and self.player == self.board[4] and self.player == self.board[7] \ or self.player == self.board[2] and self.player == self.board[5] and self.player == self.board[8] \ or self.player == self.board[0] and self.player == self.board[4] and self.player == self.board[8] \ or self.player == self.board[2] and self.player == self.board[4] and self.player == self.board[6] def draw(self): return (" " in self.board) == False def changePlayer(self): if self.player == self.player1: self.player = self.player2 else: self.player = self.player1 def printStart(self): if self.printable: print("Tic Tac Toe Start!", flush=True) def printEnd(self): if self.printable: print("Tic Tac Toe End!", flush=True) def printChoice(self): if self.printable: print("Choice! (0,1,2,3,4,5,6,7,8)", flush=True) def printChoiceInvalid(self): if self.printable: print("Your choice is invalid", flush=True) def printWin(self): if self.printable: print(self.player, "Win!", flush=True) def printDraw(self): if self.printable: print("Draw!", flush=True) def printSpace(self): if self.printable: print(flush=True) def getRandomAction(self): return np.random.randint(0, 9) def start(self): self.printStart() while True: action = self.getRandomAction() (_, _, done, _) = self.step(action) self.show() self.printSpace() if done: self.show() if self.win(): self.printWin() else: self.printDraw() break self.printEnd() def step(self, action): while True: if self.put(action) : break else: action = self.getRandomAction() reward = 0 if self.judge() : if self.win(): if self.player == self.player1: reward = 1 else: reward = -1 else: reward = 0 else: self.changePlayer() reward = 0 return (self.getObservation(), reward, self.done, {}) def getObservation(self): return (self.board, self.player, self.player1, self.player2) if __name__ == "__main__": ttt = TicTacToe(True) ttt.start()
train_tictactoe.py
- 強化学習用のプログラムです。
import numpy as np from tictactoe import TicTacToe import numpy as np def get_action(q_table, state, epsilon): if np.random.uniform(0, 1) <= epsilon: return np.random.randint(0, 9) else: a = np.where(q_table[state] == q_table[state].max())[0] return np.random.choice(a) def board_to_state(board): state = 0 for i in range(0, 9): if board[i] == 'o': state = state + 3**i * 2 elif board[i] == 'x': state = state + 3**i * 1 else: state = state + 3**i * 0 return state def update_q_learning(state, ation, reward, next_state, q_table): eta = 0.1 gamma = 0.9 if reward != 0: q_table[state,action] = q_table[state,action] + \ eta * (reward - q_table[state,action]) else: q_table[state,action] = q_table[state,action] + \ eta * (reward + gamma * np.max(q_table[state,:]) - q_table[state,action]) return q_table np.set_printoptions(precision=6, suppress=True) prefix = "q_table_data_" episode = 100_000 # episode = 2_000_000 # q_table = np.random.uniform(low=-1, high=1, size=(3**9, 9)) q_table = np.zeros((3**9, 9)) # q_table = np.load('q_table_dataa_5000000.npy') ttt = TicTacToe() threshold = 10000 initial_epsilon = 0.5 win = 0 draw = 0 for i in range(1, episode): epsilon = initial_epsilon * (episode - i) / episode my_turn = True observation = ttt.reset() while True: state = board_to_state(observation[0]) action = None if my_turn: action = get_action(q_table, state, epsilon) else: action = ttt.getRandomAction() (observation, reward, done, _) = ttt.step(action) if my_turn: next_state = board_to_state(observation[0]) q_table = update_q_learning(state, action, reward, next_state, q_table) if done: if ttt.win(): if ttt.player == ttt.player1: win = win + 1 else: draw = draw + 1 break my_turn = not my_turn if i % threshold == 0: lose = threshold - win - draw print("episode", i, "/", episode, "win", win, "draw", draw, "lose", lose) win = 0 draw = 0 print(q_table) np.save(prefix + str(episode), q_table)
play_test_tictactoe.py
- 学習結果を確認するプログラムです。
- 学習済みのQテーブルをロードして、10000回の試行結果を出力します。
from tictactoe import TicTacToe import numpy as np def get_action(q_table, state): a = np.where(q_table[state] == q_table[state].max())[0] return np.random.choice(a) def board_to_state(board): state = 0 for i in range(0, 9): if board[i] == 'o': state = state + 3**i * 2 elif board[i] == 'x': state = state + 3**i * 1 else: state = state + 3**i * 0 return state max_episode = 10000 q_table = np.load('q_table_data_5000000.npy') ttt = TicTacToe(False) win = 0 draw = 0 ttt.printStart() for i in range(1, max_episode): my_turn = True observation = ttt.reset() while True: state = board_to_state(observation[0]) action = None if my_turn: action = get_action(q_table, state) # action = np.random.randint(0, 9) else: ttt.printChoice() action = np.random.randint(0, 9) (observation, reward, done, _) = ttt.step(action) ttt.show() ttt.printSpace() ttt.printSpace() if done: if ttt.win(): ttt.printWin() if ttt.player == ttt.player1: win = win + 1 else: ttt.printDraw() draw = draw + 1 break my_turn = not my_turn print("episode", max_episode, "win", win, "draw", draw, "lose", max_episode - win - draw)
play_tictactoe.py
- ユーザと対戦用のプログラムです。
from tictactoe import TicTacToe import numpy as np def get_action(q_table, state): a = np.where(q_table[state] == q_table[state].max())[0] return np.random.choice(a) def board_to_state(board): state = 0 for i in range(0, 9): if board[i] == 'o': state = state + 3**i * 2 elif board[i] == 'x': state = state + 3**i * 1 else: state = state + 3**i * 0 return state q_table = np.load('q_table_5000000.npy') ttt = TicTacToe(True) win = 0 draw = 0 ttt.printStart() my_turn = True observation = ttt.reset() while True: state = board_to_state(observation[0]) action = None if my_turn: action = get_action(q_table, state) # action = np.random.randint(0, 9) else: ttt.printChoice() action = int(input().strip()) # action = np.random.randint(0, 9) (observation, reward, done, _) = ttt.step(action) ttt.show() ttt.printSpace() ttt.printSpace() if done: if ttt.win(): ttt.printWin() if ttt.player == ttt.player1: win = win + 1 else: ttt.printDraw() draw = draw + 1 break my_turn = not my_turn
参考
イプシロンの更新ロジックを参考にさせてもらいました。
改めて読むとわかりやすいです。既に入力済みの場所を選択した場合の制御も入れると勝率上がりそう。エピソード数は10000回で良さそう。
強化学習の始め方
普段やらない、強化学習について少し勉強したのでメモしておきます。
参考書籍
先に参考書籍の紹介です。

Pythonによる深層強化学習入門 ChainerとOpenAI Gymではじめる強化学習
- 作者: 牧野浩二,西崎博光
- 出版社/メーカー: オーム社
- 発売日: 2018/08/17
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
DeepLearningの経験がある人にオススメです。コードによる説明が多く、プログラマー向けの書籍という感じでした。一冊目にちょうど良かったです。個人的にはQ-Learningのサンプルがとてもわかりやすかったです。OpenAI Gymのサンプルも豊富で手を動かす勉強に向いています。後半はRaspberry PIやArduinoを使ったデモも載っています。

つくりながら学ぶ! 深層強化学習 ~PyTorchによる実践プログラミング~
- 作者: 株式会社電通国際情報サービス小川雄太郎
- 出版社/メーカー: マイナビ出版
- 発売日: 2018/06/28
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
こちらも良書です。とても説明が丁寧で読みやすいです。技術の背景や用語の説明もしっかりしているので勉強になります。順番的には2冊目にちょうど良い印象です。あとサンプルプログラムにはアニメーションなども実装されているので、直感的でわかりやすいです。著者の方はブログやQiita等での情報発信も凄くてファンになりました。こんな本書けるようになりたいです。
あともう一冊手元にあるのですが、まだしっかり読めていないので割愛です。
今回勉強するまで、強化学習についてはなんとなく「準備が大変そう」みたいに思っていたのですが、実際やってみるとそうでもなく、今は良い書籍がたくさんあって、ライブラリも豊富で、とても学びやすくなっている印象を受けました。
強化学習の始め方
1週間勉強した感想です。私の場合は上記の2冊が学び始めにちょうど良かったです。
これから強化学習を学ぶ方は、強化学習について全体像をザックリ見ておくと良いです。強化学習にもいろんなアルゴリズムがあります。以下の記事が詳しいです。
アルゴリズムを俯瞰してみると名前にインパクトのあるDQNとか有名ですね。私のように、これから強化学習について学び始める人にとってはQ-Learning、SARSA、方策勾配法あたりから手を付けてみるのが良さそうです。
何を題材にするか
教師あり学習、たとえば画像認識の場合だとMNISTデータセット、簡単な分類問題、クラスタリングの場合はirisデータセットみたいに、すぐに学習に使えるデータセットがあると便利です。
強化学習においてはデータセット、というより、もう少し大きな枠組みになるので「環境」といった方で良いでしょうか。OpenAI Gymを使うと倒立振子(CartPole)やスペースインベーダー、ブロック崩しなどを題材に強化学習を始めることができます。
OpenAI Gymの環境設定も難しくはありませんが、Pythonのライブラリ管理の知識が必要だったり、環境設定特有のトラブルがついてくるので、もっと手軽に始めれる題材ないのかなーと思っていたらやっぱりあるんですね。「Skinner箱」というのが強化学習の入り口のようです。
スキナー箱
マウス(ネズミ)が餌をとるまでの物語です。スキナー箱の中には1匹のマウスがおり、餌を獲得するための2つのスイッチがあります。
- 電源スイッチ
- 押すたびにON/OFFが切り替わる
- 餌スイッチ
- 電源スイッチがONのときに餌が出る
スキナー箱には2つの状態(State)と2つの行動(Action)があります。
初期状態において、マウスはどちらのスイッチを押したら餌が出るのかわかないため、ランダムにスイッチを押すことになります。運が良ければ「電源スイッチ」=>「餌スイッチ」と押すことで、最短2ステップで餌を獲得することができます。
マウスは繰り返し餌の獲得に取り組むことで、学習によってテーブルの値を更新していきます。
以降は、参考書籍のプログラムを参考にスキナー箱について、3つのアルゴリズムの解法をまとめました。
方策勾配法(Policy Gradient Method)
方策(Policy)とは、エージェントがどのように振る舞うかを決めるルールのことです。エージェントとは今回でいうとマウス(の意思決定する部分)のことです。方策は表形式で表現したり、関数で表現したりします。ここでは状態と行動の2x2の表形式で方策を管理します。
import numpy as np def to_pi_softmax(theta): pi = np.zeros((theta.shape)) exp_theta = np.exp(1.0 * theta) for i in range(theta.shape[0]): pi[i,:] = exp_theta[i,:] / np.sum(exp_theta[i,:]) return pi def get_action(pi, state): return np.random.choice([0, 1], p=pi[state]) def get_next_state(state, action): if state == 0 and action == 0: return 1 elif state == 0 and action == 1: return 0 elif state == 1 and action == 0: return 0 else: return 1 def challenge(pi): state = 0 history = [] while True: action = get_action(pi, state) history.append([state, action]) if state == 1 and action == 1: break state = get_next_state(state, action) return history def update_policy_gradient(theta, pi, history): delta_theta = theta.copy() t = len(history) for i in range(theta.shape[0]): for j in range(theta.shape[1]): n_i = len([sa for sa in history if sa[0] == i]) n_ij = len([sa for sa in history if sa == [i, j]]) delta_theta[i, j] = (n_ij - pi[i, j] * n_i) / t return theta + 0.25 * delta_theta theta = np.array([[1.0, 1.0], [1.0, 1.0]]) pi = to_pi_softmax(theta) print(pi) for i in range(1, 100): history = challenge(pi) new_theta = update_policy_gradient(theta, pi, history) new_pi = to_pi_softmax(new_theta) print(len(history), end=" ", flush=True) if i % 10 == 0: print("\n", new_pi) theta = new_theta pi = new_pi print("\n", pi)
方策はPolicyのPをギリシャ文字のπとして表現することが一般的なようです。上記のプログラムの場合、thetaが学習で更新されるパラメータで、方策テーブル(π)では状態ごとにsoftmax関数を使うことで、各行動を割合として管理しています。
実行結果
$ python skinner_policy_gradient.py [[0.5 0.5] [0.5 0.5]] 2 10 6 4 7 2 7 4 7 2 [[0.5055308 0.4944692 ] [0.39641196 0.60358804]] 9 9 2 4 11 7 4 4 2 5 [[0.57791613 0.42208387] [0.3823193 0.6176807 ]] 3 7 10 2 4 4 8 2 2 2 [[0.6332647 0.3667353 ] [0.30543971 0.69456029]] 2 5 2 7 9 8 6 3 13 2 [[0.68658989 0.31341011] [0.33609984 0.66390016]] 5 4 7 2 2 2 2 12 2 6 [[0.69865129 0.30134871] [0.28321905 0.71678095]] 2 2 5 3 2 3 3 2 5 2 [[0.72277102 0.27722898] [0.21997818 0.78002182]] 5 4 2 4 2 2 3 4 2 4 [[0.72245457 0.27754543] [0.2008336 0.7991664 ]] 4 2 2 4 2 2 2 2 2 2 [[0.82114666 0.17885334] [0.16544254 0.83455746]] 6 3 4 2 8 4 2 2 2 2 [[0.85727318 0.14272682] [0.18526107 0.81473893]] 2 2 2 2 2 2 4 2 4 [[0.86500809 0.13499191] [0.15425348 0.84574652]]
実行結果には方策テーブルとマウスの試行回数を10回ずつ出力しています。学習が進むに連れてマウスは試行回数2回で餌にたどり着けるようになります。方策テーブルも状態0においては、行動0をとるようになり、状態1においては行動1をとるように学習できています。あと方策勾配法は強化学習でよく聞く「報酬」とか出てこないんですね。
SARSA
おまけでSARSAに置き換えてみました。SARSAや後のQ-Learningは価値反復法(value iteration)に分類されるようです(この辺、ググるといろんな説明がある)。価値反復法では、報酬や価値(状態価値、行動価値)、マルコフ決定過程やベルマン方程式というキーワードが出てきます。また時間があるときにまとめるかも。。
import numpy as np def get_action(q_table, state, epsilon): if np.random.uniform(0, 1) <= epsilon: return np.random.choice([0, 1]) else: if q_table[state, 0] == q_table[state, 1]: return np.random.choice([0, 1]) return np.argmax(q_table[state,:]) def get_next_state_and_reward(state, action): if state == 0 and action == 0: return (1, 0) elif state == 0 and action == 1: return (0, 0) elif state == 1 and action == 0: return (0, 0) else: return (1, 1) def update_sarsa(state, action, reward, next_state, next_action, q_table): eta = 0.1 gamma = 0.9 if reward == 1: q_table[state,action] = q_table[state,action] + eta * (reward - q_table[state,action]) else: q_table[state,action] = q_table[state,action] + eta * (reward + gamma * q_table[next_state,next_action] - q_table[state,action]) return q_table def challenge(q_table, epsilon): state = 0 action = get_action(q_table, state, epsilon) history = [] while True: history.append([state, action]) [next_state, reward] = get_next_state_and_reward(state, action) next_action = get_action(q_table, next_state, epsilon) q_table = update_sarsa(state, action, reward, next_state, next_action, q_table) if reward == 1: break state = next_state action = next_action return (q_table, history) np.set_printoptions(precision=6, suppress=True) q_table = np.zeros((2, 2)) print(q_table) epsilon = 1.0 for i in range(1, 101): epsilon = epsilon * 0.9 [q_table, history] = challenge(q_table, epsilon) print(len(history), end=" ", flush=True) if i % 10 == 0: print("\n", q_table)
このプログラムでは行動価値関数を変数q_tableで管理しています。q_tableは2x2の状態と行動の表データで初期値を0としています。学習が進むにつれてq_tableの値が調整されていきます。
SARSAの名前の由来はSARSAの更新式に必要なState、Action、Reward、next-State、next-Actionの5つの頭文字です。ここではSARSAの更新式であるupdate_sarsa関数の引数もその順で定義しています。
SARSAでは基本的にはq_tableに従って、ある状態における行動を決定するわけですが、一定の割合でランダムな行動をとるようにしています。これはε-greedy法という考え方に従うもので、より良い行動を探すための仕組みです。強化学習の世界には「探索と利用のトレードオフ(exploitation-exploration trade-offs)」という言葉もあるようです。深いです。
ランダムに動作するためのepsilonの割合は繰り返し(エピソード)ごとに0.5を掛けるものが多くありましたが、ここでは学習の様子(失敗するケース)を強調するために0.9を掛けています。
実行結果
$ python skinner_sarsa.py [[0. 0.] [0. 0.]] 8 5 3 2 7 4 4 2 4 2 [[0.216381 0.010251] [0.020069 0.651322]] 2 2 2 2 2 2 2 2 2 2 [[0.54006 0.010251] [0.020069 0.878423]] 2 2 2 2 2 2 2 2 2 2 [[0.732106 0.010251] [0.020069 0.957609]] 2 2 2 2 3 2 2 2 2 2 [[0.826678 0.07931 ] [0.020069 0.985219]] 2 2 2 2 2 2 2 2 2 2 [[0.86928 0.07931 ] [0.020069 0.994846]] 2 2 2 2 2 2 2 2 2 2 [[0.887492 0.07931 ] [0.020069 0.998203]] 2 2 2 2 2 2 2 2 2 2 [[0.895012 0.07931 ] [0.020069 0.999373]] 2 2 2 2 2 2 2 2 2 2 [[0.898042 0.07931 ] [0.020069 0.999782]] 2 2 2 2 2 2 2 2 2 2 [[0.899241 0.07931 ] [0.020069 0.999924]] 2 2 2 2 2 2 2 2 2 2 [[0.899709 0.07931 ] [0.020069 0.999973]]
q_tableの初期値は0としています。学習が進むにつれて、q_table[0][0]やq_table[1][1]の値が大きくなっているのがわかります。余談ですが、方策勾配法の方策piと比較すると、q_tableの状態ごとの値は割合ではないので加算したら1になるわけではないようです。
また学習が進むにつれて、ランダムに動作する割合を示すepsilonが小さくなるので不規則な行動はとらなくなります。そのためq_tableに従って最小の2ステップで餌にたどり着くことができています。
Q-Learning
さいごにQ-Learningです。SARSAとよく似ていて、行動価値関数(変数q_table)の更新式が少し異なります。SARSAでは更新式にnext-State、next-Actionの2つが必要でしたが、Q-Learningではnext-Stateにおける行動(Action)の中から値の最大値のものを選択するようにします。
import numpy as np def get_action(q_table, state, epsilon): if np.random.uniform(0, 1) <= epsilon: return np.random.choice([0, 1]) else: if q_table[state, 0] == q_table[state, 1]: return np.random.choice([0, 1]) return np.argmax(q_table[state,:]) def get_next_state_and_reward(state, action): if state == 0 and action == 0: return (1, 0) elif state == 0 and action == 1: return (0, 0) elif state == 1 and action == 0: return (0, 0) else: return (1, 1) def update_q_learning(state, action, reward, next_state, q_table): eta = 0.1 gamma = 0.9 if reward == 1: q_table[state,action] = q_table[state,action] + eta * (reward - q_table[state,action]) else: q_table[state,action] = q_table[state,action] + eta * (reward + gamma * np.max(q_table[next_state,:]) - q_table[state,action]) return q_table def challenge(q_table, epsilon): state = 0 action = get_action(q_table, state, epsilon) history = [] while True: history.append([state, action]) [next_state, reward] = get_next_state_and_reward(state, action) q_table = update_q_learning(state, action, reward, next_state, q_table) if reward == 1: break state = next_state action = get_action(q_table, state, epsilon) return (q_table, history) np.set_printoptions(precision=6, suppress=True) q_table = np.zeros((2, 2)) print(q_table) epsilon = 1.0 for i in range(1, 101): epsilon = epsilon * 0.9 [q_table, history] = challenge(q_table, epsilon) print(len(history), end=" ", flush=True) if i % 10 == 0: print("\n", q_table)
実行結果
$ python skinner_q_learning.py [[0. 0.] [0. 0.]] 2 3 6 2 3 2 6 2 2 3 [[0.270356 0.047601] [0.01882 0.651322]] 3 3 2 2 2 2 2 2 2 2 [[0.55888 0.08763 ] [0.01882 0.878423]] 2 2 2 2 2 2 4 4 3 2 [[0.759274 0.145173] [0.136828 0.957609]] 2 2 2 2 2 2 2 2 2 2 [[0.836151 0.145173] [0.136828 0.985219]] 2 2 2 2 2 2 2 2 2 2 [[0.872583 0.145173] [0.136828 0.994846]] 2 2 2 2 2 2 2 2 2 2 [[0.888643 0.145173] [0.136828 0.998203]] 2 2 2 2 2 2 2 2 2 2 [[0.895414 0.145173] [0.136828 0.999373]] 2 2 2 2 2 2 2 2 2 2 [[0.898182 0.145173] [0.136828 0.999782]] 2 2 2 2 2 2 2 2 2 2 [[0.89929 0.145173] [0.136828 0.999924]] 2 2 2 2 2 2 2 2 2 2 [[0.899726 0.145173] [0.136828 0.999973]]
結果はSARSAのときと同じようにq_tableの値が更新されているのがわかります。
その次の勉強
とりあえずはOpenAI Gymの題材にチャレンジするのが良さそうです。有名な倒立振子(CartPole)については書籍やインターネット上でサンプルもたくさん紹介されています。他にもToy textなるものもありました。私もいくつか触ってみましたが、FrozenLake問題というのは勉強するのにちょうど良い感じがしました。
https://gym.openai.com/envs/#toy_text
他にも調べているとブロック崩しゲームも自力で解けるみたいです。GPUマシンもクラウドでどうにかなるし。
今後は自分で何か強化学習の題材を作ってみようと思っています。面白いかどうかは別として、マルバツゲームや五目並べなどにチャレンジしてみようかと思っています。Q-Learningだと状態や行動、報酬の設計をゼロからできるようになれば世界が広がりそうです。
あとはアルゴリズムについてはコードだけでなく数式による理解も大事ですね。この辺はコツコツと。それからDQNなどの深層学習に取り組むのも面白そうです。今まではKerasしか使ったことなかったですが最近はPyTorchが良いみたいです。
まとめ
普段やらないことをやってみました。勉強になりました。
超速習 - はじめてのPHPプログラミング
最速?でPHPプログラミングの基礎を学ぶためのガイドです。できるだけ短いコードでプログラミングの基礎となる変数や配列、制御構文、関数を紹介してみました。30分くらいで一通り紹介するイメージで。
開発環境としてはMacを想定しています。最近のMacはデフォルトでPHPがインストールされているので、AtomやVSCodeのようなテキストエディタとターミナル*1を起動すればすぐにPHPの学習を始めることができます。*2
サンプルプログラム
ここでは以下のサンプルプログラムを取り上げます。最速で。
- データの出力
- 変数
- 配列(添字配列)
- 制御構文(for文)
- 制御構文(if文)
- 配列(連想配列)
- 関数
- まとめ
プログラミング初学者にとっては配列や関数など躓きやすいポイントがいくつかありますが、上から順番に学習を進めていけばどこが苦手かチェックできると思います。
1 データの出力(sample1.php)
次のプログラムは画面(ターミナル)にHello PHP
と出力するプログラムです。ファイル名はsample1.php
として任意のフォルダに保存します。
以降のサンプルプログラムはMac上で
/Users/murayama/Desktop/php-basic
フォルダに保存したものとします。
<?php echo "Hello PHP"; ?>
PHPで画面にデータを出力するにはecho
命令を使います。上記のように記述すれば画面にHello PHP
と出力されます。
次にターミナル(Windowsの場合はコマンドプロンプト)を開きます。カレントディレクトリ(カレントフォルダ)を変更するために以下のコマンドを入力します。
$ cd /Users/murayama/Desktop/php-basic
ターミナル上で現在作業している(開いている)ディレクトリをカレントディレクトリと呼びます。cd
コマンドによってカレントディレクトリを変更することができます。ここではカレントディレクトリを/Users/murayama/Desktop/php-basic
に変更しています。
注意:自分のMacに合わせて上記のパス
/Users/murayama/Desktop/php-basic
は置き換えてください。
それでは作成したプログラムを実行してみましょう。ターミナル上でphp
コマンドを入力します。
$ php sample1.php Hello PHP
上記のようにHello PHP
と出力されればOKです。
PHPプログラムの終端となる閉じタグ
?>
は省略可能です。以降は?>
を省略してコードを記載します。
2 変数(sample2.php)
続いて変数を扱うプログラムについて見てみましょう。変数とはプログラム上でデータを扱う仕組みです。次のプログラムをsample2.php
という名前で保存します。
<?php $name = "Andy"; echo "Hello "; echo $name;
ここでは変数$name
を定義しています。また変数$name
の中に"Andy"
という文字列データを代入しています。PHPの文字列データは""
(ダブルクォーテーション)あるいは''
(シングルクォーテーション)で囲む必要があります。変数に格納したデータはecho
命令で出力できます。
PHPの変数は先頭に
$
マークが付きます。
それでは作成したプログラムを実行してみましょう。ターミナル上でphp
コマンドを入力します。
$ php sample2.php Hello Andy
上記のようにHello Andy
と出力されればOKです。
さて、もう一つ変数を扱うサンプルプログラムを考えてみましょう。先ほどのプログラムはHello Andy
と出力しましたが、もう一人、登場人物として"Betty"
を追加してみましょう。先ほどのプログラムsample2.php
を修正します。
<?php $name = "Andy"; $name2 = "Betty"; echo "Hello "; echo $name; echo "Hello "; echo $name2;
変数が$name
と$name2
の2つになりました。変数$name
には"Andy"
、$name2
には"Betty"
がそれぞれ代入されています。
それでは作成したプログラムを実行してみましょう。ターミナル上でphp
コマンドを入力します。
$ php sample2.php Hello AndyHello Betty
上記のように画面にHello AndyHello Betty
と出力されればOKです。ところでもう一人名前の出力を追加する場合はどうでしょうか。"Andy"
、"Betty"
、"Carol"
のように名前を追加していくと、変数の数も多くなってしまいます。
3 配列(添字配列)(sample3.php)
続いて配列を扱うプログラムについて見てみましょう。配列は変数の一種で、関係性のある複数のデータをまとめて管理する仕組みです。たとえば先ほどの"Andy"
、"Betty"
、"Carol"
のような名前を表すデータは配列で管理すると簡単になります。次のプログラムをsample3.php
という名前で保存します。
<?php $names = ["Andy", "Betty", "Carol"]; echo "Hello "; echo $names[0]; echo "Hello "; echo $names[1]; echo "Hello "; echo $names[2];
変数$names
には"Andy"
、"Betty"
、"Carol"
と3つのデータが代入されています。このように複数のデータをまとめて管理する仕組みを配列(添字配列)と呼びます。配列は前から順番に要素番号が割り振られます。また要素番号の先頭は1
ではなく0
から始まる点に注意しておきましょう。
変数名が
$names
と複数形になっている点も注目してください。配列のような複数のデータを表現する変数は名前の付け方を工夫すると読みやすくなります。
それでは作成したプログラムを実行してみましょう。ターミナル上でphp
コマンドを入力します。
$ php sample3.php Hello AndyHello BettyHello Carol
上記のように出力されればOKです。
4 制御構文(for文)(sample4.php)
配列のような集合データはfor文などの繰り返し構造を使えば簡単に出力できます。次のプログラムをsample4.php
という名前保存します。
<?php $names = ["Andy", "Betty", "Carol"]; for ($i = 0; $i < 3; $i++) { echo "Hello "; echo $names[$i]; }
ここで繰り返し構造であるfor
文は変数$i
の値が0
から3
まで(計3回)処理を繰り返します。
for文で扱う変数
$i
はカウンター変数などと呼ばれます。変数$i
のiはincrement(増える)という単語の頭文字を意味しています。for ($i = 0; $i < 3; $i++)
とすると、変数$i
の初期値は0となり、$i < 3
の条件が成立する間、処理(forの後の{}
)を繰り返します。繰り返しが1回終了するごとに$i++
が実行されます。$i++
は$i
の値を1
増やすという処理なので、繰り返しの都度、変数$i
の値が1増えることになります。
それでは作成したプログラムを実行してみましょう。ターミナル上でphp
コマンドを入力します。
$ php sample4.php Hello AndyHello BettyHello Carol
上記のように出力されればOKです。
5 制御構文(if文)(sample5.php)
さきほどのプログラムを少し修正してみましょう。ここではif
文を使って"Andy"
、"Betty"
、 "Carol"
3人の名前の中から"Andy"
以外の名前を出力するように修正してみましょう。次のプログラムをsample5.phpという名前保存します。
<?php $names = ["Andy", "Betty", "Carol"]; for ($i = 0; $i < 3; $i++) { if ($names[$i] != "Andy") { echo "Hello "; echo $names[$i]; } }
for文の中で、if文を使って変数($names[$i]
)の値が"Andy"
でないか確認しています。
if文で利用している演算子
!=
は左辺と右辺が等しくない場合に真(True)となります。等しいかどうかを比較する場合は==
を使います。!==
や===
といった演算子も大切ですが、もう少しあとで勉強しましょう。
それでは作成したプログラムを実行してみましょう。ターミナル上でphp
コマンドを入力します。
$ php sample5.php Hello BettyHello Carol
上記のように出力されればOKです。
6 配列(連想配列)(sample6.php)
少し複雑なプログラムも見てみましょう。ここでは"Andy"
、"Betty"
、"Carol"
3人のデータに対して"Andy"
は20
歳、"Betty"
は19
歳、"Carol"
は21
歳のように、年齢(age
)データも定義します。また出力の条件も変更して、年齢が20
歳以上であればHello
と出力するように修正してみましょう。次のプログラムをsample6.php
という名前保存します。
<?php $students = [ ["name" => "Andy", "age" => 20], ["name" => "Betty", "age" => 19], ["name" => "Carol", "age" => 21] ]; for ($i = 0; $i < 3; $i++) { if ($students[$i]["age"] >= 20) { echo "Hello "; echo $students[$i]["name"]; } }
連想配列はダブルアロー演算子=>
を使って定義します。通常の配列(添字配列)は前から順番に要素番号が割り振られるのに対して、連想配列は要素番号ではなく、キー("name"
や"age"
のような文字列)を割り振ります。
またfor
文の中で、if
文を使って変数$names[$i]["age"]
の値が20
以上かどうか確認しています。
それでは作成したプログラムを実行してみましょう。ターミナル上でphp
コマンドを入力します。
$ php sample6.php Hello AndyHello Carol
上記のように出力されればOKです。
7 関数(sample7.php)
続いて本講座では関数について取り上げます。関数を使うサンプルプログラムを見てみましょう。次のプログラムはstrtoupper
関数を使って"Andy"
、"Betty"
、"Carol"
3人の名前をアルファベット大文字で出力します。次のプログラムをsample7.php
という名前保存します。
<?php $names = ["Andy", "Betty", "Carol"]; for ($i = 0; $i < 3; $i++) { echo "Hello "; echo strtoupper($names[$i]); }
for
文の繰り返し処理の中でstrtoupper
関数を利用しています。strtoupper
関数は引数$names[$i]
に受け取った値を大文字に変換する関数です。
引数とは関数の受け取るデータのことです。また関数の返却するデータは戻り値(返り値)などと呼びます。
それでは作成したプログラムを実行してみましょう。ターミナル上でphp
コマンドを入力します。
$ php sample7.php Hello ANDYHello BETTYHello CAROL
上記のように出力されればOKです。
アルファベット小文字に変換するにはstrtolower関数を使います。
PHPにはstrtoupper
以外にも様々な関数が用意されています。次のサンプルプログラムはmkdir
関数を使ってディレクトリ(フォルダ)を作成するプログラムです。さきほどのプログラムsample7.php
を次のように修正してみましょう。
<?php $names = ["Andy", "Betty", "Carol"]; for ($i = 0; $i < 3; $i++) { mkdir($names[$i]); }
for
文の繰り返し処理の中でmkdir
関数を利用しています。mkdir関数は引数$names[$i]
に受け取った値を使ってディレクトリを作成します。
それでは作成したプログラムを実行してみましょう。ターミナル上でphp
コマンドを入力します。
$ php sample7.php
このプログラムは実行結果をターミナルに表示しませんが、Finderやls
コマンド(Windowsの場合はExplorerやdirコマンド)で生成されたディレクトリを確認できるでしょう。
$ ls Andy Betty Carol
上記のように生成されたディレクトリを確認できればOKです。
8 まとめ(sample8.php)
それではこれまでのまとめとして、配列(連想配列)や制御構造(for
文やif
文)、それから関数(strtoupper
)を使うプログラムを作成してみましょう。次のプログラムをsample8.php
という名前保存します。
<?php $students = [ ["name" => "Andy", "age" => 20], ["name" => "Betty", "age" => 19], ["name" => "Carol", "age" => 21] ]; for ($i = 0; $i < 3; $i++) { if ($students[$i]["age"] >= 20) { echo "Hello "; echo strtoupper($students[$i]["name"]); } }
それでは作成したプログラムを実行してみましょう。ターミナル上でphpコマンドを入力します。
$ php sample8.php Hello ANDYHello CAROL
上記のように出力されればOKです。
おわりに
以上、駆け足でPHPプログラミングについて紹介しました。
- データの出力
- 変数
- 配列(添字配列)
- 制御構文(for文)
- 制御構文(if文)
- 配列(連想配列)
- 関数
わずかなプログラムでしたが、これだけでもプログラミングの基礎的な概念は網羅できています。ここで紹介したコードの読み書きができればPHPerとしての道は開けると思います。
*1:ターミナルはSpotlightでTerminalと入力すると起動します。
*2:ブラウザでPHPプログラムを開発するには https://repl.it/ などが便利です。repl.itで実行する場合はPHPの閉じタグ?>が要るみたいなので注意してください。
30minくらいで学ぶVue.jsとVuex
カウンターアプリケーションの開発を通じてVue.jsによるプログラミングとVuexによる状態管理を学びます。
ボタンを押したら数字が増えていくアプリケーションです。
Agenda
- Part 1 Vueアプリケーションの開発(10min)
- プロジェクトの作成
- Vueコンポーネント(MyCounter.vue)の作成
- Part 2 Vuexを活用したVueアプリケーションの開発(10min)
- Vuexのインストール
- Storeの作成
- Vueコンポーネント(MyCounter.vue)の修正
- Part 3 非同期処理(Actions)の実装(10min)
- Storeの修正
- Vueコンポーネント(MyCounter.vue)の修正
Part 1 Vueアプリケーションの開発(10min)
プロジェクトの作成
プロジェクトの雛形を作成するためにvue-cliをインストールします。
npm install -g @vue/cli
インストールしたvue-cliを使ってプロジェクトの雛形を作成します。
vue create counter-app
vue-cliの設定はデフォルトを使います。途中でVuexを追加しますが手動で追加するものとします。
プロジェクトが作成できたらディレクトリを移動してサーバを起動してみましょう。ここではyarnを使って起動しています(インストールの設定によってはnpmでも大丈夫です)。
cd counter-app yarn serve
次のような画面を確認できればOKです。
Vueコンポーネント(MyCounter.vue)の作成
vue-cliで作成したプロジェクトの開発では主にsrcディレクトリ下のファイルを編集することになります。
- src/
- assets/
- 画像ファイルなどのアセット
- components/
- App.vueからロードされる画面の部品となるVueファイル
- App.vue
- main.jsからロードされるVueファイル
- main.js
- 起動ファイル
- assets/
src/components/MyCounter.vueファイルを新規作成します。
<template> <div> Count: {{ count }} <button @click="onclick">Increment</button> </div> </template> <script> export default { data () { return { count: 1 } }, methods: { onclick () { this.count++ } } } </script>
続いてsrc/App.vueを修正します。
<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> - <HelloWorld msg="Welcome to Your Vue.js App"/> + <MyCounter /> </div> </template> <script> -import HelloWorld from './components/HelloWorld.vue' +import MyCounter from '@/components/MyCounter.vue' export default { name: 'app', components: { - HelloWorld + MyCounter } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
ブラウザでサーバ(localhost:8080)にアクセスすると次のような画面が表示されるでしょう。
Part 2 Vuexを活用したVueアプリケーションの開発(10min)
Vuexのインストール
つづいてVuexをインストールします。
VuexはVue.jsアプリケーションのための状態管理パターン+ライブラリです。 https://vuex.vuejs.org/ja/
$ yarn add vuex --save
ここではMyCounter.vueの状態(dataプロパティ)をVuex上で管理するように修正していきます。
Storeの作成
Vuex上で状態を管理するためのStoreファイルを作成します。src/store/index.jsを作成し、以下のように実装します。
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ }, }, }) export default store
VuexのStoreには以下の主に4つを記述します。
- state・・・状態データ
- mutations・・・状態を更新する処理
- getters・・・状態を取得する処理
- actions・・・非同期処理(mutationsを呼び出す)
gettersとactionsについては後述します。
またsrc/main.jsファイルを編集してsrc/store/index.jsを読み込むよう修正します。
import Vue from 'vue' import App from './App.vue' + import store from './store' Vue.config.productionTip = false new Vue({ render: h => h(App), + store, }).$mount('#app')
Vueコンポーネント(MyCounter.vue)の修正
src/components/MyCounter.vueを次のように修正します。
<template> <div> Count: {{ count }} <button @click="onclick">Increment</button> </div> </template> <script> export default { - data () { - return { - count: 1 - } - }, + computed: { + count() { + return this.$store.state.count; + } + }, methods: { onclick () { - this.count++ + this.$store.commit('increment') } } } </script>
VueコンポーネントからStoreにアクセスするにはthis.$store
と記述します。this.$store.state.count
で状態(state)にアクセスし、状態を更新するmutationsを実行するにはcommitメソッドを使います。
ブラウザでサーバ(localhost:8080)にアクセスして正しく動作することを確認しておいてください。
mapGetters、mapMutationsによるリファクタリング
Vuexには以下のヘルパーメソッドが用意されています。
- mapState
- mapMutations
- mapGetters
- mapActions
上記のヘルパーメソッドを使うとVueコンポーネントの記述がシンプルになります。
ここではmapGettersとmapMutationsを利用するように修正してみます。
まずはsrc/store/index.jsを修正します。Storeファイルにgettersを追加します。
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const store = new Vuex.Store({ state: { count: 0 }, + getters: { + count: state => state.count + }, mutations: { increment (state) { state.count++ }, }, }) export default store
ここでgettersは単純にstateを返却していますが、gettersはStoreの状態を算出(フィルタリングなど)する用途に使えます。
次にsrc/componetns/MyCounter.vueを修正します。
<template> <div> Count: {{ count }} - <button @click="onclick">Increment</button> + <button @click="increment">Increment</button> </div> </template> <script> +import { mapGetters, mapMutations } from 'vuex' + export default { computed: { - count() { - return this.$store.state.count; - } + ...mapGetters(['count']) }, methods: { - onclick () { - this.$store.commit('increment') - } + ...mapMutations(['increment']) } } </script>
mapMutationsやmapGettersは戻り値にオブジェクトを返すので、オブジェクトスプレッド演算子を合わせて利用します。また引数には利用したい処理を定数の配列で渡すようにします。
ブラウザでサーバ(localhost:8080)にアクセスして正しく動作することを確認しておいてください。
Part 3 非同期処理(Actions)の実装(10min)
Storeの修正
続いて非同期処理を実装してみます。ここではボタンを押してから2秒後にインクリメントするIncrement Async
ボタンを実装します。
まずはsrc/store/index.jsを修正します。
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const store = new Vuex.Store({ state: { count: 0 }, getters: { count: state => state.count }, mutations: { increment (state) { state.count++ }, }, + actions: { + incrementAsync ({ commit }) { + setTimeout(() => commit('increment'), 2000) + } + } }) export default store
新たにactionsを定義しています。actionsではmutationsをcommitするようにします。
actionsはWeb APIの呼び出しなどに適しています。
Vueコンポーネント(MyCounter.vue)の修正
続いてsrc/components/MyCounter.vueを修正します。
<template> <div> Count: {{ count }} <button @click="increment">Increment</button> + <button @click="incrementAsync">Increment Async</button> </div> </template> <script> import { mapGetters, mapMutations } from 'vuex' export default { computed: { ...mapGetters(['count']) }, methods: { + incrementAsync () { + this.$store.dispatch({ + type: 'incrementAsync' + }) + }, ...mapMutations(['increment']) } } </script>
新たにincrementAsyncメソッドを追加しています。incrementAsyncメソッドではStoreに対してdispachメソッドを呼ぶことでactionsを呼び出しています。
ブラウザでサーバ(localhost:8080)にアクセスして正しく動作することを確認しておいてください。
mapActionsによるリファクタリング
続いてmapActionsを使ってリファクタリングしてみましょう。src/components/MyCounter.vueを修正します。
<template> <div> Count: {{ count }} <button @click="increment">Increment</button> <button @click="incrementAsync">Increment Async</button> </div> </template> <script> -import { mapGetters, mapMutations } from 'vuex' +import { mapGetters, mapMutations, mapActions } from 'vuex' export default { computed: { ...mapGetters(['count']) }, methods: { - incrementAsync () { - this.$store.dispatch({ - type: 'incrementAsync' - }) - }, + ...mapActions(['incrementAsync']), ...mapMutations(['increment']) } } </script>
ブラウザでサーバ(localhost:8080)にアクセスして正しく動作することを確認しておいてください。
まとめ
Vuexを使ってVue.jsアプリケーションの状態管理を試してみました。Vuexの詳細については公式のマニュアルを読めば大体わかります。
以降はVue Routerを理解して、Nuxt.jsの学習を進めていくと良いでしょう。
勢いでMac買っちゃった人のためのWebプログラミング開発環境構築
同級生(昭和54年生まれ)の友達が最近プログラミングに興味を持っていて「Mac買おうかなー」と迷っていたのでまとめました。プログラミング楽しいよ。
私とプログラミング
2019年1月からMacBook Pro(15)に乗り換えました。以前の13インチに比べて5cmくらい?画面が大きくなり、しばらく満足していたのですが1週間も経つともっと画面広かったらなーと思うようになりました。しばらくコワーキングで仕事しているのでデュアルディスプレイでないのが少しさびしい。
これからはしばらくFirebaseを中心にWebアプリケーションをいくつか作る予定です。フロントエンドはVue.jsを中心にJavaScriptをしっかりやろうと思っています。あとたまにPHPのコードも書きます。そんなわけで必要なツールはエディターとターミナルとブラウザがあれば十分です。というわけで以下のアプリをインストールしました。
エディタ(VS Code)
エディタはこれまでAtomを使っていましたが(今も使っていますが)VS Codeに乗り換えました。
デフォルトでも機能が十分ですがVimプラグインなどを入れてプログラマーっぽくしています。他にはGitLensとかGit系の拡張を入れたり、FirebaseとかVueとかのJSまわりのプラグインをいくつかインストールしました。
WakaTime
VS Codeにはプログラミングの時間を計測してくれるWakaTimeプラグインもインストールしてみました。
ちなみに私の最近のプログラミング時間はこんなかんじ。
https://wakatime.com/@murayama333
DAILY AVERAGE 2 hrs 56 mins
最近だと毎日3Hくらいコーディングの時間を確保できているみたい。5Hくらいいきたい。これからプログラミング始める人にもおすすめのプラグインです。
Dash
JavaScriptとかAPIよくわかっていないのでDash入れてみました。
ショートカットキーが安定していないけど、cmd + shift + spaceとかでAPIをひけるようにしています。あとスニペット機能も優秀みたいだけどまだあまり試していない。
ターミナル(iTerm2)
続いてターミナル編。デフォルトのターミナルでも良いような気がしますがプログラマーっぽくありたいのでiTerm2をインストールしました。
CUIベースでコード書くのもカッコいいのでVimでいくべきか迷うのですが、Vim素人なので今はVS Codeでコードを書くようにしています。そうするとエディタとiTerm2の切り替えが頻繁に発生するのでiTerm2のホットキー設定を有効にしています。Alt + SpaceでいつでもiTtem2が表示されるので気持ち良いです。ctrlキー連打で開くのもありみたいです。
Homebrew
Macだとお約束のパッケージ管理ツールHomebrewをインストールします。
インストールは上記のページを参考に。インストールが完了するとbrewコマンドでパッケージを追加できるようになります。以下はwgetコマンドをインストールする例です。
$ brew install wget
zsh
デフォルトのシェルはzshに変更しています。複数のターミナル間でコマンドのヒストリを共有できるのが有り難いです。たしかzshもbrewでインストールしたような気がします。
$ brew install zsh
tmux
iTermの中ではtmuxをインストールしています。
$ brew install tmux
tmuxを使えば、iTermの画面表示を分割したりタブ移動したり便利です。プレフィックスキーはこれまでctrl + tを使っていましたが、今回からはctrl + spaceに変更しました。余談ですがVimのleaderもspaceキーに変更していてspace駆動で操作できるように設定しています。
以下のサイトを参考にしています。
tmuxのステータスバーにバッテリーとかWi-Fiとかいろんな情報を表示すると楽しいです。
peco
シェルではctrl + rでコマンドの履歴を検索したいのでpecoを入れておきました。brewでインストールできます。
$ brew install peco
以下のサイトを参考にしています。
ctrl + x でcdコマンドを補完できるのが地味に便利です。
Vim
やっぱりVimの設定も楽しいです。LIGさんの設定がシンプルで良かったです。
上記のページでも紹介がありましたが、少し古い記事ですが「Vimの生産性を高める12の方法」も面白かったです。
ブラウザ(Chrome)
とりあえずChromeを入れました。あんまりカスタマイズはしていないです。はてなブックマーク拡張は入れています。あとはVueの開発用の拡張とか。
ユーティリティ系
ウィンドウのリサイズにはSpectacleがちょうどいいです。
あとはVS Codeでctrl + m での改行が上手く効いてくれないのでキーのリマップもやろうか迷い中。Karabinerのお世話になるかも。
画面を広く使いたい
ここまでベタなアプリの設定ばかりだったので。ライフハック?的なやつも。
これからしばらくMac1台で仕事をするので画面を広く効率的に使いたいところです。なので、まずMacの「システム環境設定」で以下のとおり変更します。
- Dockを隠す
- メニューバーを隠す
そうすると画面が少し広くなります。
次にiTerm2からタイトルバーを消します。iTerm2の環境設定Profile=>WindowにあるStyleで No Title Barを選びます。
タイトルバーが消えて1cmくらいだけでも画面が広くなると嬉しいです。
調べてみたらVS CodeやAtomもタイトルバーをプラグインで消すことができました。
VS Codeの拡張は再起動時に警告が出るけどとりあえず無視しています。
以上でこんなかんじになります。
ちなみに画面を広く使って集中していると「今何時?」となることが多いので、ターミナルとかエディタのステータスバーに時刻を表示するようにしています。
フリーランス始めました。
近況です。元気にやっています。
- 税務署に行った。開業届(青色申告)を提出した。
- 税務署はこわいイメージがあったけど書類を出すのは1分で終わった。
- フリーランス始めました。
- MacBook Proを買った。
- 大きいサイズ(15インチ)にしたら快適だった。
- お名前.comでドメインを取った。
- お名前.comからの案内メールが多すぎ。
- GSuiteを申し込んだ。
- https://gsuite.google.co.jp/intl/ja/
- Googleあればどこでも仕事できそうな気がした。
- Freeeを申し込んだ。
- https://www.freee.co.jp/
- 会計処理だけでなく見積もり書とか、書類関連も作ってくれた。
- Freeeがあればぼくでも仕事ができそうな気がした。
- 印鑑も買った。
- ほとんどWebやし印鑑とか意味あるのかなーと思いつつも購入した。
- ビジネスにおけるネクタイみたいなもの、と聞いて納得。
- ちなみに印鑑屋さんは減ってるらしい。
- 名刺はまだない。
- コワーキングに通い出した。
- 月額1万円くらい。ほどよく空いている。
- 家からコワーキングまで片道3kmを歩いてる。
- まだ痩せていない。
- むしろ肥えた。
- フリーランスの税金について勉強をした。
- 雰囲気わかってきた。
- この本のおかげ
- https://www.amazon.co.jp/dp/4801400604
- 口座も開設した。
- デビットカード。
- 領収書(レシート)をもらうようになった。
- 案の定、財布がパンパンになった。
- Windowsマシンも必要だと気づいてThinkPadを買った。
- MacよりThinkPadで仕事している人の方がかっこいい説も浮上。
- スマホの充電ができていないことが多い。
- スマホも買い換えようか迷う。
- なんでも経費に見える。
- 平日の昼間に映画:ボヘミアン・ラプソディを観た。
- この10年で観た映画の中で1番良かった。
- フレディ・マーキュリーのファンに。
- Spotifyは経費扱いになるのだろうか。
- ちなみに映画を見たのは10年ぶり。
- 学校を訪問させて頂いた。
- 勉強する空間は良い。
- 年齢とか関係なくいろいろ学び直したい。
- お世話になった人に再会できた。
- ヒントも頂いた。
- これからもお世話になります。
- 前の職場を訪問した。
- みんな元気そうだった。
- いうても2週間ぶり。
- 今はまだ懐かしさとかない。
- Webで知り合った社会人の方にプログラミングを教えている。
- Webで知り合った大学生にもプログラミングを教えている。
- お二人ともリピートしてくれている。
- ありがたい。
- 動画教材づくりにUdemyを始めようかと思っている。
- 教えてもらったCamtasiaをインストールして動画づくりを始めている。
- テキスト教材づくりも今までのやり方を見直している。
- MarkdownよりAsciidocが良いみたい。
- https://qiita.com/xmeta/items/de667a8b8a0f982e123a
- 電子書籍づくりをGitHubで管理している人もいる。
- https://azu.github.io/slide/individual/
- すごく参考になる。
- Firebaseでアプリを作り始めた。
- GoogleのCodelabsのチュートリアルがすごく充実している。
- https://codelabs.developers.google.com/
- 今は作る時間を最優先にしたい。
- とかいいつつ、こんなブログを書いている。
- ベンチャー企業の方とお会いすることが増えた。
- 志、技術、マーケティング。全部大事だと思う。
- 組織づくりに興味が湧いてきた。
- OKRの本(ジョン・ドーア本)が良かった。
- https://www.amazon.co.jp/dp/4532322405
- 去年読めなかったサピエンス全史も読み終えた。
- コロンブスの新大陸発見からいろいろ繋がっているように思う。
- 「文系でプログラマーになったけど色々失敗して3年半で会社を辞めた話」を読んだ。
- https://note.mu/denkigai/n/nafff6bd87802
- 「プログラミングを覚えてから何をすればいいかわからなかった」
- と感じている人は確かに多いかも。
- 例えとしてのSTRIDERの話も面白かった。
- 最近noteって流行ってるように思う。
- 飛行機に乗った。
- 1時間のフライトも想像力が邪魔をして生きた心地がしなかった。
- 登壇した。
- 仕事している感じがした。
- 「パフォーマーだ」
- ブログも書いた。
JavaScript中級トレーニング10問(String編)
この前の記事(JavaScript 30-seconds-of-code String編)をベースにJavaScript問題集を作ってみました。
問題は全部で10問。正規表現をゴリゴリ使うのはなるべく避けてみました。個人的にはヒント見ずに(ググらずに)1時間で全部解けたらマスタークラスだと思います。
- isLowerCase
- reverseString
- truncateString
- mapString
- mask
- compactWhitespace
- palindrome
- capitalize
- byteSize
- pluralize
開発環境はブラウザとエディタで十分ですがCodeSandboxも便利です。
CodeSandboxをVanillaで起動してもらうとJavaScriptコードの勉強にちょうど良いです。
それでははりきってどうぞ。
1 isLowerCase
文字列が小文字か検証します。次の実行結果となるようにisLowerCase関数を定義してください。
isLowerCase('abc'); // true isLowerCase('a3@$'); // true isLowerCase('Ab4'); // false
isLowerCase関数を実装して、console.log(isLowerCase('abc')) でtrueが出力されればOKです。
ヒント
String.prototype.toLowerCase()を使って、与えられた文字列を小文字に変換し、元の文字列と等しいか比較します。
const isLowerCase = str => str === str.t__________();
2 reverseString
文字列を反転します。次の実行結果となるようにreverseString関数を定義してください。
reverseString('foobar'); // 'raboof'
ヒント
スプレッドオペレータ(...)とArray.prototype.reverse()を使って文字列内の文字の並びを逆順にしString.prototype.join('')によって文字を連結して文字列とします。
const reverseString = str => [...str].r______().j___('');
3 truncateString
指定したサイズで文字列を切り詰めます。次の実行結果となるようにtruncateString関数を定義してください。
truncateString('boomerang', 7); // 'boom...'
ヒント
文字列のサイズの上限を指定します。切り詰められた文字列の後部に'...'を連結して返します。
const truncateString = (str, num) => str.l_____ > num ? str.s____(0, num > 3 ? num - 3 : num) + '...' : str;
4 mapString
文字列に含まれる個々の文字に対して、指定されたコールバック関数を適用して、新たな文字列を生成します。次の実行結果となるようにmapString関数を定義してください。
mapString('lorem ipsum', c => c.toUpperCase()); // 'LOREM IPSUM'
ヒント
String.prototype.split('')とArray.prototype.map()を使って、コールバック関数(引数のfn)を文字列内の個々の文字に対して適用します。それからArray.prototype.join('')を使って文字配列を文字列として再結合します。コールバック関数は3つの引数(現在の文字、現在の文字のインデックス、mapString関数の引数に指定された文字列)を受け取ります。
const mapString = (str, fn) => str .s____('') .m__((c, i) => f_(c, i, str)) .j___('');
5 mask
文字列を指定されたマスク文字で置き換えます。ただし、文字列の後部については、引数のnumに指定された文字数分はマスクしません。次の実行結果となるようにmask関数を定義してください。
mask(1234567890); // '******7890' mask(1234567890, 3); // '*******890' mask(1234567890, -4, '$'); // '$$$$567890'
ヒント
String.prototype.slice()を使って、アンマスク(マスクしない)する文字列を取り出し、String.prototype.padStart()で、元の文字列の長さ分のマスク文字を追加します。第2引数のnumが省略された場合、デフォルトで4文字のアンマスク文字を確保します。またnumに負の値が指定された場合は、文字列の先頭部分をアンマスクします。第3引数のmaskが省略された場合、デフォルトのマスク文字に*を使います。
const mask = (cc, num = 4, mask = '*') => `${cc}`.s____(-num).p_______(`${cc}`.l_____, mask);
6 compactWhitespace
連続するホワイトスペース文字(スペース、タブ、改ページ、改行)をホワイトスペース文字1文字に置き換えます。次の実行結果となるようにcompactWhitespace関数を定義してください。
compactWhitespace('Lorem Ipsum'); // 'Lorem Ipsum' compactWhitespace('Lorem \n Ipsum'); // 'Lorem Ipsum'
ヒント
正規表現とString.prototype.replace()を使うことで、2文字以上のホワイトスペース文字を1文字に置き換えます。
const compactWhitespace = str => str.r______(/\s{2,}/g, ' ');
7 palindrome
与えられた文字列が回文になっているか検証します。回文の場合、true、そうでない場合、falseを返します。次の実行結果となるようにpalindrome関数を定義してください。
palindrome('taco cat'); // true
ヒント
String.prototype.toLowerCase()とString.prototype.replace()を使って非アルファベット文字を除去して小文字に統一し、それからスプレッドオペレータ(...)を使って文字列を文字の配列に変換し、Array.prototype.reverse()、String.prototype.join('')を使って生成した文字列と、元の文字列(非アルファベット文字を除去して小文字に統一したもの)を比較します。
const palindrome = str => { const s = str.t__________().r______(/[\W_]/g, ''); return s === [...s].r______().j___(''); };
8 capitalize
文字列の先頭文字を大文字に変換します。次の実行結果となるようにcapitalize関数を定義してください。
capitalize('fooBar'); // 'FooBar' capitalize('fooBar', true); // 'Foobar'
ヒント
配列の分割代入とString.prototype.toUpperCase()を使って先頭の文字を大文字にします。 ...restには先頭文字を除く文字の配列が格納されるのでArray.prototype.join('')を使って再び文字列に復元しています。引数のlowerRestが指定されなかった場合は残りの文字列(先頭文字を除く)をそのまま使います。lowerRestにtrueが指定された場合は残りの文字列を小文字に置き換えます。
const capitalize = ([first, ...rest], lowerRest = false) => first.t__________() + (lowerRest ? rest.j___('').t__________() : rest.j___(''));
9 byteSize
文字列の長さをbytesで返します。次の実行結果となるようにbyteSize関数を定義してください。
byteSize('😀'); // 4 byteSize('Hello World'); // 11
ヒント
文字列をBlobオブジェクトに変換してsizeプロパティを参照します。
const byteSize = str => new B___([str]).s___;
10 pluralize
入力された数値によって、基準となる文字列を単数系、あるいは複数形にして返します。第1引数にオブジェクトが指定された場合、関数によって返却されるクロージャを返します。これは"s"による単純な複数形の変換だけでなく、指定されたディクショナリに含まれる複数形単語を返却します。次の実行結果となるようにpluralize関数を定義してください。
pluralize(0, 'apple'); // 'apples' pluralize(1, 'apple'); // 'apple' pluralize(2, 'apple'); // 'apples' pluralize(2, 'person', 'people'); // 'people' const PLURALS = { person: 'people', radius: 'radii' }; const autoPluralize = pluralize(PLURALS); autoPluralize(2, 'person'); // 'people'
ヒント
numが-1か1の場合、単数系の単語を返却します。numがそれ以外の値の場合、複数形の単語を返却します。第3引数のpluralが省略された場合、デフォルトで第2引数のwordに"s"を連結した文字列を複数形の単語として処理するので、必要に応じてカスタマイズすることができます。第1引数のvalがオブジェクトの場合、複数形の単語を格納したディクショナリを保持したクロージャを返却します。
const pluralize = (val, word, plural = word + 's') => { const _pluralize = (num, word, plural = word + 's') => [1, -1].i_______(Number(num)) ? word : plural; if (typeof val === 'object') return (num, word) => __________(num, word, val[word]); return __________(val, word, plural); };
答え
お疲れ様でした。答えはこちらの記事を参考に。