package Math::GrahamFunction::SqFacts;
use strict;
use warnings;
=head1 NAME
Math::GrahamFunction::SqFacts - a squaring factors vector.
=head1 WARNING!
This is a module for Math::GrahamFunction's internal use only.
=cut
use parent qw(Math::GrahamFunction::Object);
use List::Util ();
__PACKAGE__->mk_accessors(qw(n factors));
sub _initialize
{
my $self = shift;
my $args = shift;
if ( $args->{n} )
{
$self->n( $args->{n} );
$self->_calc_sq_factors();
}
elsif ( $args->{factors} )
{
$self->factors( $args->{factors} );
}
else
{
die "factors or n must be supplied.";
}
return 0;
}
=head1 CONSTRUCTION
=head2 Math::GrahamFunction::SqFacts->new({n => $n})
Initializes a squaring factors object from a number.
=head2 Math::GrahamFunction::SqFacts->new({factors => \@factors})
Initializes a squaring factors object from a list of factors.
=head1 METHODS
=head2 $facts->clone()
Creates a clone of the object and returns it.
=cut
sub clone
{
my $self = shift;
return __PACKAGE__->new( { 'factors' => [ @{ $self->factors() } ] } );
}
sub _calc_sq_factors
{
my $self = shift;
$self->factors( $self->_get_sq_facts( $self->n() ) );
return 0;
}
my %gsf_cache = ( 1 => [] );
sub _get_sq_facts
{
my $self = shift;
my $n = shift;
if ( exists( $gsf_cache{$n} ) )
{
return $gsf_cache{$n};
}
my $start_from = shift || 2;
for ( my $p = $start_from ; ; ++$p )
{
if ( $n % $p == 0 )
{
# This function is recursive to make better use of the Memoization
# feature.
my $division_factors = $self->_get_sq_facts( ( $n / $p ), $p );
if ( @$division_factors && ( $division_factors->[0] == $p ) )
{
return ( $gsf_cache{$n} =
[ @{$division_factors}[ 1 .. $#$division_factors ] ] );
}
else
{
return ( $gsf_cache{$n} = [ $p, @$division_factors ] );
}
}
}
}
# Removed because it is too slow - we now use our own custom memoization (
# or perhaps it is just called caching)
# memoize('get_squaring_factors', 'NORMALIZER' => sub { return $_[0]; });
# This function multiplies the squaring factors of $n and $m to receive
# the squaring factors of ($n*$m)
# OOP-Wise, it should be a multi-method, but since we don't inherit this
# object it's all-right.
=head2 $n_facts->mult_by($m_facts)
Calculates the results of the multiplication of the number represented by
C<$n_facts> and C<$m_facts> and stores it in $n_facts (destructively).
This is actually addition in vector space.
=cut
sub mult_by
{
my $n_ref = shift;
my $m_ref = shift;
my @n = @{ $n_ref->factors() };
my @m = eval { @{ $m_ref->factors() }; };
if ($@)
{
print "Hello\n";
}
my @ret = ();
while ( scalar(@n) && scalar(@m) )
{
if ( $n[0] == $m[0] )
{
shift(@n);
shift(@m);
}
elsif ( $n[0] < $m[0] )
{
push @ret, shift(@n);
}
else
{
push @ret, shift(@m);
}
}
push @ret, @n, @m;
$n_ref->factors( \@ret );
# 0 for success
return 0;
}
=head2 my $result = $n->mult($m);
Non destructively calculates the multiplication and returns it.
=cut
sub mult
{
my $n = shift;
my $m = shift;
my $result = $n->clone();
$result->mult_by($m);
return $result;
}
=head2 $facts->is_square()
A predicate that returns whether the factors represent a square number.
=cut
sub is_square
{
my $self = shift;
return ( scalar( @{ $self->factors() } ) == 0 );
}
=head2 $facts->exists($myfactor)
Checks whether C<$myfactor> exists in C<$facts>.
=cut
sub exists
{
my ( $self, $factor ) = @_;
return defined( List::Util::first { $_ == $factor } @{ $self->factors() } );
}
=head2 my $last_factor = $factors->last()
Returns the last (and greatest factor).
=cut
sub last
{
my $self = shift;
return $self->factors()->[-1];
}
use vars qw($a $b);
=head2 $facts->product()
Returns the product of the factors.
=cut
sub product
{
my $self = shift;
return ( List::Util::reduce { $a * $b } @{ $self->factors() } );
}
=head2 $facts->first()
Returns the first (and smallest) factor.
=cut
sub first
{
my $self = shift;
return $self->factors()->[0];
}
=head1 AUTHOR
Shlomi Fish, C<< <shlomif at cpan.org> >>
=cut
1;