ビットフラグを衝突判定を例に紹介する

2018年のAdventCalendar 1日目です。

と言いつつ12月1日にかけなくて2日になってしまいました。。!!しかも夜中!!!1!

シューティングゲームを作ったときに衝突判定でビットフラグというのを使ったので紹介したいなと思います。(みんな一般的に知っているものなんだろか。。)

通常Bool値を使ってフラグを管理すると思うのですが沢山のフラグを持っていると管理が大変ですしメソッドの引数として渡したりWebAPIのパラメータとして渡すのも大変です。そんな時に一つの値に複数のフラグを持たせることができるのがビットフラグです。


ビットフラグとはその名の通り2進数を使用してtrue(1)、false(0)を表します。例えば次のような値は一桁目のフラグが立った状態です。

0001

今回は衝突判定で解説していくので次のようなフラグを用意しておきます。

let FLAG_PLAYER =        0b00000001 // プレイヤー
let FLAG_PLAYER_BULLET = 0b00000010 // プレイヤー弾丸
let FLAG_ENEMY =         0b00000100 // 敵
let FLAG_ENEMY_BULLET =  0b00001000 // 敵弾丸
let FLAG_ITEM =          0b00010000 // アイテム


そして敵弾丸オブジェクトを作っておきます。フラグとしてENEMY_BULLETを設定しています。

let enemyBullet = EnemyBullet()
enemyBullet.flag = ENEMY_BULLET


そして「プレイヤーがぶつかったのが敵の弾丸だった場合プレイヤーのライフを1減らす」という処理を書いてみました。

class Player {
    /// ライフ
    var life = 10

    /// 物体と衝突した時に呼び出される
    /// object: 衝突してきた物体
    func collision(with ObjectInterface: object)  {
        if (object.flag & FLAG_ENEMY_BULLET) > 0 {
           player.life =- 1
        }
        if (object.flag & FLAG_ENEMY) > 0 {
            // 敵と当たった時の処理
        }
        if (object.flag & FLAG_ITEM) > 0 {
            // アイテムと当たった時の処理
        }
    }
}

衝突の判定にはAND(論理積)を使っています。 ANDはAとBが両方真(true)の場合に真になることを表します。表に表すと次の通り。(基本情報試験のテキストで見たことありますよね)

A B 出力
1 1 1
1 0 0
0 1 0
0 0 0


objectに敵弾丸が入ってきた場合は、object.flagとFLAG_ENEMY_BULLETをAND演算した結果、0よりも大きい数になったのでobjectが敵弾丸であるということが言えます。

    0001 1000 // object.flag
AND 0000 1000 // FLAG_ENEMY_BULLET
-----------------
    0000 1000 = 80より大きい数


上記の例では引数で渡されたobjectに「プレイヤーの弾丸」が入ってきた時、objectが敵の弾丸であるか、敵であるか、アイテムであるかを判定するまで処理を終えることができません。 イージーモードの道中であれば一つ一つのオブジェクトにこのような判定をしていても良いかもしれませんが某ゲームのこんな画面だった場合少しでも実行する処理を少なくしたいものです。

f:id:withoneAdventCalendar:20181202234130p:plain
20181201_1


なので次のように修正してみることにします。

class Player {
    /// ライフ
    var life = 10

    /// ★追加:衝突対象
    let collisionFlags = ENEMY | ENEMY_BULLET | ITEM // 0001 1100

    /// 物体と衝突した時に呼び出される
    /// object: 衝突してきた物体
    func collision(with ObjectInterface: object)  {
        // ★追加:衝突対象であるかを判定して対象でなければ
        // この先の処理を行わない
        if (object.flag & collisionFlags) == 0 {
            return
        }
        if (object.flag & FLAG_ENEMY_BULLET) > 0 {
           player.life =- 1
        }
        ・・・省略・・・
    }
}

プレイヤーと衝突可能な物体のフラグをcollisionFlagsとして持つように修正しました。 「ENEMY | ENEMY_BULLET | ITEM」(00011100)が入っています。 こうすることによって、objectにプレイヤーの弾丸が入ってきた場合はcollisionFlagsとAND演算をした結果、どこの桁もtrueにならないため衝突対象にないということになります。

    0001 1100 // collisionFlags
AND 0000 0010 // プレイヤーの弾丸
-----------------
    0000 0000 = 0

ビットフラグでの判定は処理が早いらしいのですが最近のパソコンやスマホでそこまで違いがあるのかなーという気もします。
また、余談ですがDBのカラムにフラグが増えてきてしまった場合にもこのビットフラグを使用することもあるようです、 ただ、intで格納されている値を、5 = 0101 = 1番目と3番目のフラグがONだな!とパッとわかるかといえばそうではないと思うので可読性が良くないのと、フラグが追加される分には簡単ですが廃止されるとややこしいといった点もありますしインデックスが効かないのではという懸念もあるため使いどころが限られるかもしれません。 (ビットフラグ用のインデックスがあるというのを誰かがTwitterでつぶやいていたのを見た気がしたのですが該当する資料が見つけられず。。ご存知の方いたらぜひ教えていただきたいです。)

