gorogoroyasu

福岡の開発会社で働いている。

IteratorをGeneratorと関数内static変数で実装する

Excel のカラムは、 A から始まって Z までいくと次は AA となり、 AZ、BA、BZ・・・と続いていきます。

以前行った案件でExcel出力を行う際に github.com

を使わせていただいていましたが、

        'col' => 'B',

を指定するのがかなり大変でした。

というのも、カラムが AX ぐらいまであり、今回の改修で、そのうちのいくつかの項目を削ることになったのです。

つまり、

        'col' => 'D',

という値を消すと、以降の

        'col' => 'E',
        'col' => 'F',
        'col' => 'G',

をすべて

        'col' => 'D',
        'col' => 'E',
        'col' => 'F',

に書き換えなくてはなりません。
そして、今回、機能追加を行うことになりました。
そんなことやってられるかー (怒)

ということで、2つ対策を考えてみました。

俗に言う、Enum ってやつなのかな?
イメージとしては、Golang のiota 的なやつ。

package main

import (
    "fmt"
)

const (
    a = iota
    b
    c
    d
)
func main() {
    fmt.Println(a, b, c, d)
        # => 0, 1, 2, 3
}

今回は、 Excel のカラムに沿った値を返すところが違います。

Generator を使う解決策

class GenColumn
{
    public function __construct()
    {
        $this->gen = $this->excelColumnGenerator();
    }

    public function getCurrent()
    {
        return $this->gen->current();
    }

    public function getNext()
    {
        $this->gen->next();
        return $this->gen->current();
    }

    private function excelColumnGenerator()
    {
        $ary = [];
        for ($a="A"; $a != "AZ"; $a++) {
            $ary[] = $a;
        }
        foreach($ary as $column) {
            yield $column;
        }
    }

}

$column = new GenColumn();
print($column->getCurrent());
print($column->getNext());
print($column->getCurrent());
print($column->getNext());
print($column->getCurrent());

getCurrent() というメソッドを追加して、現在の値を取得することができるようにしています。

これを 関数内static変数を使うバージョンで書き直すと、

関数内static変数を使う解決策

class GenColumn
{
    private static $ary = [];
    
    private static function genAry()
    {
        $ary = [];
        for ($a="A"; $a != "AZ"; $a++) {
            $ary[] = $a;
        }
        self::$ary = $ary;
    }

    public function getColumnName($current=false)
    {
        static $a=1;
        if (empty(self::$ary)) {
            self::genAry();
        }
        if ($current) {
            return self::$ary[$a - 1];
        }
        
        $a++;
        return self::$ary[$a - 1];
    }
}
$column = new GenColumn();
print($column->getColumnName(true));
print($column->getColumnName(false));
print($column->getColumnName(true));
print($column->getColumnName(false));
print($column->getColumnName(true));

という風に書けます。

両者に一長一短があります。

ちなみに、10000回ループした際の実行速度(5回の平均)は

Generator: 0.089s 関数内static変数: 0.023s

と、関数内static変数の方で実装したほうが4倍ほど早かったです。

メリットとデメリット

Generator を使う解決策は、コードが長くなりますが、 - $column->getCurrent() - $column->getNext() と、行う操作にわかりやすい名前をつけることができます。

一方、コードが長くなり、実行に時間がかかります。

関数内static変数 を使う解決策は、コードは短く実行時間も早いです。 ただし、現在の値とインクリメントした値を取得する際に、 $column->getColumnName($current) という風に、$current の true/false で取れる値が変わります。 可読性は決して高くないです。

まとめ

結局、2つの実装方法を検討しましたが、より安全性が高い Generator で実装を行いました。 ただ、 関数内static変数 の使い方はあまり良く知らなかったので、調べてみてよかったです。

LINE公式アカウント の料金を調査するWebサイト作った。

昨日、LINE 社より 【重要】LINE@サービス統合のお知らせ というお知らせが発表されました。

blog-at.line.me

そこには、 「LINE@」は「LINE公式アカウント」「LINE ビジネスコネクト」「LINE カスタマーコネクト」とサービス統合し、名称を「LINE公式アカウント」として生まれ変わります。 と書かれています。

つまり、今までと料金体系が変わるということです。

フリープランでも、今まで上位プラン・もしくはビジネスコネクトでしか使えなかった機能が使えるようになる半面、
メッセージの送信が従量課金制になります。

料金プランも同時に発表されましたが、そこそこ分かりづらい。
無料枠の取扱とかが難しいです。

ということで、プランとメッセージ数を入力したら月いくらかかるか試算してくれるアプリ(と呼べるほどのものでもないが。。。)を作りました。

