うどん県出身グーナーの呟き

神奈川県在住のグーナーです。プログラムをやっているので勉強していることや作った便利ツールを公開したいと思います。

Raspberry Pi 4 ModelBを買ってみたよ

Raspberry Pi 4 ModelBを買ってみた

概要

すでに2 B plusを持っていますが、最近さらに性能が上がっているRaspberry Piを購入してみました。

これを使ってWebスクレイピングBotなどを常時稼働させたりとかしたいと思っています。

今回はその中でRaspberry Piの立ち上げと画像処理の勉強用にROS2のインストールまでを目標にやりたいと思います。

Raspberry Pi とは?

そもそもRaspberry Piとはなにかご存知ではない方も多いでしょう。

Raspberry Piはなにかというと、

"安価かつコンパクトなパソコン"です。

性能

まずお得感を感じるためにはその性能から語る必要あるでしょう。

現在最新モデルであるRapberry Pi 4 Model Bの性能は以下のとおりです。

CPU: ARM Quad-core 64bit @ 1.5GHz RAM(メモリ): 4GB/8GB インターフェイス: 2.4GHz/5.0GHz Wireless LAN, Bluetooth 5.0, USB3.0 x 2, USB2.0 x 2 ビデオ/オーディオ出力: Micro HDMI x 2(4K60fps), ステレオ音声出力など 電力: 15W程度(マニュアルに5V3A程度と書かれているのでおそらくこのくらい)

この他GPIOピンなども入っているので、かんたんな電子工作からWebブラウジング程度のライトな作業は何でもこなせます。

値段

ここで値段ですが、 最新モデルであるRaspbery Pi 4Bは少し過去のモデルから値段が上がって、

メモリ4GBモデルであれば6500円程度、 8GB(今回はこちらを購入しました)であれば9000円となっています。

これだけの機能がありながら1万円を切るコスパの高さは本当に魅力的です。

というわけで今回は様々なボットを動かすためのサーバとして構築していきたいと思っています。

セットアップ

セットアップに必要なもの

セットアップに必要なものはいかになります。

  • Raspberry Pi 本体
  • micro SDカード
  • micro SDカード <-> USB 変換機
  • キーボード/マウス(USB)

ここから下はお使いのディスプレイの端子とRaspberry Piのモデルに合わせて準備してください。

自分の場合(Raspberry Pi 4B)は以下を用意しました。 * HDMIケーブル * micro HDMI <-> HDMI 変換ケーブル * USB Type-Cケーブル(給電用)

もしお使いのRaspberry Piが3以前のものでしたら、必要なものは以下になります。 * HDMIケーブル * micro USBケーブル

なお、ここで注意ですが、HDMIには大きく分けて三種類規格があります。 1. HDMI 2. mini HDMI 3. micro HDMI

上から下に向けて端子が小さくなります。 自分もやってしまったのですが、間違ってmini HDMIを買わないように気をつけてください。

不安でしたら、下にリンクを貼りますのでこちらを使ってみてください。

OSインストール

こちらの公式サイトを参考にしました。

今まではSDカードにimgを書き込むソフトを使っていましたが、最近になった今更ながらRaspberry Pi Imager というソフトが提供されていることを知ったので、今回はこちらを説明します。

使い方は簡単です。

Raspberry Piに差す予定のmicro SDカードをメインPC(Mac, Windows, Linux)につないだ状態でソフトを起動した後、 下のような画面がでてきます。 f:id:ryo_udon:20210108102314p:plain 後は左から順番に自分に合わせてOS, 書き込むSDカードを選択していきます。

まず、CHOOSE OS ボタンをクリックし、OSを選択します。 f:id:ryo_udon:20210108102331p:plain

ここではRasbianを始め、Ubuntuのデスクトップイメージなどが選択できます。

今回はUbuntu20.10をインストールしたので、Ubuntu -> Ubuntu 20.10 Desktop (Raspberry Pi 4) を選択します。 f:id:ryo_udon:20210108102358p:plainf:id:ryo_udon:20210108102404p:plain

次にSDカードを選択します。 CHOOSE SD CARDをクリックすると今パソコンに接続されている外部ストレージの一覧が出てきます。

(画像を取り忘れたため、下の画像ではひとまず適当なHDDをさしています。)

f:id:ryo_udon:20210108102511p:plain

この中からRaspberry Pi用のSDカードを選択してWRITEボタンを押せば完了です。

OS起動

こちらについては画面に従って操作するだけなので説明は省きます。 もし分からなければコメントをいただければお応えします。

なおここからはディスプレイに接続する必要がありますのでケーブルの準備などご注意ください。

SSH

ネットワークを介して、他のパソコンを操作できるSSHを使う設定をしていきます。 基本はこちらのサイトを参考にしました。

今回はpassphrase(パスフレーズ)を使った公開鍵を作っていきます。

パスフレーズ?

そもそもパスフレーズとはなにか。 こちらのサイト を参照すると以下のように説明されています。

パスフレーズとは、10文字以上の単語(フレーズ)を組み合わせて作られたパスワードの一種です。 パスフレーズとは、本人認証で用いるパスワードの一種です。通常のパスワードが8文字前後のところ、パスフレーズは10文字以上の単語を組み合わせた文字列で構成されているため、セキュリティが強固になります

要は、一般的なパスワードと違い、

文字列が長いため不正にパスワードを暴く「総当り攻撃」に対して有効

ということです。

設定

以下の手順で実施していきます。

  1. メインPC上で鍵ペアファイル作成
  2. 1.で作成した鍵ファイルのうち、公開かぎファイルをRaspberry Piに送信
  3. Raspberry Pisshサーバ設定ファイルを編集
  4. Mac or Windows PC側でのRaspberry Piの登録
鍵ペアファイルの作成

まず以下のコマンドを実行します。

ssh-keygen -t rsa

ここで-tのオプションで指定しているの公開鍵の形式です。 作成できるキーペアは以下の4つのようです。

  • dsa
  • ecdsa
  • ed25519
  • rsa

それぞれの形式の良し悪しについてはまた別に話すとして、今回は最後のrsaで作成しました。

Raspberry Piへの公開鍵の登録
scp id_rsa.pub [raspiのユーザ名]@[raspiのIPアドレス]:

(最後の:を忘れがちなので注意)

これをするためにはIPアドレスの固定化が必要なので、以下のサイトを参考に設定。

raspberry pi内のターミナルで、

ifconfig

次に同じネットワーク上に存在するIPアドレスを確認。

Mac or Windows PCでターミナルを立ち上げ、以下のコマンドを実行

ipconfig /all

あるいは、

arp -a

ここで使われていないIPアドレスを確認し、合わせてルータのIPアドレスを確認

(ルータについては各社ごとにルータのIPアドレスを確認するソフトが公式に配布されているので、そちらで確認するのがベスト)

ここで調べたIPアドレスをもとに、Raspberry Piのアドレスを固定化していきます。

またRaspberryPiのターミナルに移動し、以下のコマンドを実行。

sudo vim /etc/dhcpcd.conf

ここではエディタでvimを使っていますが、他のエディタでも可です。 ただsshで接続している場合、geditなどは開けませんのでご注意ください。

重要なのは管理者権限(sudo)で編集するということ。

以下の四行を追加

interface wlan0
static ip_address=[固定化したいIPアドレス]
static routers=[ipconfig /allで調べたルータのIPアドレス]
static domain_name_servers=[上のルータのIPアドレス]

上記の設定が終わったところでRaspberry Pi を再起動する。

reboot

再度Raspberry Piが立ちあがったらifconfig でアドレスが固定化されているかを確認して設定は終了。

もし不安だったら、Mac or Windows PCからsshでアクセスできるか確認する。

ssh [raspiのユーザ名]@[固定化したraspiのIPアドレス]

これでパスワードを要求されるので入力して、ログインできれば設定完了です。

この後のサーバ設定ではssh接続しておくと作業が捗るので試しておくことをおすすめします。

sshサーバ設定

sshで Raspberry Piに接続しているターミナルから以下のコマンドを実行。

mkdir .ssh

ここがsshを実行する際に公開鍵を探しに行くディレクトリになります。

次に先程scpでRaspberry Piのホームディレクトリにコピーした"id_rsa.pub"ファイルを.sshに移動します。

cat id_rsa.pub >> .ssh/authorized_keys

次に.sshディレクトリとauthorized_keysの権限を変更していきます。

(グループ権限、他のユーザの権限を無効にします。)

chmod 700 .ssh
chmod 600 .ssh/authorized_keys

その後、もう必要はないのでid_rsa.pubを削除します。

rm id_rsa.pub

次にsshサーバ設定ファイル(/etc/ssh/sshd_config)を編集していきます。

sudo vim /etc/ssh/sshd_config

まずPortを設定します。

sshだとデフォルトは22番ポートになっています。

ただ、このままだと外部から簡単に不正アクセスされてしまう恐れがありますので、

こちらのページ を参考に使われていないポートを設定しましょう。

予約済みのポートは同時に使えないので、予約済みになっていないポートを使いましょう。

#Port 22
Port 49151

次にリモートでのRootログインを禁止します。

PermitRootLoginという項目をsshd_configの中から探しだし、yes -> noに変更します。