サイトデザインを考えてみる

見やすいサイトとは何か

世の中にはたくさんのサイトが公開されています。

しかし、そのすべてが見やすいものではありません。

では、「見やすいサイト」とは何でしょうか?

今回は「見やすいサイト」にするために活用できる簡単なデザイン方法などを紹介していこうと思います。

Vertical Rhythm

Vertical Rhythmとは、ページの要素間の縦のスペースをそろえるようにする概念をいいます。

一見は百聞に如かずなので、画像どーん。

f:id:withoneAdventCalendar:20171224175847p:plain
f:id:withoneAdventCalendar:20171224175903p:plain

※水色ブロックはどれも全て同じ大きさです。

パターン1は間隔をそろえて配置しています(Vertical Rhythm)が、パターン2はそろえていません。

人の脳は同じ要素が繰り返されることによって「パターン」を認識し、記憶します。そして、その「パターン」をとりだし認証することで、対象物を認識するようなつくりになっています。

Vertical Rhythmはその脳のつくりに沿っているので、見やすく感じられます。

実際にどうするのさ

Vertical Rhythmは高さのルールを決めることで簡単にできます

  1. まず、基準となる高さを決める(etc.最少となる要素のline-heightの数値)
  2. 間隔や各要素の高さを基準の倍数に設定する

これだけです。

カラーリング

割合

 WEBサイトの配色は3つで、かつ以下の割合で設計すると美しい外見になるといわれます。

  • ベースカラー 70%
    特に背景などサイトの中で一番大きな面積を占める色。
  • メインカラー 25%
    サイトのテーマカラー的存在。ヘッダーの背景などによく使われる。
  • アクセントカラー 5%
    1番面積が少ない色だが、1番目立つ色。
配色パターン

 色についても、1色が違うだけでサイトの雰囲気がガラリと変わります。

配色を簡単に確認することができるサイト( Color Supply )を例に4つの配色パターンを紹介します。

  • 捕食

    f:id:withoneAdventCalendar:20171224214942j:plain
    色相環の対岸に存在する色を選ぶパターンです。
    全く違う色味同士のため、インパクトのある配色ですが逆に悪目立ちする場合もあります。

  • 類似色

    f:id:withoneAdventCalendar:20171224215433j:plain
    色相環の隣同士の色を選ぶパターンです。
    同系色の色となるため全体的に落ち着きのある色味となります。
    その代わり、ハッキリとした印象からは遠ざかります。

  • トライアド配色

    f:id:withoneAdventCalendar:20171224215852j:plain
    色相環に正三角形を描くように色を選ぶパターンです。
    ハッキリとした色味にしたい場合にお勧めです。

  • スプリットコメンタリー

    f:id:withoneAdventCalendar:20171224220334j:plain
    ある色と、その補色の両隣に位置する色を選ぶパターンです。
    捕色のようなインパクトのある色味にしたいけど、もう1色加えてバランスを取りたい場合にお勧めです。

  • テトラディック

    f:id:withoneAdventCalendar:20171224220542j:plain

    正四角形を描くように色を選ぶパターンです。

    華やかな印象にはなりますが、かえって色が散らかり、見づらいサイトにもなりうる配色です。

最後に

いくつかのデザイン方法を紹介しましたが、これらはあくまで「見やすいデザインにするための技術メモ」にしかすぎません。
「見やすいサイト」を作るには「なぜそのレイアウトにしたのか、なぜそのデザインにしたのか」の理由付けが最大の要だと私は考えます。
小手先だけの方法論に頼らず、根拠のあるデザインをすれば、おのずと見やすく、利用しやすいサイトになるのではないでしょうか。

Excelで資料を見るときのちょっとしたこと

WithOne Advent Calendar 2017 の 23日目です

 

今回はExcelで作成された資料(仕様書など)を確認する時に

教えていただいたことを書きたいと思います。

 

■資料を見ている時に・・・

 ファイルサーバ上にある資料を確認している時に

 これをよく見かける方は多いかなと思います。

f:id:withoneAdventCalendar:20171223154858p:plain

※こちらの画像は以下のサイトを参照いたしました。

Office TANAKA - Excel VBA Tips[ブックが開かれているかどうか調べる]

 

 この状態だとファイルの編集ができない読み取り専用の状態になってしまい
 編集するには閲覧している人に一度ファイルを閉じてもらう必要があります。

 ・・・資料を編集する時は少し手間になるかなと思います。

 

 そこで資料を見るときのちょっとした方法があります。

 

 ①開いているファイルを「読み取り専用」に切り替える。 
 ・「ファイル」→「オプション」→「クイック アクセス ツールバー」を開く
 ・「読み取り専用の設定/解除」を追加する。

f:id:withoneAdventCalendar:20171223154910p:plain

 設定した後に、ファイルを読み取り専用にすると・・・

 

 【読み取り専用前】

f:id:withoneAdventCalendar:20171223160803p:plain

 

 【読み取り専用後】

