package Bio::Phylo::Util::Math;
use strict;
use base 'Exporter';

BEGIN {
    our ( @EXPORT_OK, %EXPORT_TAGS );
    @EXPORT_OK = qw(nchoose gcd gcd_divide);
    %EXPORT_TAGS = (
        'all' => [@EXPORT_OK],
    );
}

=head1 NAME

Bio::Phylo::Util::Math - Utility math functions

=head1 EXPORTED FUNCTIONS

=over

=item nchoose_static

Calculation of n choose j. This version saves partial results for use later.

 Type    : Exported function
 Title   : nchoose_static
 Usage   : $c = nchoose_static( $n, $j )
 Function: Calculation of n choose j.
 Returns : n-choose
 Args    : $n, $j

=cut

# Calculation of n choose j
# This version saves partial results for use later
my @nc_matrix; #stores the values of nchoose(n,j)
# -- note: order of indices is reversed
sub nchoose_static {
	my ( $n, $j, @nc_matrix ) = @_;
	return 0 if $j > $n;
	if ( @nc_matrix < $j + 1 ) {
		push @nc_matrix, [] for @nc_matrix .. $j;
	}
	if ( @{ $nc_matrix[$j] } < $n + 1 ) {	
		push @{ $nc_matrix[$j] }, 0 for @{ $nc_matrix[$j] } .. $j - 1;
	}
	push @{ $nc_matrix[$j] }, 1 if @{ $nc_matrix[$j] } == $j;
	for my $i ( @{ $nc_matrix[$j] } .. $n ) {
		push @{ $nc_matrix[$j] }, $nc_matrix[$j]->[$i-1] * $i / ( $i - $j );
	}
	return $nc_matrix[$j]->[$n];
}

=item nchoose

Calculation of n choose j. Dynamic version.

 Type    : Exported function
 Title   : nchoose
 Usage   : $c = nchoose( $n, $j )
 Function: Calculation of n choose j.
 Returns : n-choose
 Args    : $n, $j

=cut

# dynamic programming version
sub nchoose {
	my ( $n, $j ) = @_;
	return nchoose_static($n,$j,@nc_matrix);
}

=item gcd

Greatest common denominator - assumes positive integers as input

 Type    : Exported function
 Title   : gcd
 Usage   : $gcd = gcd( $n, $m )
 Function: Greatest common denominator
 Returns : $gcd
 Args    : $n, $m

=cut

# GCD - assumes positive integers as input
# (subroutine for compare(t,u,v))
sub gcd {
	my ( $n, $m ) = @_;
	return $n if $n == $m;
	( $n, $m ) =  ( $m, $n ) if $m > $n;
	my $i = int($n / $m);
	$n = $n - $m * $i;		
	return $m if $n == 0;
	
	# recurse
	return gcd($m,$n);
}

=item gcd_divide

Takes two large integers and attempts to divide them and give
the float answer without overflowing

 Type    : Exported function
 Title   : gcd_divide
 Usage   : $gcd = gcd_divide( $n, $m )
 Function: Greatest common denominator
 Returns : $gcd
 Args    : $n, $m

=cut

# Takes two large integers and attempts to divide them and give
# the float answer without overflowing
# (subroutine for compare(t,u,v))
# does this by first taking out the greatest common denominator
sub gcd_divide {
	my ( $n, $m ) = @_;
	my $x = gcd($n,$m);
	$n /= $x;
	$m /= $x;
	return $n/$m;
}

=back

=head1 SEE ALSO

There is a mailing list at L<https://groups.google.com/forum/#!forum/bio-phylo> 
for any user or developer questions and discussions.

=over

=item L<Bio::Phylo::Manual>

Also see the manual: L<Bio::Phylo::Manual> and L<http://rutgervos.blogspot.com>.

=back

=head1 CITATION

If you use Bio::Phylo in published research, please cite it:

B<Rutger A Vos>, B<Jason Caravas>, B<Klaas Hartmann>, B<Mark A Jensen>
and B<Chase Miller>, 2011. Bio::Phylo - phyloinformatic analysis using Perl.
I<BMC Bioinformatics> B<12>:63.
L<http://dx.doi.org/10.1186/1471-2105-12-63>

=cut

1;