package Permute;
# Accept an array reference (arrayref), and return an arrayref of
# arrayrefs of all of the possible permutations of the original
# array. Recursive code.
#
# T. Alex Beamish / August 11, 2016
sub possibilities {
my ( $list ) = @_;
my $len = scalar @{$list};
# This is the trivial case: if there's just a single element
# in the array, then there's only a single permutation.
if ( $len == 1 ) { return [ [ $list->[0] ] ]; }
my @output;
# OK -- there are at least two elements in the array. Here's
# the approach: each of the elements is going to get a chance
# to be the first element. We're calling that element the
# pivot in the comments below.
for my $p ( 0 .. $len-1 ) {
my @work;
# We're building a work array consisting of everything after
# the pivot, followed by everything before the pivot.
if ( $p < $len-1 ) { push @work, @{$list}[$p+1..$len-1]; }
if ( $p > 0 ) { push @work, @{$list}[0..$p-1]; }
# We call ourselves recursively to get all permutations of
# the work array, then add each of those possibilities to
# the list, with our pivot as the first element.
my $poss = possibilities ( \@work );
foreach my $soln ( @{$poss} ) {
push ( @output, [ $list->[$p], @{ $soln } ] );
}
}
return \@output;
}
1;
####
#!perl
use strict;
use warnings;
use Test::More tests => 1;
use lib 'lib';
BEGIN { use_ok ( 'Permute' ); }
####
#!perl
use strict;
use warnings;
use Test::More;
use lib 'lib';
use Permute;
{
my @a1 = ( 4 );
my $possibilities = Permute::possibilities ( \@a1 );
ok ( defined $possibilities, "Got result back" );
is ( scalar @{$possibilities}, 1, "Have just one row" );
is ( $a1[0], $possibilities->[0]->[0], "Value matches" );
done_testing;
}
####
#!perl
use strict;
use warnings;
use Test::More;
use lib 'lib';
use Permute;
{
my @a2 = ( 3, 44 );
my $possibilities = Permute::possibilities ( \@a2 );
ok ( defined $possibilities, "Got result back" );
is ( scalar @{$possibilities}, 2, "Have just two rows" );
my %correct = ( '3:44' => 1, '44:3' => 1 );
my %results;
foreach my $soln ( @{$possibilities} ) {
$results{ join(':',@{$soln}) }++;
}
foreach my $try ( keys %results ) {
ok ( exists $correct{ $try }, "Result $try exists" );
is ( $correct{ $try }, $results{ $try },
"Result count matches" );
}
done_testing;
}
####
#!perl
use strict;
use warnings FATAL => 'uninitialized';
use lib 'lib';
# In response to mjd's comments on his blog post at
#
# http://blog.plover.com/math/17-puzzle.html
#
# I present my solution to the problem. Most entertaining.
#
# T. Alex Beamish / August 11, 2016
use Permute;
{
my @values = ( 6, 6, 5, 2 );
my @ops = qw/+ - \/ */;
my $possibilities = Permute::possibilities( \@values );
foreach my $v ( @{$possibilities} ) {
for my $o1 ( @ops ) {
for my $o2 ( @ops ) {
for my $o3 ( @ops ) {
my @list = ( $v->[0], $o1, $v->[1], $o2,
$v->[2], $o3, $v->[3] );
# Just try the expression as-is, without any brackets.
display ( \@list );
# Now we're going to insert a pair of brackets into the
# expression, making sure that a) they encompass at least
# a number, an operation, and another number, and b) we
# don't bother by bracketing the entire expression, as
# that's redundant.
for my $lb ( 0, 2, 4 ) {
for my $rb ( 4, 6, 8 ) {
next unless ( $lb + 2 < $rb ); # a)
next if ( $lb == 0 && $rb == 8 ); # b)
my @blist = @list;
splice ( @blist, $lb, 0, '(', @blist[ $lb .. -1 ] );
splice ( @blist, $rb, 0, ')', @blist[ $rb .. -1 ] );
display ( \@blist );
}
}
}
}
}
}
}
# Since we have a duplicated value, we can add a hash to catch
# expressions identical to the ones we've evaluated already.
# It won't catch the similarity between '2*5' and '5*2' that
# evaluate to the same value. Oh well.
my %tried_that;
sub display {
my ( $list ) = @_;
my $expression = join('',@{$list});
if ( exists $tried_that{ $expression } ) { return; }
$tried_that{ $expression } = 1;
my $result = eval $expression;
if ( defined $result ) {
print "$expression = $result\n";
} else {
print "$expression is undefined.\n";
}
}