f:id:withoneAdventCalendar:20171223160809p:plain

 今開いているファイルが読み取り専用になりました。

 

 ②ファイルを開くときに「新規作成」で開く。

 ・「ファイルを右クリック」→「新規」で開く。

f:id:withoneAdventCalendar:20171223162025p:plain

 

 これで開くとファイルの内容は同じですが、別物としての扱いになります。
 ファイルをロックする必要がありません。

 

■最後に

 仕事をしている中で何度が同じようなことがあったので、

 この方法を知ってから資料の見方が少し変わりました。

 今後もこういうちょっとしたことを知って使っていきたいと思います。

CakePHPを勉強する人へ

私は入社半年の新人プログラマーです。研修では2か月間、Javaを勉強したのですが、配属された後、CakePHPというPHPフレームワークを使うことになり、大変苦労して学習しました。

そこで、今後Cakeを勉強しようとしている方や、未来の新人プログラマーの助けに少しでもなればと考え、私なりにCakePHPの基本について記事を書いてみることにしました。

何と言っても自分がつい半年前に頭を抱えていたヴィヴィッドな記憶があるので、それを生かして、自身の経験、理解するための工夫等を伝えられればいいなと考えております。是非ご一読下さい。

 

  1. そもそもMVCとは
    CakePHPは、MVC(Model-View-Controller)という構造を採用しているフレームワークである。しかし、大半の新人プログラマーはまずこの概念を理解するのに苦労するのではないだろうか。私もそうだった。
    そもそも1ファイルの中であれこれプログラムを書くことでさえヒィヒィ言いながらやっている状態なのに、それが3つに分かれる!?となれば混乱するのも当然だ。しかも、MVCでググってみたところで、意味不明の用語が並ぶスーパー分かり辛い解説しか出てこない。
    ならば、出来る限り難解な専門用語を控え、簡潔にこの構造を説明することこそ私に課せられた使命であろう。
    なお、概念の解説だけではすこぶる分かり辛いので、実際にコードを書きながら解説していくが、ひとつ留意点がある。
    それは、コードを書くにあたってCakeのバージョン2.x(以下、2系)を使用していることである。必然的に解説も2系寄りのものになるが、幸い自分には3.x(以下、3系)の経験も少しあるので、相違点を補完できる所は出来る限り補完していく。

    モデル(M)
    PHPはWeb開発に適した言語であるが、Web上で動く何らかのサービスを創ろうと思えば、とりあえずDBとの接続は必要になってくる。
    たとえば簡単な掲示板を作るとしよう。そうすると、画面上にはとりあえず過去の投稿のタイトルと内容くらいは最低でも表示されることになる。
    じゃ、タイトルとかのデータはどこに置いておくの?

    f:id:withoneAdventCalendar:20171214155033p:plain
    こういう構造のDBに入れておく。(ここではMySQLというDBに入れてある。)
    ただし、DBは、あくまでデータの入るただのだ。それ以上の機能はない。
    データを使って画面に投稿を表示したり、画面から投稿されたものを箱に入れたりするのはプログラムの役目になるわけだ。
    CakePHPで開発をするには、Cake側(データを使う側)とDB側(データを保持する側)を接続しなければならない。
    簡単な設定だけでこれをやってくれるのがモデルクラスであり、DBからデータを検索して引っ張ってくるメソッド(find)やDBにデータをセーブするメソッド(save)等、様々なメソッドが用意されている。
    要は、DBと直接レコードのやりとりをする処理は全部ここでやっちまおう!というクラスになる。

    モデルを作る
    実際に上記のテーブルから掲示板を作ってみることにする。
    まず重要なのが

    f:id:withoneAdventCalendar:20171214155033p:plain
    適当に作ったこのテーブルの名前。
    掲示板のデータが入るので掲示板っぽい名前なら何でもよいのだが、CakePHP の規約(重要)に従ってboardsテーブル(単語の複数形)とかにしておく。
    そして、このテーブルと接続してデータの取得、更新を行ってくれるモデルクラスのファイルBoard.phpはこんな感じ。

    f:id:withoneAdventCalendar:20171225093458p:plain

    気を付けること。クラスファイルを作る場所を間違えないこと(2系ではapp/Modelの下)。クラス名をCakePHP の規約(ほんとに重要)に従って単数形にすること。Cakeの用意してくれてるモデルクラス(2系ならAppModel)を継承すること。以上。
    データをどうこうしてくれるメソッドとかは全部Cake側で用意されているため、そいつを継承してしまえば、ただ単にデータをどうこうするだけなら何も書かなくてもできる。マジで。

    ※3系では
    モデルがTableクラスとEntityクラスという謎クラスに分かれているが、基本は同じ。

    CMS チュートリアル - データベース作成 - 3.x
    上記リンクを参考に、規約に沿った階層に規約に沿った名前で、Cakeのクラスを継承して作れば、基本的なメソッドは全部使えるはず。


    ビュー(V)

    ビューとは文字通り、実際にクライアント(サービスを使う側)がURLにアクセスした際に見ることのできる出力部分、つまりWebページだと考えてよいだろう。
    Webページの枠(見た目)を定義するHTMLにPHPを組み込むことで、Webページ上でデータを扱わなければできない複雑なことが簡単に出来てしまう。
    例えば、掲示板の投稿内容等のデータを変数に入れておき、その変数を出力することで初めて、掲示板を訪れた人が過去の投稿を見ることができるのである。
    また反対に、来訪者が入力したものを投稿する為の掲示板の書き込みフォーム等も、CakePHPのビューなら簡単に作ることができる。
    要は、ページ上でユーザーとの接点を持つのは任せろ!データの入出力処理とか俺がやってやる!ってな感じの部分である。

    ビューを作る
    早速、掲示板の投稿が表示されるようなビューを作ってみよう。
    Cakeではctpという拡張子でビューファイルを作る決まりになっている。また、これも慣例のようなものだが、一覧表示を担当するビューファイルは、Cakeでは「index(元々は索引という意味)」という名前になる。
    よって、index.ctpという名前でビューファイルを作ってみよう。
    このファイルを、また所定のディレクトリに配置する。ディレクトリは、2系ならapp/View/モデル名の複数形、3系ならsrc/Template/モデル名の複数形となる。

    f:id:withoneAdventCalendar:20171225112120p:plain

    配列の中に入ってる数だけぐるぐる回してくれるforeach文で全体を囲うことで、投稿が100件あろうが1000件あろうが全件表示できる。
    但し、まだこの段階では、このページのurlを叩いても何も表示されないどころかエラーになることに注意されたし。
    ここで使用している変数$postsは、文字通りただの変数なので、ここにデータの配列を入れないと表示ができないのは当然だ。
    しかし、この段階では、モデルでDBからデータを取ってくる準備は整った、ビューでデータを表示する準備は整ったという状態でしかない。実際にデータを取ってきてビューに渡すという行為を、誰も行っていない。
    いまいちパっときていない人も、この先を読み進めれば多分理解して頂けるだろう。

    コントローラ(C)
    モデルがDBとの接続、ビューがクライアントとデータのやり取りをやってくれるというのは分かった。
    でも、実際に、モデルがDBからとってきたデータをどうやってビューに渡すの?逆にビューで入力されたデータをどうやってDBに格納するの?という疑問が出てくる。
    その橋渡し的な役割をやってくれているのがコントローラクラスである。コントローラを調べると、「モデルとビューの制御」とかよく書かれているが、私は、「モデルとビューを繋げる為に頑張ってくれてる凄いやつ」くらいに理解したほうがすんなり頭に入り易かった。

    コントローラを作る
    やはり分かり辛いので、先程の掲示板の例で見ていこう。
    CakePHP の規約(とても重要なので100回くらい読むべし)によると、コントローラ名はモデル名の複数系+Controllerとなっている。
    よって、BoardsController.phpを、2系ならapp/Controllerの下、3系ならsrc/Controllerの下に配置する。

    f:id:withoneAdventCalendar:20171225132857p:plain
    で、こんなコードを書く。
    解説しよう。Boardsという名前のコントローラーなら、自動的にBoardという名前のモデルをクラス内で呼び出せるようになっている。これがCakeの規約だ。
    つまり、先程作ったBoardモデルのメソッドを呼び出せるということだ。
    8行目の$this->Board->find('all')というのはつまり、「このクラスでBoardモデルのfindメソッドを呼び出しますよ」という記述。
    で、findメソッドとは何なのかというと、DBから条件に沿ったデータを取得してくるメソッドだ。ここでは全件取ってきたいので、引数に’all’を指定する。
    findメソッドの戻り値は$postsという変数の中に格納する。
    これでモデルからコントローラにデータが渡った。
    ちなみに$postsの中身はこんな感じ。

    f:id:withoneAdventCalendar:20171225135043p:plain

    f:id:withoneAdventCalendar:20171214155033p:plain
    DBの中に入ってたデータが、ちゃんと配列の形式で取れているのがわかる。
    ちなみに、3系では配列ではなくオブジェクト型というよくわからない形で取ってくるが、foreachとかで回して表示する分には配列と同じように使えるので、あまり問題はない。
    12行目のsetメソッドは、「第二引数のデータを、第一引数の名前でビューに変数として渡しますよ」というメソッド。
    index()というアクション(コントローラークラス内に書いたメソッドのこと)の中でsetしていれば、Cakeが勝手に先程作ったindex.ctpに変数を渡してくれる。これも規約通りだ。
    これで、モデルのメソッドを使って取得したデータをビューに渡すという一連の流れを、コントローラがやってくれたことになる。橋渡し役という自分の理解も少し伝わったのではないだろうか。

    さて、この状態でブラウザから
    自分の環境のipアドレス/コントローラ名(Boards)/アクション名(index)にアクセスすると・・・
    まずコントローラに書いたアクションの処理が走り、それからビューを描画する。
    indexアクションの中では、モデルのメソッドを使ってデータを取得し、それをビューにセットしている。その処理後にindex.ctpが描画されるので・・・

    f:id:withoneAdventCalendar:20171225141617p:plain
    できた!!!!!!!!!!!!
    データ取得(モデル)→セット(コントローラ)→表示(ビュー)の流れとMVCの概念を、少しは理解して頂けただろうか。


  2. 逆方向の流れもやってみる
    せっかくなので、ビューでデータを入力してそれをDBにセーブするまでの流れも、先程の掲示板を使ってやってみる。要するに掲示板への書き込みも出来るようにする。
    まず、index.ctpと同じ階層に新しいビューファイル=新しいページを作ってみよう。データを追加するページなので、add.ctpという名前にする。中身はこんな感じ。

    f:id:withoneAdventCalendar:20171225155451p:plain
    ビューファイルなのにほとんどHTML書いてねえじゃねえか!!というツッコミが入るであろうが、実はこれ、Cakeに元々用意されていたメソッドを使ってHTMLを出力しているに過ぎない。
    例えば$this->Form->createやらendやらはformタグ(ここからここまで入力フォームですよ、というタグ)を出力しているだけだし、$this->Form->inputはinputタグ(サイトによくあるユーザーが文字とかを入力できる部分)、$this->Form->buttonはbuttonタグ(よくある送信ボタン)を出力している。
    要は、Formというクラスのメソッドで色んなHTMLのタグを出しているんですねー。
    間違えてはいけない箇所が2つ。$this->Form->createの引数はモデル名と同じにする。$this->Form->inputの第一引数はDBのカラム名(titleやらbodyやら)と同じにする。以上。
    こうすることで、「このモデルが接続しているDBの、このカラムにデータをセーブするぜ」的な入力項目が出来上がる。

    次、Boardsコントローラにも、indexアクションの下に新しくaddという名前のアクションを作る。

    f:id:withoneAdventCalendar:20171225161339p:plain
    解説しよう、17行目のif文は、「リクエストがPOSTの時(画面からsubmitボタンが押された時)だけ以下の処理を実行しますよ」というもの。つまり、先程のindexアクションとは違い、普通にURLを叩いてこのページに飛んだ時(GETリクエストという)は何も処理が走らず画面だけが描画されて、画面から「投稿」ボタンが押されたら下のセーブ処理が走る。
    20行目は、先程のfindと同じようにBoardモデルのメソッドを呼び出している。今回はDBにデータを登録したいので、saveメソッドを呼び出す。
    ちなみに投稿ボタンが押された時点で、画面から入力されたデータは$this->request->dataという変数の中に自動的に入ってきている。これをsaveメソッドの引数にすることで、モデルに渡してデータをセーブできる。
    つまり、先程の一覧表示とは逆方向の、ビューからデータを受け取りモデルに渡す、という橋渡しをコントローラがやっている。
    saveメソッドの戻り値は、セーブが正常に成功したらtrue、何らかの原因で失敗したらfalseが返るようになっている。つまり、20行目のif文の中には、セーブに成功した時に入る。
    んでもって23行目の記述は、セーブに成功したのにずっと投稿画面のままって何か変なので、redirectというメソッドを使って一覧表示画面(さっき作ったindexアクション→index.ctp描画の流れ)にワープさせているものである。必須ではないが、あるとサイトの挙動として綺麗になる。

    じゃあ登録やってみる
    先程と同じノリで、ブラウザから自分の環境のipアドレス/コントローラ名(Boards)/アクション名(add)にアクセス。

    f:id:withoneAdventCalendar:20171225170723p:plain
    入力フォーム的なやつができてる!!
    先程述べた通り、この段階ではURLを叩いただけなので、何も処理は走らず画面だけが描画されている。
    新しい投稿を入力してみる。

    f:id:withoneAdventCalendar:20171225170957p:plain
    下の「投稿」ボタン(見にくい)を押すと、画面に入力した文字列がコントローラのaddアクションに飛ぶ!この時はPOSTなので処理が開始される!
    addアクションのなかの$this->request->dataは次のようになる

    f:id:withoneAdventCalendar:20171225171240p:plain
    これをsaveメソッドの引数に渡しているので、セーブが成功すればDBに新しいデータが登録された後、indexアクションにリダイレクトされる。
    indexアクションでは新しく更新されたDBからデータを引っ張ってきて表示するので・・・

    f:id:withoneAdventCalendar:20171225171527p:plain
    新しい書き込みが増えている!!!!!!!!!


  3. まとめ
    1では、モデルがDBから引っ張ってきたデータをコントローラの変数に渡し、それをビューに渡すことでデータを表示する流れをやってみた。
    2では、ビューから入力されたデータがコントローラの変数の中に入り、それをモデルに渡してDBにデータを登録する流れをやってみた。

    と、いうわけで、CakeにおけるMVCとデータフローを私なりに図にしてみるとこんな感じになる。

    f:id:withoneAdventCalendar:20171215132324p:plain

    ※あくまで私なりの理解なので、多分、ググって出てくる図とは少し違う。
    オレンジの矢印が1でやったデータ取得の流れ、黄色の矢印が2でやったデータ登録の流れになる。
    勿論、DBの操作は取得、登録だけではない。掲示板なら、例えば過去の投稿を編集する機能(更新)も必要だろうし、不要な投稿を削除する機能も不可欠だろう。
    しかし、どんな処理であろうと、必ず上のオレンジか黄色の矢印のどちらかの流れに当てはまると自分は考えている。要は、DBから得られた情報でビューを描画するか、ビューで得られた情報でDBを操作するかの二択である。
    そして、モデルには他にも様々な手法でDBを操作するメソッドが多数用意されている。
    つまり、これから実装しようとしている処理では、データがどのような流れを描くのかということを理解し、その処理に即したモデルのメソッド(これはその場その場でググって覚えるしかない)を使えば、少なくともMVCフレームワークを用いたWebアプリケーションにおいて大局的に迷子になることはない。
    自分はそのような考え方で、半年間CakePHPを勉強してきた。この記事が、将来Cakeを学ぶ誰かの助けになることを切に願って、本稿の締めとさせて頂く。
                            ~fin~

