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変数 の使い方はあまり良く知らなかったので、調べてみてよかったです。