#PermitRootLogin yes
PermitRootLogin no

最後に公開鍵ファイル認証を有効にします。

RSAAuthentication yes
PubkeyAuthentication yes
AuthorizedKeysFile ~/.ssh/authorized_keys

もしパスワードの入力を省略したい場合はこれに加えて以下の設定を変更します。

#PasswordAuthentication yes
PasswordAuthentication no

これで編集は完了になります。 最後に保存して、編集画面を閉じてください。

その後、設定を反映させるためにMac or Windows PC 内でRasberry piにsshでつないでいるターミナルで以下のコマンドを実行します。

sudo /etc/init.d/ssh restart

これで設定は完了になります。

正常に設定ができているかを確認したい場合はMac or Windows PCで新しくターミナルを開き以下のコマンドを実行してsshできるか確認してください。

ssh -i [秘密鍵ファイル このページだと~/.ssh/id_rsa] -p [設定したポート番号 このページだと49151] [raspberrypiのユーザ名]@[raspberry Pi のIPアドレス]
Mac or Windows PC側でのRaspberry Piの登録

最後にsshする際に一々上の長いコマンドを打つのは面倒なので、以下のようにユーザ名を設定していきます。

ここでもターミナルを開いてもらって、以下のファイルを編集していきます。

vim .ssh/config

開いたら、以下の5行を追加します。

Host raspi4b
    HostName [固定化したIPアドレス]
    User [Raspberry Piのユーザ名]
    Port 49151
    IdentityFile ~/.ssh/id_rsa

編集終了したら保存して、ファイルを閉じると、以下のコマンドで接続ができるようになります。

ssh raspi4b

これで接続できれば成功です。

余談

ちなみに私はこれに加えて、mac内の.bashrcを変種し、以下のalias(ショートカット)を足しています。

alias raspi4b='ssh raspi4b'

これで保存すると、ターミナル上で

raspi4b

と打つだけでssh接続ができるようになります。

VSCodeの設定

Remote Developmentとは? / インストール方法

私は普段からVSCodeを利用していますが、最近MicroSoft公式でリモートアクセス機能が使えるExtensionが公開されたと聞いたのでその設定をしてみました。

そのExtension の名前が"Remote Development"になります。

インストール方法はその他のExtensionと同じく左側のメニューバーからExtemsionを選択し、

[f:id:ryo_udon:20210108103834p:plain]

検索タブでRemote Developmentを検索します。

似たような名前のExtensionが上位で出てきますが、それを無視して名前が一致するRemote Developmentをインストールしてください。

Remote DevelopmentでのSSH接続方法

使い方は簡単です。

  1. 左下にある緑のボタンをクリック
  2. Remote-SSH Connect Current Window to Host ... または Remote-SSH Coneect to Host ...を選択(後者は新しくwindowを立ち上げる)
  3. 接続先を指定(.ssh/configを設定しているとクリックのみで接続可)
  4. パスワードを入力(パソワード入力を必要なしにしていた場合3.で接続完了)

上の4ステップでRemote接続ができます。

どうやって使えばいいの?

基本的にはメインPCと同じ感覚で作業ができます。

また、VSCode内でターミナルを開くと自動的にSSHした状態のターミナルが開けるので、ディレクトリの作成などの操作も簡単に行えます。

Raspberry PiPython環境構築

色々なサイトでRaspberry PiでのPython環境構築についての記事が上がっていますが、 色々と試した結果以下の方法でインストールができました。

pyenv

今回はAnacondaのインストールをしましたが、動作の確認などができていないのでひとまず確認できたpyenvのインストールを行います。

pyenvPythonのバージョンを簡単に切り替えられるツールになります。

なので、例えば基本は3.8で開発したいが、一部のアプリは2.7でといったアプリごとに使い分けることができるようになります。

なお、ここでは何もPython関連のライブラリがインストールされていない状況を想定して書いていきます。

pyenvのインストール

まず、パッケージリストの更新とインストール済みのパッケージを更新します。

sudo apt-get update
sudo apt-get upgrade
sudo reboot

最後に再起動を行い、改てpyenvをインストールしていきます。

git clone https://github.com/pyenv/pyenv.git ~/.pyenv

これで必要なパッケージはインストールしました。

次にpyenvを使うためにパスの設定を行っていきます。

sudo vim ~/.bashrc

.bashrcに以下のようにパスを設定していきます。

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

上の設定が終わったら、保存してファイルを閉じた後、

source ~/.bashrc

を実行すればパスの設定は完了です。 ちなみにターミナルを立ち上げ直しても反映されます。

Python3のインストール

Python3.7 のインストールにはgcc, makeなどのライブラリが必要になります。 なのでまずはそれをインストールしていきます。

sudo apt install gcc make zlib1g-dev libffi-dev libbz2-dev libssl-dev libreadline-dev libsqlite3-dev python3-tk tk-dev

次にpyenvでインストール可能なバージョンを確認していきます。

pyenv install --list

こちらのコマンドでインストール可能なバージョンのリストが出てきます。 ここで、anacondaがリストの中に出てきますが、Raspberry PiではCPU(ARM)の関係でインストールできませんのでご注意ください。

今回は3.9.1をインストールしたのでそれを例にしていきます。

pyenv install 3.9.1
pyenv global 3.9.1

これでターミナルでpythonを起動する際にpyenvでインストールされたpython3.9.1が起動します。 起動するpythonのバージョンは以下で確認ができます。

python --version

終わりに

というわけでRaspberry Pi 4Bのインストール方法について書いてみました。 以前にもサラッと説明したのですが、自分の備忘録も兼ねて今回、整理しております。

なにかうまくいかないやここがおかしいなどがあればご連絡ください。

ではでは。

【Python】自作の 行列演算クラス、 MatrixLib を作ってみたよ

自作のMatrixクラスを作ってみたよ

github.com


追記

'2020/07/07' Det(), Inverse()の部分で、間違って元の行列を書き換えるようになっていたため修正。


概要

最近、仕事の関係でMATLAB/Simulinkを使うことが多くなったのですが、色々とイライラが募っています。

特に、最近S-Functionを使ったC++ソースコードSimulinkに移植する作業をしているのですが、

もうイライラがとまりません。

関数の使い方はよくわからないし、何より、行列が使えない!! MATLABなのに!!

(本当は使い方があるのかもしれませんが、すいません。僕にはレベルが高すぎました。。。)

というわけで、ひとまずオレオレMatrixライブラリをC++で作ろうと思い立ったわけです。

そこで、とりあえず逆行列行列式を求めたりするので、そのロジックがあっているのかを確認するため、

今回はMatrixクラスをPythonで組んでみたいと思います。

具体的に今回やったこと

今回やったことは主に以下の4つです。

  1. 二次元行列を格納できる
  2. 行列式を計算できる
  3. 逆行列を計算できる
  4. 加減などの計算も符号演算できるようにする

それぞれ、コードとともに具体的に説明していきます。

コードの中身

まず、今回のソースコードはJupyterにて作成しました。

ryo-udon.hatenadiary.jp

もし、実際のPythonコードに使いたいという方は、Jupyterからクラス部分のみをコピーアンドペーストしていただければ

大丈夫です。

importするのも、pythonインストール時にデフォルトでインストールされている,math, copyのみになります。

二次元行列の格納

import math
import copy
class Matrix:
    def __init__(self, row, col):
        self.row = row
        self.col = col
        self.data = [ 0 for i in range(row * col)]
        self.error = False

    def ResetMatrix(self, row, col):
        self.row = row
        self.col = col
        self.error = False

    def InitializeWithIdentity(self):
        for row in range(self.row):
            for col in range(self.col):
                if row == col:
                    self.data[ row, col] = 1
                    #self.data[row * self.col + col] = 1
                else:
                    self.data[ row, col] = 0
                    #self.data[row * self.col + col] = 0
class Matrix:
    def __init__(self, row, col):
        self.row = row
        self.col = col
        self.data = [ 0 for i in range(row * col)]
        self.error = False

まず、データの格納部分です。 今回は色々と考えたのですが、とりあえずデータの格納は一次元の配列にし、このクラスを呼び出した際に、行、列を宣言するようにしました。

ここについては[ 1, 2, 3, 4] ではなく[[ 1, 2],[ 3, 4]]することも考えたのですが、ひとまずはこの形でも問題ないことが確認できたので、

簡単なこちらにしています。

なので、行数などを変えたいときはResetMatrix()関数を、単位行列にしたいときはInitializeWithIdentity()関数を呼び出すようにしています。

ちなみに、InitializeWithIdentity()内のここの部分、

                if row == col:
                    self.data[ row, col] = 1
                    #self.data[row * self.col + col] = 1
                else:
                    self.data[ row, col] = 0
                    #self.data[row * self.col + col] = 0

あえてコメントアウトしていますが、本来、コメントアウトされた書き方でないとエラーになります。

しかし、今回、メソッドのオーバーライドをしているので、この記法ができるようになっています。

この方法についてはまた後ほど説明します。(実はこれが今回このライブラリを作ってみようと思ったモチベーションの一つだったりします。)