Excelを使った文字列の比較

どうも。WithOne Advent Calender 2017の12月20日分担当、稲岡です。

 

今年の私は、Excelで資料を作ったり、サーバー上のパッケージ更新や既存ソースの更新をしたりと、あまり「プログラムを書く」ことはありませんでした。
ですが今回、パッケージ更新作業で必ずやることのひとつである「更新前後のバージョン比較」について、Excelのマクロを使ってプログラムを書いてみました。

 

これまではずっと、

  • 現在導入されているパッケージを確認
  • ↑の内容と、更新対象になるパッケージを目視で比較

という感じでやってきたのですが、これがまぁめんどくさい。

似たような文字列が並んでいるので、どれを見てるのか、どこが変わっているのか、たまに空目してしまいます。
そんなことを思いつつ作業してきたのですが、最近Excelで「文字列を比較し、違う箇所の色を変える」という処理を作ってみたのでご紹介します。

 

処理の流れ

今回の処理の流れは下記のとおりです。

 1. インストールされているパッケージの情報を取得
 2. その中からバージョンを抽出
 3. 更新後のパッケージ情報を取得
 4. 同様に、バージョンを抽出
 5. 文字列を比較

 

このうち、

 1.と3.についてはTera Termなどのターミナルにコマンドを打って取得、

 2.と4.についてはExcelの数式で抽出、

 最後の5.はマクロで比較、