https://gorogoroyasu.github.io/lineat_price/

必要最低限の機能しかないし、バリデーションもしていない。 メッセージも不親切だしデザインもなにもない。と、不親切極まりない感じですが。。。

ちょこちょこ修正して、いい感じにしていこうと思っています。

一応、Vue.js を使っています。
使ったことないので、間違った使い方してるかも。
(そもそもJSをHTML 内に書いてる時点でというのはおいといてw)

テスト

上記のLINE社の発表の中で、いくつか例示されている金額があります。
手動ではありますが、その金額がただしいことは確認しています。 逆に言うと、他の金額のことは考えていないので、違うかもしれません。

算出根拠とかも出るようにしたほうがいいですね。 改修します。

終わりに

プルリク送ってください。

tensorflowのrandom crop で、2枚の画像の同じ箇所を切り取る

画像が2枚あります。
画像A が入力データ 画像B が正解データ。

f:id:adiboy:20181026102449p:plain 画像引用: https://helpx.adobe.com/jp/photoshop-elements/kb/222341.html

この画像をrandom crop するときに、どのようにするとうまくcrop できるのか試しました。
そして、たどり着いた答えが以下のコード。
もっといい方法があるのかもですが、とりあえずこれで動きます。

import tensorflow as tf
import numpy as np
import random

# この二つの配列を、画像と見立てる
img1 = np.arange(300).reshape((10, 10, 3))
img2 = np.arange(300).reshape((10, 10, 3)) + 300

i1 = tf.constant(img1)
i2 = tf.constant(img2)

with tf.Session() as s:
    s.run(tf.global_variables_initializer())
    seed = random.randint(0, 1000)
    croped_img1 = tf.random_crop(i1, [5, 5, 3], seed=seed)
    croped_img2 = tf.random_crop(i2, [5, 5, 3], seed=seed)
    c1 = s.run(croped_img1)
    c2 = s.run(croped_img2)    
"""
c2 - c1 => array([[[300, 300, 300],
        [300, 300, 300],
        [300, 300, 300],
        [300, 300, 300],
        [300, 300, 300]],

       [[300, 300, 300],
        [300, 300, 300],
        [300, 300, 300],
        [300, 300, 300],
        [300, 300, 300]],

       [[300, 300, 300],
        [300, 300, 300],
        [300, 300, 300],
        [300, 300, 300],
        [300, 300, 300]],

       [[300, 300, 300],
        [300, 300, 300],
        [300, 300, 300],
        [300, 300, 300],
        [300, 300, 300]],

       [[300, 300, 300],
        [300, 300, 300],
        [300, 300, 300],
        [300, 300, 300],
        [300, 300, 300]]])
"""

おそらくこれで切れているんだと思います。 ということで、random_crop に seed を入れてあげれば解決。 以上。

追記。

俺のやりたかったことは、そんなんじゃない!!!! こんな記事、嘘っぱちだ。

dataset API の中で、どうやってそれ使えばいいんだ(怒)

ということで、調べた。

import tensorflow as tf
import numpy as np
import random

# 画像 (10枚)
img1 = np.arange(3000).reshape((-1, 10, 10, 3))
img2 = np.arange(3000).reshape((-1, 10, 10, 3)) + 300

def _parse_function(img1, img2):
    r = tf.get_seed(1)
    img1 = tf.random_crop(img1, [5, 5, 3], seed=r[0])
    img2 = tf.random_crop(img2, [5, 5, 3], seed=r[0])
    return img1, img2

dataset = tf.data.Dataset.from_tensor_slices((i1, i2))
dataset = dataset.map(_parse_function, num_parallel_calls=2)
dataset = dataset.shuffle(buffer_size=(len(img1))).batch(1).prefetch(1).repeat()
iterator = dataset.make_one_shot_iterator().get_next()

with tf.Session() as sess:
    for i in range(5):
        x, y = sess.run(iterator)
        print(x[0, 0, 0])
print(y - x)

結果

[2823 2824 2825]
[675 676 677]
[1932 1933 1934]
[2232 2233 2234]
[2436 2437 2438]
[[[[300 300 300]
   [300 300 300]
   [300 300 300]
   [300 300 300]
   [300 300 300]]

  [[300 300 300]
   [300 300 300]
   [300 300 300]
   [300 300 300]
   [300 300 300]]

  [[300 300 300]
   [300 300 300]
   [300 300 300]
   [300 300 300]
   [300 300 300]]

  [[300 300 300]
   [300 300 300]
   [300 300 300]
   [300 300 300]
   [300 300 300]]

  [[300 300 300]
   [300 300 300]
   [300 300 300]
   [300 300 300]
   [300 300 300]]]]

