読者です 読者をやめる 読者になる 読者になる

PHPでFollowというイテレータを書いた

こんなん。

<?php

class Follow implements Iterator
{
    protected $first, $fn, $key = 0, $current_value, $is_valid = true;
    function __construct($first, $fn)
    {
        list($this->first, $this->fn) = func_get_args();
        $this->current_value = $first;
    }
    
    function valid()
    {
        return $this->is_valid;
    }
    
    function next()
    {
        $result = call_user_func($this->fn, $this->current_value, ++$this->key);
        if ($result === false) {
            $this->is_valid = false;
        }
        else {
            $this->current_value = $result;
        }
    }
    
    function current() {
        return $this->current_value;
    }
    
    function key() {
        return $this->key;
    }
    
    function rewind()
    {
        $this->key = 0;
        $this->current_value = $this->first;
        $this->is_valid = true;
    }
}

function follow($first, $fn)
{
    return new Follow($first, $fn);
}

これの元ネタは、Factorという言語のfollowなんだけど、便利っぽいのでPHPでも書いてみた。

使い方

こんな感じ。
初項と漸化式をイテレータに変換してくれる。

<?php
function hoge($elt) 
{
    return $elt + 2;
}

foreach (follow(-10, 'hoge') as $i => $elt) if ($elt > 10) break; else {
    echo "{$i}:{$elt}\n";
}

実行結果。

0:-10
1:-8
2:-6
3:-4
4:-2
5:0
6:2
7:4
8:6
9:8
10:10

公差2の等差数列が-10から10まで表示される。
以下のように書いても結果は同じ。
followに渡したコールバックがfalseを返すとイテレータは止まる。

<?php
function fuga($elt) 
{
    return $elt < 10 ? $elt + 2 : false;
}

foreach (follow(-10, 'fuga') as $i => $elt) {
    echo "{$i}:{$elt}\n";
}

と、ここまで記事を書いていて、「for使った方が早くね?」という心の声が聞こえたけど、イテレータにしておくとイテレータを取るイテレータと連携できたり、勝手に添え字を生成してくれたりするのでこれはこれでまあいいかな、と思った。終わり。

おまけ:Python

Pythonにはyieldがあるので楽チンですね

def follow(first, fn):
    yield first
    val = first
    while True:
            val = fn(val)
            if val == False: break;
            yield val

for elt in follow(0, lambda a:a+1):
    if elt > 10: break
    print(elt)