という形です。

 

パッケージ情報の取得

まず、パッケージの情報を取得します。

情報を取得するコマンドで自分が使ったことがあるのは、下記の2つです。

  • yum list installed | grep xxx
    yumコマンドを使ったパッケージ情報の取得方法です。
    この方法で取得したデータは「パッケージ名」「バージョン」「リポジトリ」の順で一覧表示されます。
    なお、末尾の「xxx」は絞り込みの条件ですので、インストールされている、すべてのパッケージを確認したい場合は「|」以降の記載は不要です。
    今回はこれで取得した情報からバージョンを抽出しました。

  • rpm -qa | grep xxx
    rpmコマンドを使ったパッケージ情報の取得方法です。
    こちらはyumコマンドとは異なり、インストールされているrpmファイル名が表示されます。
    末尾の「xxx」は、yumコマンドの時と同様、絞り込みの条件です。インストールされている、すべてのrpmファイルを確認したい場合は「|」以降の記載は不要です。

 

パッケージ更新前のバージョンを抽出

次に、取得したパッケージの情報を任意のセルに貼り付けます。

このとき「パッケージ名」「バージョン」「リポジトリ」の各情報を個別のセルに配置するようにします。

# 配置されないときは、メニュー「データ」の「区切り位置」で設定を変更できます。