行列式の計算

    def Det(self):
        temp = copy.deepcopy(self)
        det = 1.0
        for i in range(self.row):
            if self[i,i] == 0:
                for j in range(i,self.row):
                    for k in range(self.row):
                        buf = self[i,k]
                        temp[i,k] = temp[j,k]
                        temp[j,k] = buf
                    det *= -1.0
                    break   


        for i in range(self.row):
            for j in range(self.row):
                if i < j:
                    buf = temp[i,j] / temp[i,i]
                    for k in range(temp.row):
                        temp[j,k] = temp[j,k] - temp[i,k] * buf

        for i in range(temp.row):
            det *= temp[i,i]
        return(det)

次に行列式の計算です。

行列式の計算については、こちらのサイトを参考に、上三角行列への変形 -> 対角成分の積を取る

方法にしています。

ここは、ただ数学の方程式をプログラムに書き起こしただけなので、深くは説明しません。

逆行列の計算

    def Inverse(self, inv_type="GaussJordan"):

        if inv_type == "GaussJordan":
            return(self.GaussJordanInverse())



    def GaussJordanInverse(self):
        det     = self.Det()
        ans     = Matrix(self.row, self.col)
        temp    = copy.deepcopy(self)
        ans.InitializeWithIdentity()

        for i in range(self.row):
            buf = 1.0 / temp[i,i]
            for j in range(self.row):
                temp[i,j] = temp[i,j] * buf
                ans[i,j] = ans[i,j] * buf
        
            for j in range(self.row):
                if i != j:
                    buf = temp[j,i]
                    for k in range(self.row):
                        temp[j,k] = temp[j,k] - temp[i,k] * buf
                        ans[j,k] = ans[j,k] - ans[i,k] * buf

        return(ans)

続いて逆行列の計算です。

本当はLU法もやるつもりだったのですが、とりあえずGaussJordan法が形になったので、このブログにはここまでしか掲載しません。

Gitには追々追加予定です。

こちらも同じくこのサイトの数式を書き起こしただけになります。

加減などの計算も符号演算できるようにする

最後に今回、演算子オーバーロードしてみたので、その説明をしたいと思います。

(正直、この記事はこれを書きたいがためにやったと言っても過言ではないです。)

そもそも演算子オーバーロードとは何か、わからないですよね。

簡単な例を説明すると、例えば以下のような行列があったとします。

A=
|   1.000   2.000   3.000   |
|   4.000   5.000   6.000   |
|   7.000   8.000   9.000   |
B=
|   1.000   2.000   3.000   |
|   4.000   5.000   6.000   |
|   0.000   0.000   0.000   |

例えばこの2つの行列を足し合わせたい。そう思ったときに、どうするか。

もし、数式で書くなら次のようになります。

ANS = A + B

ANSが3行3列のA,B,2つの行列の和の答えです。

しかし、実際にプログラムを書かれた方ならわかるかと思いますが、自作のクラスではこの書き方では計算ができません。

なぜか。

それは、計算の方法がわからないからです。

なので、だいたいの場合、Add関数なるものを作成し、

ANS = Add(A,B)

このような形で、Add関数内で各要素にアクセスし、その結果を返り値としてANSに格納するという方法をとっていました。

しかし、不思議には思いませんか? ではなぜ、numpyなどのライブラリは行列で+などの演算子が使えるのか。

それは、この+などの演算子を使った場合の処理(特殊メソッド)をオーバーロード、つまり書き換えられるからです。

Pythonの公式ドキュメントにはこちら のように、様々な特殊メソッドが公開しており、これと同じ関数名でクラス内で宣言するだけで、簡単にオーバーロードをすることができます。

では、簡単な例を見ていきましょう。

    def Add(self, other):
        scalar = self.CheckScalar(other)
        ans = Matrix(self.row, self.col)

        if scalar==False:
            if self.row != other.row or self.col != other.col:
                ans.error = True
                return(ans)

            for row in range(self.row):
                for col in range(self.col):
                    ans[row, col] = self[row, col] + other[row, col]

        else:
            for row in range(self.row):
                for col in range(self.col):
                    ans[row, col] = self[ row, col] + other
        return(ans)

    def __add__(self, other):
        return(self.Add(other))

今回のMatrixクラスに入っている足し算のメソッドを紹介します。

まずは先に実行例を示します。

    a.data = [1,2,3,4,5,6,7,8,9]
    b.data = [1,2,3,4,5,6,0,0,0]
    print("a=\n",a)
    print("b=\n",b)
    ans = a+b
    print("ans=\n",ans)

この実行結果がこちらになります。

a=
|   1.000   2.000   3.000   |
|   4.000   5.000   6.000   |
|   7.000   8.000   9.000   |

b=
 |  1.000   2.000   3.000   |
|   4.000   5.000   6.000   |
|   0.000   0.000   0.000   |

c=
 |  2.000   4.000   6.000   |
|   8.000   10.000  12.000  |
|   7.000   8.000   9.000   |

と、このように、def add(self, other): という形で関数を再度宣言して上げることで、+演算子でも計算ができるようになりました。

その他

今回、いくつか特殊メソッドをオーバーロードしてみたので、その紹介もしてみたいとおもいます。 オーバーロードしたのは、以下の3つです。

  1. __str__(self)
  2. __getitem__(self, key)
  3. __setitem__(self, key, value)

1. __str__(self)

これはstr()という文字列に変換する関数でこのクラスが呼ばれたときに何を返すのかを決める特殊メソッドです。

なので、簡単な例でいうと、print()関数がいい例でしょう。

例えば、

a = 1
print(a)

としたときに、出力はどうなるかというと、もちろん下のようになります。

1

ですが、今回のように複数のデータがあるクラス、しかも今回は行列のクラスなので、

できれば行ごとに開業してきれいに表示してほしい。

というわけで、オーバーロードしてみました。

    def __str__(self):
        if self.error == True:
            return("No Matrix")

        s = ""
        for row in range(self.row):
            s += "|"
            for col in range(self.col):
                s += "\t" + "{:.3f}".format(self[row,col])
            s += "\t|\n"
        return(s)

この関数を加えたことで、行列が下のようにprint()できるようになります。

|   1.000   2.000   3.000   |
|   4.000   5.000   6.000   |
|   7.000   8.000   9.000   |

|   1.000   2.000   3.000   |
|   4.000   5.000   6.000   |
|   0.000   0.000   0.000   |

|   2.000   4.000   6.000   |
|   8.000   10.000  12.000  |
|   7.000   8.000   9.000   |

2. _getitem__(self, key), 3. _setitem__(self, key, value)

この2つについてはほぼ役割は同じなのでまとめて説明します。

この2つの特殊メソッドの役割は、以下のような場面です。

a       = [1,2,3]
b       = a[0]
a[1]    = 4

このコードの2行目が_getitem__, 3行目が_setitem__を使っています。

つまり、

要素を指定して値を取り出すときにどの要素を取り出すかを決めるのが__getitem__,

要素を指定して値を書き込むときにどの要素に書き込むかを決めるのが__setitem__

になります。

    def __setitem__(self, key, value):
        if len(key) == 2:
            row = key[0]
            col = key[1]
        elif len(key) == 1:
            row = 0
            col = key[0]
        else:
            return(None)
        if row>=self.row or row < 0 or col >= self.col or col < 0:
            return(None)

        self.data[ row * self.col + col ] = value


    def __getitem__(self, key):
        if len(key) == 2:
            row = key[0]
            col = key[1]
        elif len(key) == 1:
            row = 0
            col = key[0]
        else:
            return(None)
        if row>=self.row or row < 0 or col >= self.col or col < 0:
            return(None)

        #print("row:",row," col:",col)
        return(self.data[ row * self.col + col])

この2つを宣言することで、クラス内やクラス外で行列の要素にアクセスする際、

A[0,0]

のような形でアクセスすることができるようになります。

最後に

というわけで、今回は行列のクラスを試しにつくってみました。

今まで、手を出してなかった特殊メソッドのオーバーロードを試すことができたので、かなりいい勉強になりました。

今後もクラスを作る際には積極的に作って行きたいとおもいます。

ではでは。

Jupyterにも差し込める作図ツールdraw.ioを使ってみたよ

draw.io を使ってみたよ

draw.ioとは?

draw.ioは高性能な作画ツールです。

G Suite Markerplaceの説明文をお借りすると以下のように説明しています。

draw.io is completely free online diagram editor built around Google Drive(TM), that enables you to create flowcharts, UML, entity relation, network diagrams, mockups and more.

draw.ioはGoogle Driveを利用した完全フリーのダイアグラムエディタです。 このエディタでは、フローチャートUML、ネットワーク図などなどを描くことができます。

つまり、フローチャートやクラス図など、プログラムを書き始める前やあるいはあとに、プログラム全体の構成を書くのに便利なツールなわけです。

では、なぜこれをここで紹介したのかというと、最近紹介しました、Jupyter Notebook を使ってみたよ でJupyter Notebookにフロー図を差し込みたいと思ったからです。

最近、Deep Learningの勉強を今更ながらはじめたのですが、Notebookにレイヤやパーセプトロンなどの図を差し込まないとなかなか後で見て分かりづらかったので、 それに適したツールを探したところ見つかったのがこちらというわけです。