ということで、ランダムに画像を crop しつつ、対象の2枚の画像については同じ箇所で切り出すことができた。

メモ golang の defer 2

package main

import (
    "fmt"
)

type Hoge struct {
    a string
}


func main() {
    h := &Hoge{"a"}
    defer fmt.Println("no func", h.a)
    defer func () {fmt.Println("with func", h.a)}()
    h.a = "b"
}

出力は、

with func b
no func a

以前の記事に書いたが、defer は、後入れ先出しで処理される。
よって、 with func が先に実行され、
no func が後で実行される。

1つ目の defer、with func については、h.a = "b" の結果が表示され、 2つ目のdefer、 no func については、 h.a = "a" の結果が表示されている。

これは、 defer 文が、式の実行を遅らせるだけで、式の評価自体は defer 文が呼ばれた段階で行われていることを示唆している。
たぶん。

メモ golang のdefer

defer は便利でよく使うので、特性をちゃんと把握しておく必要がある。

  1. defer の実行順
package main

import (
    "fmt"
)

func main() {
    for i:=0; i<3; i++ {
    fmt.Println("1")
    defer fmt.Println(fmt.Sprintf("2-%d", i))
    fmt.Println("3")
    }
}

結果

1
3
1
3
1
3
2-2
2-1
2-0

ということで、後入先出になってることがわかる。

  1. 他の関数内のdefer はいつ呼ばれるのか?
package main

import (
    "fmt"
)

func de(s string) {
    defer fmt.Println(s)
}

func main() {
    for i:=0; i<3; i++ {
    fmt.Println("1")
    de("2")
    }
}

結果

1
2
1
2
1
2

となった。 関数のスコープを抜ける時に呼ばれるらしい。 なるほど。

この辺は、ドキュメントにも書いてあると思うが、 簡単な実験で挙動を確認できるので、サンプルを書いてみると良いと思う。
続けていこう。

"Key (id)=(1) already exists" error occurred in CakePHP3 model test.

久々にCakePHP でテストを書いている。
Fixture を整えて、TestCase を書いた。
そして、値を save するメソッドをテストした。

すると、

duplicate key value violates unique constraint "hoges_pkey"
DETAIL:  Key (id)=(1) already exists.

というエラーが出た。
悲しい。

書いてたFixture は、こんな感じ。

<?php
namespace App\Test\Fixture;

use Cake\TestSuite\Fixture\TestFixture;


class HogesFixture extends TestFixture
{

    /**
     * Fields
     *
     * @var array
     */
    // @codingStandardsIgnoreStart
    public $fields = [
        'id' => ['type' => 'integer', 'length' => 10, 'autoIncrement' => true, 'default' => null, 'null' => false, 'comment' => null, 'precision' => null, 'unsigned' => null],
        'user_id' => ['type' => 'integer', 'length' => 10, 'default' => null, 'null' => false, 'comment' => null, 'precision' => null, 'unsigned' => null, 'autoIncrement' => null],
        'fuga' => ['type' => 'text', 'length' => null, 'default' => null, 'null' => false, 'collate' => null, 'comment' => null, 'precision' => null],
        'modified' => ['type' => 'timestamp', 'length' => null, 'default' => null, 'null' => true, 'comment' => null, 'precision' => null],
        'deleted' => ['type' => 'timestamp', 'length' => null, 'default' => null, 'null' => true, 'comment' => null, 'precision' => null],
        '_constraints' => [
            'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []],
        ],
    ];
    // @codingStandardsIgnoreEnd

    /**
     * Init method
     *
     * @return void
     */
    public function init()
    {
        $this->records = [
            [
                'id' => 1,
                'user_id' => 1,
                'fuga' => 'a',
                'modified' => 1539138087,
                'deleted' => 1539138087
            ],
            [
                'id' => 2,
                'user_id' => 1,
                'fuga' => 'b',
                'modified' => 1539138087,
                'deleted' => 1539138087
            ],
            [
                'id' => 3,
                'user_id' => 1,
                'fuga' => 'c',
                'modified' => 1539138087,
                'deleted' => 1539138087
            ],
        ];
        parent::init();
    }
}

そして、原因は Fixture にあった。