貼り付けた後は「パッケージ名」から末尾にあるアーキテクチャ(「.x86_64」や「.noarch」など)を除外します。

 

この時に使った数式がコチラ


    =IF(W8="","",MID(W8,1,IF(ISERROR(FIND(".",W8)),"",FIND(".",W8))-1))
    

 

パッと見、何をやっているかさっぱりだと思うので説明しますと、

  • "."がないかどうかを検索する
  • "."がなければ空白、あればその位置(何文字目にあるか)を出力する。
  • "."が現れた位置までの文字列を抽出する。

という処理になります。

一番最初に出てくる「IF」については、表内の見栄えをよくする(空欄だったら何も表示させない)ためだけに記載しています。

これでアーキテクチャが除かれたパッケージ名が抽出できました。

 

あとはパッケージ名を基にバージョンを抽出するだけです。

この時に使った数式がコチラ


    =IF(X8="","",IF(ISERROR(VLOOKUP(C8,$V$8:$Y$1000,3,FALSE)),"",VLOOKUP(C8,$V$8:$Y$1000,3,FALSE)))
    

 

相変わらず文字ばっかりで嫌になりそうですが説明しますと、

  • 確認したい対象のパッケージ名が、アーキテクチャを除いたパッケージ名の中に存在しないか。
  • 存在した場合、それに該当するバージョンを取得する。

以上です。意外とあっさりしています。

ここでも、一番最初に出てくる「IF」については、表内の見栄えをよくするためだけに記載しています。

これで更新前のバージョンが取れました。

 

パッケージ更新後のバージョンを抽出