どういうところで使えるの?

プログラムを書きなれた方であれば、このツールを起動すればこのツールの良さがすぐに分かるかとは思いますが、

そもそも、あまりプログラムを書かない方だと、なかなかこのツールの良さは伝わらないかとおもいます。

そこで、今回はあまりクラス図やフロー図など、事前知識が必要な用途については言及せず、あくまでJupyterに差し込む図を簡単に作れるよという紹介をしたいとおもいます。

今回は自分が学習用に分けていたリポジトリを参考に説明します。 

github.com

まずは下の画像を見てください。

f:id:ryo_udon:20200614155211p:plain

こちらに差し込んでいる見た目のいい図(自分でいうなよと思わないでください。。。)がすべてこのdraw.ioで作成したものになります。

また、こちらの図のように、図の中にTex形式で数式を記述することもできます。

f:id:ryo_udon:20200614155311p:plain

このようにdraw.ioは、そこそこ見た目のいい図が簡単に作成でき、しかもjupyter notebookに簡単にコピーアンドペーストができる、

そういう超便利ツールなわけです。

どうやって使うの?

使い方は至って簡単ですので順を追って説明していきます。

まずはこちらのページに行きます。

すると、次のように新しくダイアグラム(図)を作るか、それとも既存のものを作るかを聞いてきます。

f:id:ryo_udon:20200614155332p:plain

今回は新しく図を作っていきますので "Create New Diagram" を選択します。

するとどういう図を作りたいかというフォーマットの選択画面に移動します。

f:id:ryo_udon:20200614155348p:plain

今回は特にフローチャートなどはつくらず、簡単なお絵かきだけしようと思うので、 "Blank Diagram" を選択します。


注意

ちなみに、おそらく起動した際にDesktop版のインストールを進められるかと思いますが、現状はインストールをおすすめしません。

理由は、上のdraw.ioの良さの一つに上げた、数式が書けないからです。

Q&Aを確認したところ、LaTex形式での数式記入はブラウザ版のみの対応となっており、デスクトップ版にはまだ追加されていないようです。

そのため、数式を書くたびにわざわざ別のウィンドウで開き直すという作業が必要になるデスクトップ版はあまりおすすめしません。


図の描き方

図の描き方は至ってシンプルです。

左のサイドバーにある、豊富な図形の中から使いたいものをクリックするだけで、真ん中の編集エリアに図形が現れます。あとはそれをPowerPointの図形を編集するのと同じ用に、 上下左右などにある青い点をクリックし、ドラッグしてあげることでサイズを変更できるようになります。

f:id:ryo_udon:20200614155441p:plain

もし図形同士を先で結びたいのなら、線を結びたい元の図形をクリックし、結びたい方向に表示された矢印をクリックしてあげれば、簡単に線をつないでくれます。

f:id:ryo_udon:20200614155452p:plain

もちろん、クリックのあとにドラッグをしてあげれば任意のところに線を伸ばすことも可能です。

ここについては、自分で色々と弄っていくと操作方法もわかってくるでしょう。

数式の描き方

数式については、少し混乱するかもしれませんが、やり方を覚えればあとは、都度都度文法をGoogleで調べるだけになります。

例えばこちらの図の矢印の横に、

f:id:ryo_udon:20200614155514p:plain

$$y = \frac{1}{x}$$

という数式を追加したいとします。

まず、メニューバーから"Extras"->"Mathmatical Setting" をクリックします。

f:id:ryo_udon:20200614155529p:plain

もし、この設定がONになっていれば、再度"Extras"を開いたときに"Mathmatical Setting"の横にチェックがついているはずです。

その後、Latex形式で数式を書いていきます。

ちなみに余談ですが、draw.ioでは、3つの数式記述法に対応しています。リンク

今回は、二番目の "$$"で前後を囲むLatexの記述法で数式を書いていきます。

まず、左にあるサイドバーから、Textという図をクリックし、編集エリアにテキストボックスを追加します。

その後、このテキストボックスに以下の文を追加します。

$$ y = \frac{1}{x} $$

f:id:ryo_udon:20200614155554p:plain

ここで、

$$ がこの間に数式を書くよという宣言

\frac が分数を意味しています。

この記入が終わったら、あとはテキストボックス外の編集ページのどこかをクリックすると、勝手に数式に変換してくれるのです。

f:id:ryo_udon:20200614155609p:plain

これを使うことで、自分のJupyter Notebookの完成度をより一層高めることができるようになります。

保存や出力について

保存は他のアプリケーションと同じく、

"File" -> "Save" または "Save as"

から保存ができます。

また画像やpdfで出力したいときには、

"File" -> "Export as"

から好きな形式で保存することができます。

Jupyterへの添付

最後に、今回の目玉であるJupyter Notebook への挿入についてです。

今回作成した図を実際にJupyter Notebookへペーストする方法について順を追って説明します。

図形の整理

まず、編集ページの中を貼り付けたい図形のみにします。

これは、このあとにコピーする際に、page単位でのコピーとなるためです。

なので、もし貼り付けたくない図形などがある場合は、新たにpageを作成して、そちらへ移動しましょう。

埋め込み式(html形式)への変換

図形の整理が終わったら、下の図のように"File" -> "Embed" -> "Image"を選択していきます。

f:id:ryo_udon:20200614155642p:plain

次に色々と設定項目があるウィンドウがでてきますが、影の追加などについてなので、特にイジる必要はありません。

そのまま、"Embed"をクリックします。

すると下の図のように、html形式に変換してくれます。

f:id:ryo_udon:20200614155701p:plain

あとはこれを Ctrl + C か、下のCopyをクリックしてコピーしたら、あとはJupyter Notebookに貼り付けるだけです。

Jupyter Notebookへの貼り付け

Jupyter Notebookへの貼り付けは何も難しいことはありません。

ただ、図を差し込むようの Markdownブロックを作ってあげ、そこに先程コピーしたhtml形式のimageタグを貼り付けて上げれば、

あとはおなじみの Ctrl + Enter(Return)を押したらJupyter Notebookに図形が表示されます。

f:id:ryo_udon:20200614155746p:plain

 終わりに

というわけで、今回はdraw.ioという描画ツールについて紹介しました。

draw.ioは、これまでパソコンでノートを作成する際に障害になっていた"手書きの図を作るのに時間がかかる”という問題を解決してくれる超便利ツールです。

今回はJupyter に差し込むにはという部分しか説明していませんが、プログラムを書く際の簡単な構成図を書くのにもかなり使えます。

ぜひ、色々と活用してみてください。

ではでは

【初心者向け】Jupyter Notebookを使ってみたよ

Jupyter を使ってみたよ

今回はプログラミング初心者から愛用者まで使うべきツールの紹介をします。

Jupyter Notebookって何?

プログラムと聞くと、何行、何十行とコードを書いて、初めて動作確認をするイメージを持つ方もいるかと思います。

特に、CやC++など作ったプログラムを実行する前にコンパイルという作業が必要な言語に慣れ親しんでいる人ほど、そういうイメージを持っているのではないでしょうか (Jupyterを知る前のわたしも同じでした。)

だけど、ご存知の通り、一気に複数行に渡るコードを追加しても想定どおりに動いてくれないことが多々あります。

逆に一発で想定通りに動いてくれたときもとてつもない不安に襲われていると思います。

Jupyterはそういう手間を省いてくれるツールになります。

Jupyterはブラウザ上で様々な言語を対話形式で動かすことができる開発環境です。

本家Wikiの言葉を借りると

Project Jupyter is a set of open source software projects that form the building blocks for interactive and exploratory computing that is reproducible and multi-language. The main application offered by Jupyter is the Jupyter Notebook, a web-based interactive computing platform that allows users to author documents that combine live code, equations, narrative text, interactive dashboard and other rich media. ...

Jupyterは多数に渡る言語をブロックで組み合わせることで対話的にそして探索的に計算処理をすることができるOpen Sourceソフトウェアプロジェクトの集合体です。 Jupyterの主な機能はJupyter Notebookと呼ばれるブラウザ上で対話的に計算処理を行うことができる環境で、ユーザにその場で実行可能なコード、数式、処理の流れなどの情報を含んだドキュメントを作成することができます。

ここでいう対話的というのは、一行実行したら、すぐに結果が出力されることをいいます。

つまり、Jupyterを使うことで得られる大きなメリットは,

  • 位置行ごとに実行確認をすることができる

  • 自分が書いたコードの意図を数式や図を使って簡単にまとめることができる

ことにあります。

もちろんその他にもいろいろなOpen Sourceを使うことなどのメリットもあります。

と、言葉で説明しましたが、おそらくイメージできていない方が多いかと思いますので、実際に使っている様子を交えながら説明したいと思います。

何はともあれ、使ってみたよ

僕のjupyter notebook のレポジトリはこちらになります。

github.com

今回はここでの処理結果を一例として示しますが、特にクローンする必要はありません。

また、細かい操作やコマンド, インストール方法についてはこの章では省略して、どういうふうに使っていて便利なのかだけ紹介したいと思います。

まずJupyter Notebookを起動するとデフォルトに設定しているブラウザにこちらのようなページが表示されます。 f:id:ryo_udon:20200606154942p:plain

