LoginSignup
2
2

More than 5 years have passed since last update.

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

Posted at

ArrayAccessインターフェイス

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

まずArrayAccessのメソッド。

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番目のフィボナッチ数列を取得できる配列を作ってみました。

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
*/

動かしてみると後半がクソ遅いですが、 メモ化 という処理をすると早くなります。
あとforループはこのままでいいけど、foreachで使うにはIteratorAggregateインターフェイスも実装が必要です。
この2つを追加したコードは最後に載せときます。

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

あとこれ、無限の長さの配列になるからcount()に渡すとどうなるんだろうね(ニッコリ

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

まとめ

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

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

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
*/
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2