続いて、パッケージ更新後のバージョンを抽出です。

パッケージ情報の取得、そしてアーキテクチャを除いたパッケージ名の抽出については前述のとおりなので割愛します。

パッケージ更新後のバージョン抽出だけ、更新前と数式が変わります。

その数式がコチラ


    =IF(ISERROR(VLOOKUP(VLOOKUP(C8,$V$8:$Y$1000,2,FALSE),$AD$8:$AF$1000,2,FALSE)),"",VLOOKUP(VLOOKUP(C8,$V$8:$Y$1000,2,FALSE),$AD$8:$AF$1000,2,FALSE))
    

 

もう嫌になりそうですね。

この式では、

  • パッケージ更新前に参照したパッケージ名を抽出する。
  • それと同じパッケージ名のバージョンを抽出する。

となります。

なぜパッケージ更新前と同じ方法にしないかというと、パッケージ更新後は、パッケージ名は同じだが、アーキテクチャが異なるものがインストールされる場合があるからです。

ソート順次第で回避できるかもしれませんが、それでは確実ではありません。

なので、このような数式を用いています。

 

マクロを用いた文字列の比較

 さて、これでパッケージ更新前後のバージョンを取得できました。

ここからは、VBAで作成したマクロを使ってバージョンを比較していきます。

今回作成したマクロの流れは下記のとおりです。

  • パッケージ更新前後のバージョンの文字列とその文字数を取得
  • 1文字目から文字数の少ない方までを、1文字ずつ比較
  • 同じ文字の場合はそのまま、異なる文字の場合は色を赤に変更

# パッケージのバージョンは厳密にいうと「バージョン」「リリース番号」に分けることができますが、今回は一旦保留にしています。

 

この流れに沿って作成したマクロがコチラです。


    Sub バージョン比較()
    
    ' データの定義
    Dim old_version As String
    Dim new_version As String
    Dim i As Integer
    Dim check As Integer
    Dim old_ver_length As Integer
    Dim new_ver_length As Integer
    Dim check_length As Integer
    
    Dim P As Integer
    Dim Q As Integer
    Dim R As Integer
    Dim S As Integer
    Dim st As Integer
    Dim ed As Integer
    
    ' データ(定数)の設定
    P = 16
    Q = 17
    R = 18
    S = 19
    st = 8
    ed = 57
    
    ' 処理開始
    For i = st To ed Step 1
    
        ' データの初期化
        check = 0
        old_version = ""
        new_version = ""
        Cells(i, Q).ClearContents
        Cells(i, S).ClearContents
    
        ' 比較対象の設定
        old_version = Cells(i, P).Value
        new_version = Cells(i, R).Value
    
        Cells(i, Q).Value = Cells(i, P)
        Cells(i, S).Value = Cells(i, R)
    
        Cells(i, Q).Font.ColorIndex = 0
        Cells(i, S).Font.ColorIndex = 0
    
        ' 比較対象の文字数取得
        old_ver_length = Len(old_version)
        new_ver_length = Len(new_version)
    
        ' 比較文字数の設定(数が少ない方を優先)
        If old_ver_length < new_ver_length Then
            check_length = old_ver_length
        ElseIf old_ver_length > new_ver_length Then
            check_length = new_ver_length
        Else
            check_length = new_ver_length
        End If
    
        ' 比較処理
        For check = 1 To check_length Step 1
            If Mid(old_version, check, 1) <> Mid(new_version, check, 1) Then
                Cells(i, Q).Characters(check, 1).Font.ColorIndex = 3
                Cells(i, S).Characters(check, 1).Font.ColorIndex = 3
            End If
        Next check
    
    Next i
    
    Range("A1").Select
    
    End Sub
    

 

# 記載を省けるところ、改良すべき点は多々あると思いますがどうかご容赦ください。

見慣れない記述が多いですが、基本的には

『「どのセルの」「何々」=(は)「これ」』

という形になっています。

例えば、


    Cells(i, Q).Value = Cells(i, P) ※"i"はループ変数
    

の場合、


    セルQi [Cells(i, Q)] の値 [.Value] はセルPi [Cells(i, P)] ※"i"はループ変数
    

といった形です。

 

実際に動かしてみた

準備ができたので、実際に動かしてみます。

今回は、下の2つのパッケージの差分が取れるかを確認してみます。

  • kernel.x86_64 (バージョン:2.6.32-696.13.2.el6)
  • kernel.x86_64 (バージョン:2.6.32-696.16.1.el6)

その結果がコチラ

f:id:withoneAdventCalendar:20171220175934p:plain

実行結果

# 文字がつぶれてますね・・・、すみません・・・。

今回の場合は、確認するパッケージ名(kernel)をセルC8に、更新前後のパッケージの情報をW8、X8、AD8、AE8のセルにそれぞれ入力します。

画像では省略していますが、作成したマクロを設定したボタンをクリックすると、Q8、S8のセルにバージョンの比較結果が表示されます。

問題なく、差分の箇所が赤文字で表示されました。

 

最後に (作成してみての感想など)

 普段、Excelを使う機会が多い自分ですが、マクロを使って何かすることは一切ありませんでした。