解決策は、

    public function init()
    {
        $this->records = [
            [
//                'id' => 1,
                'user_id' => 1,
                'fuga' => 'a',
                'modified' => 1539138087,
                'deleted' => 1539138087
            ],
            [
//                'id' => 2,
                'user_id' => 1,
                'fuga' => 'b',
                'modified' => 1539138087,
                'deleted' => 1539138087
            ],
            [
//                'id' => 3,
                'user_id' => 1,
                'fuga' => 'c',
                'modified' => 1539138087,
                'deleted' => 1539138087
            ],
        ];
        parent::init();
    }

$this->records に入れた連想配列内の、 id を消すこと。

普通に考えたらそうだ。
ID は、 データベースのsequenceの機能を使うので、idの発行をCakePHPに任せたら、 1からID が付与され、自動的にインクリメントされる。

一方、ID に値を入れてしまうと、Database の Sequence の機能を使わずに値が保存される。
よって、Database は、次の値は1だと思っている。しかし、1はすでに存在している。
だから、エラーになる。

細かいところだが、ハマりどころだ。

やっかいなことに、 Fixture を bake すると、デフォルトで値が挿入されるので、
Bake のテンプレートを書き換えるか (やったことない) いちいちコメントアウト (もしくは削除) する必要がある。

参考にしたのはこちら stackoverflow.com

iPhone の充電ができないときに調べるべきただ一つのこと

ここ2ヶ月ぐらいずっと、iPhone の調子がおかしかった。
これは、ライトニングケーブルが断線してるな。
勘のいいボクはすぐに気づいた。

最近買った、Magic Trackpad 2 付属のライトニングケーブルを使ってみた。
そのときはうまく充電できた。
しかし、その能力も長くは続かなかった。

それからは、iPhone を省電力モードにして、だましだまし使ってきた。
電池の色が黄色いのがデフォルトだった。
いつも、何度かケーブルを抜き差しして、ちょうどよい角度を探していた。

そして、先週の週末、夜、充電できなくなった。

充電は残り3%。
絶体絶命だ。
このまま電池が切れ、二度と充電できない状況になったら、
二段階認証 が一切できなくなる。
念の為、Google の 二段階認証 は解除した。
Authy に移行していなかった自分に腹が立つ。
また、最近 Google Photos にアップロードしていない写真たちも消滅する。
LINE も移行しなければならない。
ボクは iPhone に頼りすぎていたことに気づいた。
そして、バックアップの重要性を思い出した。

時刻は21:05。
Apple Store はすでにしまっている。
ボクは絶望に暮れた。
念の為、ビッグカメラの営業時間を調べた。
こちらも21:00 まで。
終わった。 そう思った。
しかし、次の瞬間、ボクの脳裏に、ひらめきが走った。

ヨドバシカメラApple 製品おいてるゾーンあるよな。
時間を確認する。すると、22:00 まで開いている。
自宅からヨドバシカメラまで、チャリで15分。
余裕で間に合う。

勢いで直行した。修理できない可能性はあるが仕方ない。
最悪iPhone8 を買って帰ろう。 と心の中で決めていた。

ヨドバシカメラでの体験は、最悪だった。
修理ができないのは仕方ない。
しかし、あんなに広いスペースを使って製品を展示いるにもかかわらず、販売すらしていないのだ。

値段を聞いたら、「展示してあるiPhoneApple.co.jp にアクセスすれば見れますよ」 と言われた。
そんな事も知らないんですか? 的なテンションで。

知っとるがな!w

泣く泣くヨドバシカメラを立ち去り、家路についた。
クロアチアが勝つかフランスが勝つか よりもiPhone の充電のほうが気になった。

なんとか充電してください!!!

藁にもすがる思いで何度もコネクタを抜き差しした。
しかし、何もおこらない。

ポケモンを捕まえるときに、 A B ボタンを連打したら捕まえやすくなる みたいな噂があったが、あれと同じぐらい無駄なことをしていると感じた。
ちなみに、ポケモンに関して言うと、ホウオウをモンスターボールでゲットしたことがあることを思い出した。

話を戻す。

数十分の格闘の末、iPhone が震えた。
その角度をキープしつつ、フル充電した。

次の日、起きてすぐ Apple Store に向かった。
予約せずに行ったので、予約してないやつらが並ばされる長い列の最後尾に追加された。
暇そうにしている店員に腹が立つ。

しかし、もうすぐiPhone を修理できる。
そう信じて並んだ。
並び続けた。

30分ぐらいして自分の番がきた。

店員に言った。 「充電ができなくなりました。交換だと思います。」
3秒ぐらいiPhone を見回した店員は、言った。 「あー、Dock に誇り溜まってますねー」

そして、ホコリを取り除いてくれた。 結局、原因はDock のホコリだった。

ボクのiPhone は、生き返ったかのように充電できるようになった。