ここではひとまず、既存のNotebookがどういうふうに書かれているかだけ紹介します。

一番上のkalman_filterというディレクトリをクリックすると、下のように.ipynb形式のファイルが入っているのでこれをクリックします。

f:id:ryo_udon:20200606155406p:plain

中身はこのような形になっています。

f:id:ryo_udon:20200606155432p:plain

文法はPythonそのものです。 ただここで通常のPythonコードと違うのは、最初に上げたメリット通り、

  • 各ブロックごとに計算結果を確認できる
  • 各関数に数式などで中身の意図を簡単に説明ができる

ことです。

f:id:ryo_udon:20200606155450p:plain

こちらの画像のように、関数を見ただけでは実際にノートなどに書き起こさないと数式全体を把握することが難しいものも 数式を挿入できるため簡単に表現することができます。

(ちなみに数式やコメントはこのブログを書く際に使っているMarkDownという記法で書くことができます。)

その他にも、Pythonという言語が人気な理由の一つに簡単にグラフを書くことができるという点があると思います。

Jupyter Notebookではこのグラフについても、各ブロックごとに結果を確認し、しかもそれをNotebookに表示したまま保存することができます。

これはDBSCANと呼ばれるデータのクラスタリング手法(似たような点同士にグループ分けする手法)を試したNotebookの一例です。

f:id:ryo_udon:20200606154915p:plain

そのため、Jupyter Notebookを使えば簡単なデータ解析の解析結果レポートを作成することもできるわけです。

こういった理由からJupyterは今世界中の開発者がこぞって使っています。

f:id:ryo_udon:20200606155005p:plain

すでにプログラミングをしている人からこれから始める人まで、これまでよりも効率的にプログラムを構築できる環境、それがJupyterなわけです。

どうやって使うの?

JupyterはOpen Sourceなので何もライセンス等を購入する必要はありません。

ただ、 公式ページ に従って一つコマンドを打つだけでインストールすることができます。

ただ、それでもプログラムにあまり触れたことのない方にはハードルが高く感じられる方もいるかもしれませんので、順番に説明していきます。


注意

先に注意書きとして、Pythonがすでに手元のパソコンにインストールされているものとして説明していきます。 もしPythonをインストールされていないようでしたら、 こちらのサイトこちら を見ていただければ、マウスでクリックするだけでインストールできるので、簡単にPythonユーザになれると思います。

今、巷にはPython2とPython3が存在しています。この2つは同じPythonながら記法が微妙に違ったりしますが、

今からインストールするのであればPython3一択だと思います。

理由はPython2はサポートがもうすぐ終了すること、最新のライブラリは基本Python3を対象にしているからです。


インストール方法

ではここからはPythonの環境が出来上がったものとして説明していきます。

f:id:ryo_udon:20200606154919p:plain

ここではインストール方法として二種類説明されています。

  1. Conda(Anaconda)を使う場合
  2. pipを使う場合

もしPythonに初めて触るという方の場合は、2. pipを使う場合のインストール方法を個人的におすすめします。

Anacondaはnative環境に手を加えずに済み、パソコンの中の環境をクリーンに保つことができるので個人的におすすめですが、

そのあたりがよくわからない方にはただ、ハードルを上げてしまうだけなので、素直にpipを使えばいいと思います。

またJupyterについてはおそらく、pythonのバージョンが変わっても使い続けると思いますので、そういう意味でも何も気にせずpipでインストールしてください。

では、インストール方法を決めたら、Macならターミナル、Windowsならコマンドプロンプト(Mingwでも大丈夫です)を開きます。

pipコマンドはディレクトリはどこでも問題ないので、起動後、下のコマンドを今開いたターミナル、コマンドプロンプトにコピペします。

pip install notebook

もしインストール時に権限がありませんなどのエラーがでてインストールできなかった場合は以下の方法を試してください。

  • Windowsの方:

    左下のwindowsマークを右クリックします。するとメニューが出てくると思うので、その中からコマンドプロンプトを管理者権限で実行するという項目があるのでそれをクリックしてください。

    許可を求めるポップアップがでたら臆せずはいをおして、再度上のコマンドを打ち直してください。

  • Macの方:

    Macだとsudoコマンドが使えます。なので、頭にsudoを加えて打ち込んでみてください。

      sudo pip install notebook
    

    その後、パソコンにログインする際のパスワードを打ち込めばインストールが開始するはずです。

これでJupyterのインストールは完了です。

使い方

1. ディレクトリ(フォルダ)への移動

Jupyter Notebookを使うには、まずターミナルかコマンドプロンプトを起動します。(以降、面倒なのでターミナルと言います。)

次にターミナルでJupyter Notebookを開きたいディレクトリに移動します。

例えば以下のようなディレクトリ(フォルダ)にNotebookを開きたい、保存したいということだったら、


f:id:ryo_udon:20200606155718p:plain


Windowsであれば

cd デスクトップ
cd Jupyter_test

で,

Macであれば、

cd Desktop
cd Jupyter_test

で移動できます。

余談ですが、もうディレクトリができているので、2つのコマンドをまとめても大丈夫です。

cd Desktop/Jupyter_test

また、今回Desktop上にディレクトリを作ることを例として上げましたが、別に場所に指定はありません。

2. Jupyter Notebookの起動

ディレクトリへの移動ができたら、 公式ページ の言う通り、以下のコマンドを打ち込み(コピペし)ます

jupyter notebook

これでお使いのブラウザにJupyter Notebookが自動的に開かれるはずです。

3. 自分のJupyter Notebookを作る

f:id:ryo_udon:20200606155804p:plain

さて、ブラウザに移動すると、おそらく最初は何もnotebookを作っていないので、上の画像のように表示されているはずです。

ではここから、新しくnotebookを作っていきます。

f:id:ryo_udon:20200606154923p:plain

こちらの画像のように右上のNewのボタンをクリックすると新しく作成したいNotebookの種類を選ぶプルダウンが出てきます。

今回はPython3を使いたいので、一番下にあるPython3を選びます。

するとこのような画面に移行して、早速コードを書くことができるようになります。

これでnotebookの作成は完了です。 あとはFileから好きな名前で保存しておきましょう。

4. コードを試しに書いてみる

f:id:ryo_udon:20200606155829p:plain

試しにコードを書いてみましょう。 Jupyter Notebookを編集する際にはいくつか覚えることがあります。

  1. ブロックを編集する際にはクリックするか、カーソルをあわせてEnter(Return)
  2. ブロックを実行したいときは Shift + Enter(Return)
  3. Markdown(Code)にしたいときは、ブロックを選択し、メニューのCell -> Cell Type -> Markdownを選択 (ショートカットキーもあります)
  4. ブロックを下に追加したいときはブロックを選択してキーボードのBを入力する(Shiftはいりません。Bだけです) 逆に上に入れたいときはAを押します。

基本的にはこれさえ覚えていればあとは使っていくうちに覚えます。

また、ショートカットについてもメニューのHelpから見ることができますので、ぜひそちらも確認してみてください。

終わりに

今回はJupyterについて、本当の導入部分を説明しました。

正直、ここからどういうコードを書けばいいのか、わからないという人もいるかと思います。

実は、本当はもっと別のことを書きたかったのですが、そのためにはJupyterの説明をする必要があり、

慌てて作った次第です。

とはいえ、ここで放置するのもあれなので、そんな方向けの簡単なコードを随時追加していきたいと思います。

また、Pythonで書いたものをC,C++に移植して公開もしていきたいと思っているので、またそちらも投稿していきたいと思います。

ではでは。

ポータブルSSDの自作を試しにやってみたよ

ポータブルSSDの自作を試しにやってみたよ

概要

来週には消費税の増税がなされるということで自分も駆け込み購入を色々としておりました。

その中で以前からやりたいと思っていたのが、パソコンとps4の外付け記憶媒体をHDD -> SSDにすること。

しかしSSDは高い。特に1TBでさらに通信速度も、となると2万円台コース。 (もちろん、通信速度を気にしない人なら1万2~3000円で購入できます)

なので、ここは以前から気になっていたポータブルSSDを自分で組んでみようと思った次第です。

参考

以下のサイトを参考にさせていただきました。

外付け(として使う)SSDって今何買えばいいのか調べたメモ

何を購入したのか

今回はあまり奇をてらわず、以下の2つを購入しました。

CT1000P1SSD8JP Crucial Crucial M.2 2280 NVMe PCIe Gen3x4 SSD P1シリーズ 1.0TB

価格:12,980円
(2020/8/1 21:49時点)
感想(6件)

私が購入したときは、SSD:10,887円, ケース:4,500円でした。 ケースについてはもっと安価なものもありますし、予算がという方はここを削るのもありかと思います。 (ただ、安かろう悪かろうの可能性もありますので、ご注意を)

組み立て方

ケース開封

まずケースの方を開封すると以下のようになっています。 f:id:ryo_udon:20190924115124j:plain さらに中を覗いてみるとType-C <-> USB3.0, Type-C <-> Type-C ケーブルと組み立ての際に使用するネジとドライバー、 説明書が入っています。 f:id:ryo_udon:20190924115137j:plain