「効率よく」業務をする上では、こういった工夫も必要だな、と改めて思うことができました。

 

ただ、今回作成したこの機能もいろいろと不具合がありまして・・・。

例えば、

  • バージョンとリリース番号をまとめているため、バージョンが変わる(例、1.9 → 1.10)と、それ以外に変更がない部分も赤文字で表示されてしまう
  • パッケージ名 (画像でいうW列とAD列) が同名でバージョンが異なるものが存在する場合は、うまく検知できない可能性がある
    (VLOOKUP関数は上から走査し、最初に見つけたデータを返してしまうため)
  • yumコマンドで取得したデータしか比較できない
    (rpmコマンドの場合、枠や式を作り直す必要がある)

などです。

上記の不具合は今後、業務の合間に直していきたいと思います。

 

以上、長文にお付き合いいただき、ありがとうございました。

関数ポインタについて

これと言って語れるようなことがなかったので、専門の時に習った関数ポインタについて話そうと思います。

 

C言語での話になりますが、他の言語でも応用できたりするのかなと少し考えてみたりしています。

 

*そもそも関数ポインタとは

関数をアドレスで持って変数にしまってしまおう、というもの。

そのままですね。 



// プロトタイプ宣言
void hoge(void);

int main() {
    void (*p)() = hoge;
    p();
    return 0;
}

void hoge() {
    printf("hoge 呼ばれた。");
}

久々にCを触ったせいで何度もセミコロンを忘れてコンパイラーにとても怒られました

つまり、変数にぶちこめるということは、配列にぶちこむこともできるということ。



// プロトタイプ宣言
void hoge1(void);
void hoge2(void);

typedef void (*p)();
p piyo[] = {hoge1, hoge2};

int main() {
        piyo[0]();
        piyo[1]();
        return 0;
}

void hoge1() {
        printf("hoge1 呼ばれた。\n");
}
void hoge2() {
        printf("hoge2 呼ばれた。\n");
}

わざわざtypedefする必要もなかったような…気もしますが…

今は頭が回ってないのでとりあえずはこれで…

 

配列に入れて何の利点があるんだという話ですね。

switch文で呼ぶメソッドが変わる、なんていう時に使えば、switchより若干早く呼べます。

思い浮かぶ利点はこれくらいでしょうか…

 

ポインタを使う言語をあまり知らないなんとも言えませんが、どこかで応用できたらいいなと思います。

CakePHP3のクエリービルダーについて

PHPフレームワークとして有名なCakephpの3で使える処理速度改善方法の中で最近知ったクエリービルダーについて一つお話しします

クエリーオブジェクトのforeach処理

CakePHP3でクエリービルダーを使用してクエリーオブジェクトの形でDBからデータを取得します。

その際の取得形式で処理時間に差を作ることができます。

例としてArtivlesテーブルからレコードを全件取得するソースを2パターン書いてみる。

パターン1

$articles = $this->Articles->find();
foreach ($articles as $article) {} // 処理実行1

パターン2

$articles = $this->Articles->find()->toArray();
foreach ($articles as $article) {} // 処理実行1

今回は実行時間の差を見やすくするためレコードを1000件用意してみた。

レコード取得の実行時間は以下の通り

パターン1 0.0885570049秒

パターン2 0.0890669823秒

対して違いは見れない。

 

続いて下記のソースをforeachの数を1から10にして実行してみる

foreach ($articles as $article) {} // 処理実行1

foreach ($articles as $article) {} // 処理実行2

foreach ($articles as $article) {} // 処理実行3

foreach ($articles as $article) {} // 処理実行4

foreach ($articles as $article) {} // 処理実行5

foreach ($articles as $article) {} // 処理実行6

foreach ($articles as $article) {} // 処理実行7

foreach ($articles as $article) {} // 処理実行8

foreach ($articles as $article) {} // 処理実行9

foreach ($articles as $article) {} // 処理実行10

レコード取得の実行時間は以下の通り

パターン1 0.1360001564秒

パターン2 0.0915861130秒

0.04秒の違いが見れた。

 

なぜ違いが出たかというとパターン1

$this->Articles->find();

上記時点ではまだSQL文が流れておらずクエリー形式で取得しforeachに入るとイテレートされ実行される

パターン2では

$articles = $this->Articles->find()->toArray();

上記時点でSQL文が流れてクエリーオブジェクトの形式で取得しforeachにはその取得したクエリーオブジェクトで実行している。

パターン2は最初に取得したものをforeachを実行しているのに対してパターン1はforeachのたびにイテレートしているため時間がかかるみたい。

今回は1000件で行いましたが件数が増えれば増えるほどイテレートに時間がかかりますので目に見えて遅くなります。

たとえforeachが2つしかなくとも数万~数十万件のデータを扱った場合はtoArrayを使った方が断然早いです(私はDBデータから複数CSV出力する際に確認しました)

必ずしもクエリーオブジェクトからとるべきとは言えないが、一度取得したデータをforeachで何回かに分け処理を行う場合は使ってみてはどうでしょう。