フィボナッチ数列をArrayAccessを使って書いてみる



# ArrayAccessインターフェイス

PHPのクラスでは[ArrayAccessインターフェイス](http://php.net/manual/ja/class.arrayaccess.php)を実装するとそのクラスインスタンスを配列として扱えるようになります（[]で値を得ることが出来る）。
ふつうはインスタンスの内部変数を一括でアクセスできるようにしたり、列挙するモノを配列として扱うときに使うものですが今回はちょっと違う用途に使ってみます。
仕事のコードではこんなふうに書いたらレビューで怒られると思うので、真似しないほうが良いかもね。

まず`ArrayAccess`のメソッド。

```php:ArrayAccess
ArrayAccess {
    /* メソッド */
    abstract public boolean offsetExists ( mixed $offset )
    abstract public mixed offsetGet ( mixed $offset )
    abstract public void offsetSet ( mixed $offset , mixed $value )
    abstract public void offsetUnset ( mixed $offset )
}
```

`offsetGet()`に注目！　これは添字でアクセスするときに呼ばれるもので、`$a[1];`と`$a->offsetGet(1);`は同じになります。

添字アクセスが関数呼び出しと同じになる…。 **なんか別のことに使えそう！**


# フィボナッチ配列を作る
ということで添字$nでアクセスすると$n番目の[フィボナッチ数列](http://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A3%E3%83%9C%E3%83%8A%E3%83%83%E3%83%81%E6%95%B0)を取得できる配列を作ってみました。

```php:FibArray.php
class FibArray implements ArrayAccess
{
    public function __construct()
    {
    }

    public function offsetSet($offset, $value)
    {
    }

    public function offsetUnset($offset)
    {
    }

    public function offsetExists($offset)
    {
        return is_numeric($offset) && 0<=$offset;
    }

    public function offsetGet($offset)
    {
        if (!$this->offsetExists($offset))
            return null;

        if ($offset == 0)
            return 0;
        else if ($offset == 1)
            return 1;
        else
            return $this[$offset-1] + $this[$offset-2];
    }
}

$start = microtime(true);

// 30まで列挙する
$obj = new FibArray;
for($i=0; $i<30; $i++)
    echo "$i:$obj[$i], ";

echo 'time: '.(microtime(true)-$start)."sec\n";

/*
0:0, 1:1, 2:1, 3:2, 4:3, 5:5, 6:8, 7:13, 8:21, 9:34, 10:55, 11:89, 12:144, 13:233, 14:377, 15:610, 16:987, 17:1597, 18:2584, 19:4181, 20:6765, 21:10946, 22:17711, 23:28657, 24:46368, 25:75025, 26:121393, 27:196418, 28:317811, 29:514229,
time: 2.4634881019592sec
*/
```

動かしてみると後半がクソ遅いですが、 __[メモ化](http://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%A2%E5%8C%96)__ という処理をすると早くなります。
あとforループはこのままでいいけど、foreachで使うには`IteratorAggregate`インターフェイスも実装が必要です。
この2つを追加したコードは最後に載せときます。

N番目のフィボナッチ数をN番目の配列で取得できるんだから、ある意味シンプル。しかし裏で処理が動いていることが判断しづらいですね。フィボナッチ数をただの配列に入れたものと区別が付かない。
コード遊びとしては面白いけど、添字アクセスにあまり重い処理をかますべきではないかなーと思います。
（「いまどきサンプルにフィボナッチ数列か数学ガールを読めよ、一般項の計算が出てんじゃん」というツッコミはやめてくれ）

あとこれ、無限の長さの配列になるから`count()`に渡すとどうなるんだろうね（ﾆｯｺﾘ


ではなんでこの記事を書いたかというと、[SICPの遅延ストリーム](http://sicp.iijlab.net/fulltext/x350.html)をPHPで書くことができたので、さらにそれをArrayAccessでラップしたら使いやすいよね、でも同じ記事中で説明すると長くなるよね、ということで引用のため書いてみました。
つまり次回を乞うご期待（なのか？）


# まとめ

* ArrayAccessを使うとインスタンスを配列みたいに扱えます
* 添字アクセスがただの関数呼び出しになるから何かの処理をかませることが出来る
* 捻った使い方をすると楽しいけど、調子に乗って使いすぎると分かりにくいコードになるよ
* フィボナッチ数列の一般項は[数学ガール](http://www.hyuki.com/story/genfunc.html)を読め


# おまけ：メモ化、foreachで使えるバージョン

```php:FibMemoArray.php
class FibMemoArray implements ArrayAccess, IteratorAggregate
{
    private $memo; // 計算途中メモ用の変数

    public function __construct() {
        $this->memo = array(0, 1);
    }

    public function offsetSet($offset, $value) {}

    public function offsetUnset($offset) {}

    public function offsetExists($offset)
    {
        return is_numeric($offset) && 0<=$offset;
    }

    public function offsetGet($offset)
    {
        if (!$this->offsetExists($offset))
            return null;
        else if (array_key_exists($offset,$this->memo))
            return $this->memo[$offset];
        else {
            $this->memo[$offset] = $this[$offset-1] + $this[$offset-2];
            return $this->memo[$offset];
        }
    }

    // foreachで必要なやつ
    public function getIterator()
    {
        return new ArrayIterator($this->memo);
    }
}

$start = microtime(true);

$obj = new FibMemoArray;

// 30まで参照してメモに記憶しておく
for($i=0; $i<30; $i++)
    $obj[$i];

echo "foreach\n";
foreach($obj as $i=>$v)
    echo "$i:$v, ";
echo "\n";

echo 'time: '.(microtime(true)-$start)."sec\n";

/*
foreach
0:0, 1:1, 2:1, 3:2, 4:3, 5:5, 6:8, 7:13, 8:21, 9:34, 10:55, 11:89, 12:144, 13:233, 14:377, 15:610, 16:987, 17:1597, 18:2584, 19:4181, 20:6765, 21:10946, 22:17711, 23:28657, 24:46368, 25:75025, 26:121393, 27:196418, 28:317811, 29:514229,
time: 0.00016617774963379sec
*/
```