基本的には説明書に沿えば、簡単に組み上がるようになっていますが、ここはちょっと丁寧に画像をつかって組み立て方を説明します。 まずは、ケースの蓋をType-C端子側にずらして外します。 するとこのようにボードが顔を出します。 f:id:ryo_udon:20190924115259j:plain このボードも少し爪をかけて軽く上に持ち上げて上げると簡単に外れます。 f:id:ryo_udon:20190924113730j:plain 次に、このボードにSSDを取り付けていきます。

SSD取り付け

次に、SSDを取り付けていきます。 f:id:ryo_udon:20190924115411j:plain SSDもまず箱から取り出すとこのようなプラスチックケースの中に入っています。 f:id:ryo_udon:20190924115423j:plain これを取り出してボードに取り付けていきます。 ここでコツとしては、真横から指すのではなく、少し斜めにして少し前後に軽く動かしながら指して上げると簡単に刺さりやすいようです。 ボードに差すとこうなります。 f:id:ryo_udon:20190924113819j:plainf:id:ryo_udon:20190924113807j:plain ただ、このままではケースの中に入らない + 強度的に不安ですので、 付属のネジとスペーサで固定していきます。 画像の金色のパーツがスペーサです。 f:id:ryo_udon:20190924113830j:plain このスペーサの中にはネジが切られているのでボードの裏側からネジで固定することができます。 f:id:ryo_udon:20190924113845j:plain これでSSDの取り付けは完了です。

ケースを閉じる

最後にケースにこれをしまっていきます。 取り外した時と同じく簡単に取り付けられるのですが、Type-C端子がしっかりケースの穴にハマっているか確認してください。 それが確認できると、最後、ケースの蓋を締めて、端子と反対側にあるネジ穴にネジを閉めれば完成です。 f:id:ryo_udon:20190924113858j:plain f:id:ryo_udon:20190924113912j:plain

所感

一応、速度を試しに計測してみましたが、パソコン内部に搭載されているSSDとほぼ同等の書き込み、読み込み速度が得られました。 本体搭載のSSD f:id:ryo_udon:20190924115711p:plain 今回用意したポータブルSSD f:id:ryo_udon:20190924115718p:plain

本体の4倍の容量をこの価格で手に入れられたと考えればかなりお得感があります。

またPS4でも試してみましたが、体感でも分かるぐらい圧倒的にロードが速くなっていました。 試したのはMonster Hunter Worldですがクエスト受注とステージへの移動にかなり時間がかかっていましたが、それがほぼ一瞬でロードできました。 (例として、クエスト受注は猫メシを食べてる間に、クエストステージへの移動は10秒ほど)

唯一懸念点があるのが、発熱の問題。 もともと、このタイプのSSDは発熱がすごいことで有名なので、多少危惧していましたが、案の定でした。 ちょっと触るのも躊躇う温度なので、注意して使っていきたいと思います。 (私は基本的にバックアップはHDDで取っているので、最悪どうにかなるとおもっています。)

ただ、こんなに簡単に作れるので、今後、SSDやHDDは中身とケースを別々に購入していこうかなと思っています。

以上、ポータブルSSDの自作をやってみたよ、でした。

【Python】Discord用トーナメント表自動作成BotをPythonで作ったよ。

TeamMakerボット作成日記 第二弾

概要

第一回はこちら

ryo-udon.hatenadiary.jp

2019/12/05

ご指摘がありましたので追記します。

現在、Discord.pyはver 1.0 ~ になっていますが、現状このTeamMakerボットはver 0.7より以前のもののみ対応しています。

理由は下のリンクにありますが、変数名やクラス構成が大きく変わったことが原因となります。

現在、Discord.pyのversion確認をしつつ、1.0以降も対応できるように変更中です。

変更が完了次第、こちらの記事も追記したいと思います。

discordpy.readthedocs.io

2019/12/27 追記 python 3.8.0, discord.py version 1.2.5に対応したことを確認しました。 ご指摘いただきありがとうございました。

最近は便利になりました。

私の子供の頃では、チーム分けをするにもじゃんけんで延々とアイコを繰り返したり、時間をかけてくじを作ったりととりあえず時間がかかりました。

それこそ人数が10人を超えてくるとそれはもう大変です。

じゃんけんなんか早々決まるわけがないので(私はグッパージャス派でした)、結局チームわけで時間がかかって肝心の遊びの時間がなくなってしまう。そんな悲しい経験を何度もしました。

それに比べて今はどうでしょう?

1人一台スマホやパソコンを持つ時代になり、スマホやパソコンにはチーム分けのアプリがあり、劇的にその時間は短縮されました。

だけど、ちょっとまってください。

皆さんは本当にそれで満足していますか?

チームを作るたびにアプリにプレイヤーの名前を打ち込んで、あるいは参加する人の名前を膨大なリストから選んで。

(リストの人数が少ないって? それならそのままでいいと思いますよ)

意外と時間かかっていないですか?

特に大人数で協力プレイをするゲームをやる方ならなおさらこれを感じているのではないでしょうか?

かくいう私もその1人です。 そんな私が作ったのがこちら。

自動チーム分けBot その名もTeamMaker!!

今回はそんなTeamMakerに新機能を追加したので第二弾としてこのサイトでも公開したいと思います。

github.com

追加機能の概要

今回追加した機能は、トーナメント表の自動作成機能になります。

メンバーを登録すると、その人数に合わせいい感じにシードなども作ってくれるトーナメント表作成機能です。

今回、この機能を実行するに当たり、新たなライブラリが必要となりますので、そのインストールを行います。

sudo pip install pillow

これは作成したトーナメント表を画像として作成するために使用しています。

今回追加した機能の使い方自体は簡単。以下の2つのコマンドを打つだけ。

  • !cup
  • !cupw

それぞれのコマンドが何をするのかを説明します。

!cup

友人たちと熱いトーナメント戦をしたい!! だけどいちいちトーナメント表を作るのは面倒くさい! そんなあなたに魔法のコマンド!

以下の画像のように !cupと打ってみてください。 [f:id:ryo_udon:20190803000649p:plain] するとGeneralに参加したメンバを参加者としたトーナメント表を自動で作成して貼り付けてくれます。

!cupw

ちなみにこのトーナメント表、勝ち負けの判定もできます。

コマンドで !cupw [トーナメント番号] [ラウンド番号] [買った人のID] [4-2などのスコア]を打ってもらうと、 下のようになります。 [f:id:ryo_udon:20190803000654p:plain] これで面倒な記録も必要なし! いつでもどこでも手軽にトーナメント戦ができるようになります!

今回追加したものの構成について

今回は以前作成したTeamMakerに付け加える形で作成しましたが、TeamMaker bot専用の機能にする気はなく、 他のものにも使い回せるようにというのを意識して作成しました。

そこで作成したのが以下になります。


ここにはいずれクラス図を勉強して追加します。 (正しい表記の仕方がわからない)


主に追加したものは * トーナメント表を作る * トーナメント表を描く * 何個のトーナメント表を作るかを判断する * トーナメントを管理する の4つです。 これらの機能を合計で4つのクラスで表現しています(つもりです) また、これに合わせてTeamMakerクラスのスリム化を行いました。

構成としてはBotの根幹をなすBOT_BASEクラスの中にチーム分けをするTeamMakerクラスとトーナメント表の作成などを行う今回新たに作成したCUP_MAKERクラスが内包されているという構成になっています。

BOT_BASEクラスはただそれぞれのクラスを管理しているだけですので、このページではCUP_MAKERのみ説明していきます。

CUP_MAKER

CUP_MAKERクラスを構成する上で重要なクラスが2つあります。

ROUND():

このクラスがトーナメント作成での参加者及び、参加者が誰と対戦するのかを管理するこのCUP_MAKERに格納される最小のクラスになります。 トーナメント表での一番左端の参加者名しか書かれていないところも参加者が1人しかいないラウンドとして管理していきます。 このクラスはGraph構造をイメージしながら、色々と手抜きをしたものになります。

# @brief トーナメント内の各ラウンドの構造体。全てプレイヤーはラウンドの中に格納されて管理される
class ROUND():
    def __init__(self,stageNum, id, members):
        self.roundID = id
        self.stageNum = stageNum
        self.parentID = 0
        self.childID = []

        # それぞれのラウンドの座標を保存
        # ここではしたの*の座標を格納
        #
        # ---|
        #    *---|
        # ---|   |
        #
        self.x = 0
        self.y = 0

        self.hasChildFlag = 0
        self.PositionSetFlag = 0
        self.SetMember(members)
        self.winner = -1
        self.player_id = -1
        self.score = ""


    def SetMember(self, members):
        self.memberList = members
        if len(self.memberList) == 1:
            self.hasChildFlag = 1

    def CreateChildRound(self,roundID):
        if self.hasChildFlag == 0:
            self.hasChildFlag = 1
            halfLine = int(len(self.memberList) / 2)
            child1List = self.memberList[:halfLine]
            child2List = self.memberList[halfLine:]
            newStage = self.stageNum + 1
            child1Round = ROUND(newStage, roundID+1,child1List)
            child2Round = ROUND(newStage, roundID+2,child2List)
            self.childID = [roundID+1, roundID+2]
            child1Round.SetParentID(self.parentID)
            child2Round.SetParentID(self.parentID)
            return [child1Round, child2Round], roundID+2
        return [0], roundID

    def SetParentID(self, parentID):
        self.parentID = parentID

    def SetPosition(self, x, y):
        self.x = x
        self.y = y
        self.PositionSetFlag = 1

    def SetWinnerID(self, id, score):
        self.winner = id
        self.score = score

    def SetPlayerID(self, id):
        self.player_id = id

    def GetWinnerID(self):
        return(self.winner)

