フィボナッチ数をPerlで実装できますか?

フィボナッチ数をPerlで実装できますか?

そもそもフィボナッチ数が何なのかまったくわからなかった。くやしいです。
くやしいので勉強してみた。

コード置き場

ここにコードを置いてあります。
https://github.com/okamuuu/Fibonacci

実装前に準備すること

ケルトン作成

% module-setup Fibonacci                                   
...
% cd Fibonacci

テスト作成。

#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;

BEGIN {
    use_ok('Fibonacci');
};

my $expected = [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987 ];

subtest 'get fibonacci number' => sub {

    my $got = Fibonacci->get_number_at(4);
    is($got, $expected->[4]);
};

done_testing;

実行する。もちろん失敗。

:!prove -vl t/01_fibonacci.t                                               
t/01_fibonacci.t .. 
ok 1 - use Fibonacci;
Can't locate object method "get_number_at" via package "Fibonacci" at t/01_fibonacci.t line 14.
    # Child (get fibonacci number) exited without calling finalize()
not ok 2 - get fibonacci number
...

実装開始

フィボナッチ数の定義はどの項もその前の2つの項の和となっている。という事なのでベタに次のように書いてみる

package Fibonacci;
use strict;
use warnings;
our $VERSION = '0.01';
use Carp ();

sub get_number_at {
    my ($class, $length) = @_;

    if ( $length == 0 ) {
        return 0;
    }

    ### 1つ目、2つ目の数はその2つ前の値が存在しない
    if ( $length == 1 or $length == 2 )  {
        return 1;
    }   

    my @numbers;

    for my $len ( 1 .. $length ) { 
        if ( $len == 1 ) { 
            $numbers[$len] = 1;
        }   
        elsif ( $len == 2 ) { 
            $numbers[$len] = 1;
        }   
        else {
            $numbers[$len] = $numbers[$len-1] + $numbers[$len-2]; 
        }   
    }   

    return $numbers[$length-1] + $numbers[$length-2];
}

1;

テスト実行。成功。

:!prove -vl t/01_fibonacci.t
...
All tests successful.
};

done_testing;

テストケースを増やしてみて再度prove実行。成功。

#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;

BEGIN {
    use_ok('Fibonacci');
};

my $expected = [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987 ];

subtest 'get fibonacci number' => sub {

    my $count = scalar @{ $expected } - 1;
    for my $length ( 0 .. $count ) { 
        my $got = Fibonacci->get_number_at($length);
        is($got, $expected->[$length]);
    }  
};

done_testing;
:!prove -vl t/01_fibonacci.t
...
All tests successful.
Files=1, Tests=2,  0 wallclock secs ( 0.05 usr  0.01 sys +  0.04 cusr  0.00 csys =  0.10 CPU)
Result: PASS

面接官が試したかった事。

Fibonacci.pmのget_number_atメソッドの最後にあるこの記述。再帰の匂いがぷんぷんします。

return $numbers[$length-1] + $numbers[$length-2];

ああなるほど面接官が知りたかったのは「再帰処理を書けるのかい?んん?」とそういうわけですね。承知した。

再帰処理に書き換える

lib/Fibonacci.pmを次のように書き換えます。
うおー。シンプル!!

package Fibonacci;
use strict;
use warnings;
our $VERSION = '0.01';

sub get_number_at {
    my ($class, $length) = @_; 

    if ( $length == 0 ) { 
        return 0; 
    }   

    if ( $length == 1 or $length == 2 ) { 
        return 1;
    }   

    return $class->get_number_at($length-1) + $class->get_number_at($length-2);
}

1;

テスト実行。当然成功。

:!prove -vl t/01_fibonacci.t                                               
t/01_fibonacci.t .. 
..
All tests successful.

感想

再帰処理は見た目何が何やら分からないかも知れませんが、どっかにwarnでも挿入して実行してみると何やってるか良く分かります。
小一時間程度でしたが割といい勉強になりました。おしまい。