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

Higher order perl 1_2

perl hop

Higer order perl 1_2は階乗に関するお話。
factorialで再帰の例題でよく使われる例ですね。


人によるとこんなに再帰で使うべきでないから逆に
教えるなといいますよね。でもまあ勉強なんで。


比較のコードを書いてみた。

#!/usr/bin/env perl
use strict;
use warnings;

use Benchmark qw(cmpthese);

my $do_test = shift @ARGV;

if (defined $do_test) {
    use Test::More;

    is(factorial(1), 1);
    is(factorial(2), 2);
    is(factorial(5), 120);
    is(factorial(7), 5040);

    is(factorial_tail_recursive(1), 1);
    is(factorial_tail_recursive(2), 2);
    is(factorial_tail_recursive(5), 120);
    is(factorial_tail_recursive(7), 5040);

    is(factorial_loop(1), 1);
    is(factorial_loop(2), 2);
    is(factorial_loop(5), 120);
    is(factorial_loop(7), 5040);

    done_testing();
    exit;
}

cmpthese(500000, {
    normal => sub {
        factorial( 10 );
    },

    tail_recursive => sub {
        factorial_tail_recursive( 10 );
    },

    loop => sub {
        factorial_loop( 10 );
    },
});

sub factorial {
    my $n = shift;

    if ($n == 1) {
        return 1;
    } else {
        return $n * factorial($n - 1);
    }
}

sub factorial_tail_recursive {
    my $n = shift;

    return _factorial_tail_recursive($n, 1);
}

sub _factorial_tail_recursive {
    my ($n, $acc) = @_;

    if ($n == 1) {
        return $acc;
    } else {
        return _factorial_tail_recursive($n - 1, $acc * $n);
    }
}

sub factorial_loop {
    my $n = shift;

    my $val = 1;
    foreach my $num (1..$n) {
        $val *= $num;
    }

    return $val;
}

結果は以下のような具合でした。

                   Rate tail_recursive         normal           loop
tail_recursive 152905/s             --            -8%           -67%
normal         165563/s             8%             --           -64%
loop           458716/s           200%           177%             --

引数を int(rand(10))としたらなぜかうまくいきませんでした。


loopがやっぱり一番早い。あと tail_recursiveの方が遅くなって
しまいましたね。これぐらいの規模の問題だとそんなもんなんかな〜?


この手の問題はループ使って書けってことですかね。