各ラウンドには区別するためのIDが割り振られており、ラウンド同士の繋がりを保持するために、親と子のIDをもたせています。

aaaa ---┐ ①
        ├---┐
bbbb ---┘   |②
            ├---
            |

この図を例に説明すると、aaaa, bbbb, , はどれも同じROUNDクラスで表現されています。

①の親は②であり、子はaaaa,bbbbの2つになります。

なので、各ラウンドのメンバの数を確認することでそれが参加者を示すラウンドかどうかを確認することができます。

基本的に勝敗、スコア、参加者名などの情報は全てこのクラスの中に格納されます。

本当はprivate変数などにして外部から直接編集できないように管理すべきところではありますが、ここではかなり手抜きをしています。

TOURNAMENT_MAKER()

次にこのクラスで先程のROUNDを取りまとめてトーナメントの形に整えて上げるクラスになります。

# @brief トーナメント作成用の構造体.
# ここではあくまで登録されたメンバー全員を入れたトーナメント表を作成する機能と勝ち負けの登録のみにしている。
# なので、人数が多い場合や、トーナメントを分けてあげたい場合は複数個のクラスを呼んで上げる必要がある
class TOURNAMENT_MAKER():
    def __init__(self, members):
        self.members = members
        self.tournament = []
        self.start_pos = [ 120, 30]
        # 線を描く際のオフセット値
        self.y_space = 40
        self.x_space = 50

    def CreateNormalTournament(self):
        roundID = 0
        FinalRound = ROUND(0,roundID,self.members)

        self.tournament.append(FinalRound)
        self.largestStageNum = 0

        num = 0
        while len(self.tournament) > num :
            newRounds = [0]
            newRounds, roundID = self.tournament[num].CreateChildRound(roundID)
            if len(newRounds) == 2:
                self.tournament.extend(newRounds)
            if  self.largestStageNum <  self.tournament[num].roundID:
                self.largestStageNum = self.tournament[num].roundID
            num += 1

        self.SetPosition()

    def GetLargestStageNum(self):
        return(self.largestStageNum)

    def SetFirstRoundPosition(self):
        dst_firstRound = []
        for round in self.tournament:
            if len(round.memberList) == 1:
                dst_firstRound.append(round.roundID)
        self.firstRound = []

        for name in self.members:
            for round_id in dst_firstRound:
                if name == self.tournament[round_id].memberList[0]:
                    self.firstRound.append(round_id)
                    break

        for i in range(len(self.firstRound)):
            self.tournament[self.firstRound[i]].SetPosition(self.start_pos[0], self.start_pos[1] + i * self.y_space)
            self.tournament[self.firstRound[i]].SetPlayerID(i)

    def CalcNewCenterPosition(self, roundID):
        childID = self.tournament[roundID].childID
        x = max([self.tournament[childID[0]].x, self.tournament[childID[1]].x]) + self.x_space
        y = 0
        for id in childID:
            y += self.tournament[id].y
        self.tournament[roundID].SetPosition(x, int(y / 2))

    def SetPosition(self):
        self.SetFirstRoundPosition()
        stageNum = self.largestStageNum
        roundList = range(len(self.tournament))
        while stageNum >= 0:
            for round in roundList:
                if self.tournament[round].stageNum == stageNum:
                    if self.tournament[round].PositionSetFlag == 0:
                        self.CalcNewCenterPosition(round)
            stageNum -= 1

    def SetMatchScore(self, roundID, winnerID, score):
        self.tournament[roundID].SetWinnerID(self.firstRound[winnerID], score)

このクラスがやっていることは、

  1. 参加者のリストから必要な数のROUNDクラスを呼び出して、トーナメントの形になるように親、子の関係を作ってあげること

  2. 全てのラウンドの設置位置を計算してあげること

  3. ROUNDクラスに勝ち負けをつける

の3つになります。

そのため、関数は複数個ありますが、実質外部から呼び出して上げる必要があるのは、

* CreateNormalTournament()
* SetMatchScore()

の2つ。

CreateNormalTournament()が1,2番の役割を、SetMatchScore()が3番の役割を果たしています。

  1. については、トーナメントに参加するメンバをどんどん2で割ってゆき、割れなくなったら、トーナメントの追加を終えるという方法にしています。

  2. については、参加者それぞれを格納するラウンドに最初に座標を割り当ててあげ、各ラウンドの中心をそのラウンドの子2つのちょうど真ん中になるようにy座標だけ計算してあげています。(CalcNewCenterPosition())

DRAW_SHAPE()

このクラスでは今回はpillowを使って絵を描きましたが、今後、他のライブラリを使いたいと思うことがあるかと思い、CUP_MAKER()の中でpillowの描画する関数を直接呼び出すのではなく、DRAW_SHAPE()を間に挟んであげています。

これによっていざ変えるときは、DRAW_SHAPE()の中のみを変更すればCUP_MAKER()やこのクラスを使っている他のクラスを全く変更する必要がありません。

import os, sys
from PIL import Image, ImageDraw, ImageFont

class DRAW_SHAPE():
    def __init__(self, image_name):
        self.size_x = 512
        self.size_y = 512
        self.image_name = image_name
        self.SetConfigure()

    def SetConfigure(self):
        self.SetRectangleSize()
        self.SetRectangleFillColor()
        self.SetEllipseFillColor()
        self.SetLineFillColor()
        self.SetEdgeColor()
        self.SetTextSize()
        #self.GetNewImage()

    def GetCenterPoint(self):
        return([self.size_x/2, self.size_y/2])

    def SetCenterPoint(self, x, y):
        self.size_x = x * 2
        self.size_y = y * 2


    def GetNewImage(self):
        self.im = Image.new("RGB",(int(self.size_x), int(self.size_y )),(255,255,255))
        self.draw = ImageDraw.Draw(self.im)


    def SetRectangleSize(self, dx=100, dy=20):
        self.rectangleXSize = dx
        self.rectangleYSize = dy

    def SetRectangleFillColor(self, color="white"):
        self.recColor = color

    def SetLineFillColor(self, color="black"):
        self.lineColor = color

    def SetEllipseFillColor(self, color="white"):
        self.ellipseColor = color

    def SetEdgeColor(self, color="Black"):
        self.edgeColor = color

    def DrawRectangle(self, x, y):
        self.draw.rectangle([(x,y),( x+self.rectangleXSize, y+self.rectangleYSize)], fill=self.recColor, outline=self.edgeColor, width=2)

    def DrawLine(self, x1,y1, x2, y2, width):
        self.draw.line((x1,y1,x2,y2), fill=self.lineColor, width=width)

    def DrawZigZagXtoY(self, x1, y1, x2, y2, width=10):
        self.DrawLine(x1,y1,x2,y1, width)
        self.DrawLine(x2,y1,x2,y2, width)

    def DrawZigZagYtoX(self, x1, y1, x2, y2, width=10):
        self.DrawLine(x1,y1,x1,y2, width)
        self.DrawLine(x1,y2,x2,y2, width)


    def DrawEllipse(self, x1,y1, x2,y2):
        self.draw.ellipse(( x1, y1, x2, y2), fill=self.ellipseColor, outline=self.edgeColor)

    def SetTextSize(self, textSize=10):
        self.textSize = textSize

    def SetModestTextSize(self, char, targetWidth):
        width = self.textSize * len(char)
        if width < targetWidth:
            self.textSize = targetWidth / len(char)

    def SetFontSize(self):
        self.font = ImageFont.truetype(size=self.textSize)

    def SetImageSize(self, x, y):
        self.size_x = x
        self.size_y = y

    def DrawText(self, x1, y1, text):
        self.draw.multiline_text((x1,y1), text, font=self.font, fill=(0,0,0,255))

    def SetFontFile(self, fontFileName, size=10):
        fileDir = os.path.dirname(os.path.abspath(__file__)) + "/../font/" + fontFileName
        self.font = ImageFont.truetype(fileDir, size)


    def ShawImage(self):
        self.im.show()

    def SaveImage(self):
        img_dir = os.path.dirname(os.path.abspath(__file__)) + "/../../image/"
        if os.path.exists(img_dir):
            ret = 1
        img = img_dir + self.image_name + ".jpg"
        self.im.save(img)
        return img

CUP_MAKER()

ここではこれまで説明したクラスを統括し、何個のトーナメントを作ればいいかを判断するクラスであるCUP_MAKER()を説明します。

# @brief TOURNAMENT_MAKER()にメンバーを登録する前の下処理と画像作成をしてあげるクラス
# TOURNAMENT_MAKERを内包しているので、ラッパーとして勝ち負けを登録する関数などを加えている
class CUP_MAKER():
    def __init__(self, member, tournament_name):
        self.tournament = []
        self.start_pos = [-100,0]
        self.line_width = 3
        self.tournament_name = tournament_name
        self.member = member
        self.member_size_array = self.CalcTournamentNum(member)


    def CreateCupTournament(self):
        for member_size in self.member_size_array:
            self.tournament.append(TOURNAMENT_MAKER(self.member[:member_size]))
            if len(self.member) > member_size:
                self.member = self.member[member_size:]
        for i in range(len(self.tournament)):
            self.tournament[i].CreateNormalTournament()

    def CalcTournamentNum(self, member):
        list_size_array = []
        divide_num = len(member)/12
        min = 100
        divide_size = len(member)
        if divide_num >= 1 and len(member)%12 != 0:
            for i in range(6, 13):
                remain = len(member) % i
                if min > remain:
                    min = remain
                    divide_size = i
            divide_num=len(member)//divide_size
            list_size_array = [divide_size for i in range(divide_num)]
            for i in range(min):
                list_size_array[i]+=1
        else:
            list_size_array.append(len(member))
        return(list_size_array)

    def SetRoundWinner(self, tournament_num, round_id, winner_id, score):
        self.tournament[tournament_num].SetMatchScore(int(round_id), int(winner_id), score)

    def DrawTournamentImage(self):
        tournament_num = 0
        image_list = []
        for tournament in self.tournament:
            image_name = self.tournament_name + str(tournament_num)
            ds = DRAW_SHAPE(image_name)
            ds.SetFontFile("font_1_honokamarugo_1.1.ttf", size=12)
            ds.GetNewImage()
            ds.DrawText( 0, 0,self.tournament_name+str(tournament_num))
            for t in tournament.tournament:
                width = self.line_width
                if t.stageNum == 0:
                    # 決勝戦は先にラウンドがないけど横線を引きたい
                    if t.winner != -1:
                        # 勝者のいる場合は太線で描画
                        width *= 2
                    ds.DrawLine(t.x, t.y, 50 + t.x, t.y, width=width)

                if len(t.childID) > 1:
                    for child in t.childID:
                        width = self.line_width
                        if t.winner != -1:
                            # もしこのラウンドの勝者がいる場合,太さを二倍とスコアを表記
                            for member in tournament.tournament[child].memberList:
                                if member == tournament.tournament[t.winner].memberList[0]:
                                    width *= 2
                                    ds.DrawText(t.x + 20, t.y - 20, t.score)
                        # ジグザグの線を描画
                        ds.DrawZigZagYtoX(t.x, t.y, 5+tournament.tournament[child].x, tournament.tournament[child].y, width=width)
                        ds.DrawText(t.x + 5, t.y - 20, "[" + str(t.roundID) + "]")
                if len(t.memberList)==1:
                    ds.DrawRectangle(self.start_pos[0]+t.x,self.start_pos[1]+t.y - 10)
                    member_name_text = " " + str(t.player_id) + "." + t.memberList[0]
                    ds.DrawText(self.start_pos[0]+t.x,self.start_pos[1]+t.y-5,member_name_text)
            image_dir = ds.SaveImage()
            image_list.append(image_dir)
            tournament_num += 1
        return(image_list)

このクラスがやっているのは、TOURNAMENT_MAKER()を何個呼び出せばいいのかという判断と、それぞれのトーナメント表をDRAW_SHAPE()クラスを呼び出して上げて画像に変換してあげるところです。

そのため実はこのクラスではあまり説明することがありません。

もしトーナメント表のデザインなどが好みでないなどがあれば変更するのがこのクラスになると思います。

画像の送信

今回、トーナメント表をjpgファイルで作成し、作成した画像のパスを渡してDiscordの各サーバーに送信しています。 その部分について簡単に説明します。

if image_list != "null":
    for img in image_list:
            await client.send_file(message.channel, img)

私が作成したBOT_BASE()クラスは送信するメッセージ(String型)のみか、 メッセージと画像のパスを格納したリスト型のimage_listを返してきます。

そこで、そのリストに格納されたパスの先にある画像を送ってくれるのが、 send_file()関数です。この関数では送信する先のChannelと送信するファイルを指定するだけで簡単に送信することができます。

まとめ

今回は以前作成したチーム分けのbotにトーナメント表を作成する機能を追加しみました。

かなり手を抜いてしまいましたが、構成としてはそこそこのものができたと思っています。

ただ、今後、これを拡張していく上で、Discord側から受け取るコマンドに対し、どの関数を呼び出すのかというif文が増えていってしまうという問題を抱えているので、今後はそれをまずは対処しつつ、ROUND()クラスを純粋なGraph構造に近づけるように改造したいと思います。

次に加える新機能としては、まずリーグ戦を作れるようにすること。

次にリーグ戦の後にトーナメントを行うような場合に対応させることです。

以上、TeamMaker Botの話でした!!

ブログをスケジュール通りに更新するためにTrelloを使ってみるよ

概要

最近、私生活の忙しさからか、なかなかブログが更新できません。(というより、更新するネタができながらない) そこでいい加減ちゃんとやりたいことを管理して行こうかなと思い、その決意を込めてこれを導入しました。 f:id:ryo_udon:20190617181821p:plain

Trello

Trelloはここ数年で主にIT業界などで使われるようになったプロジェクト管理ツールです。 このツールの特徴は主に以下の5つだと思います。 * 無料で使える * マウスのドラッグアンドドロップで基本操作ができるため、使い方がシンプル * 機能ごとにラベルを分けることもできる * 複数人でプロジェクトの進捗を管理できる * Eleganttを追加することで自動でガントチャートを作ってくれる

特に、5つ目の機能は、視覚的に期限がパッとわかるのはとても便利です。

では使い方を説明していきます。

簡単な使い方

今回、私が公開したプロジェクトは以下になります。 Trello-RYO_Udon

使い方の説明といってもやることは、 * リストの作成 * カードの追加 * カードに期限を設定 * ガントチャートの生成 * カードごとに進捗に合わせ進捗状況の更新とリスト移動

です。 まず、それぞれのエリアの名称からです。 主にエリアは上下に大きく2つに分かれています。 上がガントチャート、下がボードと呼ばれる、やることをメモしたカードを貼り付けるエリアになります。 f:id:ryo_udon:20190617181125p:plain ではそれぞれのエリアをどのようにして使うのかを説明したいと思います。

リストの作成

まずプロジェクトを新たに立ち上げるとこのような画面になります。 f:id:ryo_udon:20190617181156p:plain

まずやることを書いたカードを貼る前に、進捗状況を把握するためのリストを作る必要があります。 イメージとしては

やることリスト一覧 ー> 今取り掛かっていること ー> 終わったこと

のようにリストを作成し、カードの進捗状況に応じて、これらのリストを行き来させて上げるという感じです。

今回はサンプルとしてこのようにリストを作りました。 f:id:ryo_udon:20190617181251p:plain

最後のPending(保留)は進行中に一度入れたものを何らかの理由で一定期間、作業しない、できない状態になったカードを入れるところにしています。

リストができるとあとは今やりたいと思っていることを細かく機能ごとに分けてカードに書いてあげる作業になります。

カードの追加・設定

カードの追加方法は二段階に分かれています。 まず、カードを追加する際はカードの追加をクリックします。

ここではカードの名前を設定します。 今回はこの記事を書くというカードを作ってみたいと思います。 以下のような名前でカードを作成しました。 f:id:ryo_udon:20190617181406p:plain このカードをダブルクリックすると以下のようなウィンドウがピックアップされます。 f:id:ryo_udon:20190617181440p:plain

次にどのプロジェクトなのか区別するためにタグをつけていきます。 タグはデフォルトで十種類ほどの色のタグが用意されており、そのタグに文字を追加することで簡単に区別やソートができるようになります。 f:id:ryo_udon:20190617181546p:plain 次に必要であれば、チェックリストを追加します。

今回だとブログの文章を書くために以下のようなリストを作成しました。 f:id:ryo_udon:20190617181615p:plain ちなみにこのチェックリストはチェックを入れるとチェックリスト内の達成率を示すパーセンテージが変わっていきます。

ちょっと達成感がでていいですね。

次に期限についてですが、これについては普通のGoogleなどのスケジュール表と同じくカレンダーで始まりと終わりを選択するだけです。

これをするだけで、簡単にやることリストを管理することができます。

ガントチャート

最後に視覚的にわかりやすくするガントチャートの追加方法です。 とはいっても追加方法は簡単です。 画面の上の方にある Eleganttと書かれている横をOFFからONに変更してあげるだけです。 最初は登録が必要ですが、一度登録すれば他のプロジェクトでもこの操作一つで再登録の必要もなく簡単にガントチャートを作成することができます。 f:id:ryo_udon:20190617181745p:plain

 最後に

とこのような感じで簡単にプロジェクトの管理ツールの紹介をしました。

これを使って、なるべくスケジュール通りに更新したいなー

/*コードブロックに言語名を表示*/ pre.code:before { content: attr(data-lang); display: inline-block; background: white; color: #666; padding: 3px; position: absolute; margin-left: -10px; margin-top: -30px; } pre.code { padding-top: 30px !important; }