ambrus has asked for the wisdom of the Perl Monks concerning the following question:

You probably know puzzles like this: use arithmetic operators and parenthesis to generate the number 24 using exactly the following four digits in any order: (1, 2, 4, 6) 1, 3, 4, 6.

I've written a ruby program to solve puzzles like this. The program works fine and gives the solution. However, when I translated this program to perl, I couldn't get it work. I've now even reformatted the two programs to be the same line by line (lay then side by side if that helps), but I still can't find out the problem.

I'd be grateful if you could look through the program (witha fresh mind) in any way and find the error. Thank you.

When I run the perl program, I get two deep recursion warnings, and the program eats lots of memory, and doesn't give a result in some time (the ruby one completes within a second).

Here's the ruby program:

#!ruby -w # alapm.rb -- megold egy számrejtvényt # Állítsuk elő a 24-et alapműveletekkel az 1, 3, 4, 6 számjegyek ponto +san # egyszeri felhasználásával. A sorrend tetsz. def mask \ s, m; (0 ... s.size).find_all {|k| 0 != m[k] }.map {|k| s[k] } end; def poss2 \ v1, v2, s1, s2; program to solve puzzles like this. The program w +orks fine yield v1 + v2, "(" + s1 + " + " + s2 + ")"; yield v1 - v2, "(" + s1 + " - " + s2 + ")"; yield v1 * v2, "(" + s1 + " * " + s2 + ")"; 1e-8 < v2.abs and yield Float(v1) / v2, "(" + s1 + " / " + s2 + ")"; s1 =~ /^\d+$/ && s2 =~ /^\d$/ and yield v1 + 10*v2, s1 + s2; end; def poss \ v, s; if 1 == v.size; yield v.first, s.first; else (1 ... (1<<v.size) - 1).each{|m| vv2 = []; ss2 = []; poss(mask(v, ~m), mask(s, ~m)) { |v2, s2| vv2.push v2; ss2.push s2; }; poss(mask(v, m), mask(s, m)) { |v1, s1| (0 ... vv2.size).each {|k| poss2(v1, vv2[k], s1, ss2[k]) { |v3, s3| yield v3, s3; }; }; }; }; end; end; # def main; poss(NUMS, NUMS.map{|x| x.to_s}) { |r, s| (r - TARGET).abs < 1e-6 and print r, " = ", s, "\n"; }; end; NUMS = [1, 3, 4, 6]; TARGET = 24; main; __END__

And the perl one:

#!perl # alapm.pl -- megold egy számrejtvényt # Rubyból fordítottam perlbe kábé szó szerint, tehát rosszabbnak kell # lennie, mintha eleve perlben írnám. Látszik rajta, callbackeket # használok úgy, ahogy perl-ben nem tenném. # Állítsuk elő a 24-et alapműveletekkel az 1, 3, 4, 6 számjegyek ponto +san # egyszeri felhasználásával. A sorrend tetsz. use warnings; use strict; use Carp "cluck"; sub mask { my($s, $m) = @_; [map { $$s[$_] } grep { 0 != ($m & (1<<$_)) } 0 .. @$s - 1]; } sub poss2 { my($v1, $v2, $s1, $s2, $f) = @_; &$f($v1 + $v2, "(" . $s1 . " + " . $s2 . ")"); &$f($v1 - $v2, "(" . $s1 . " - " . $s2 . ")"); &$f($v1 * $v2, "(" . $s1 . " * " . $s2 . ")"); 1e-8 < abs($v2) and &$f($v1 / $v2, "(" . $s1 . " / " . $s2 . ")"); $s1 =~ /^\d+$/ && $s2 =~ /^\d$/ and &$f($v1 + 10 * $v2, $s1 + $s2); } sub poss { my($v, $s, $f) = @_; if (1 == @$v) { &$f($$v[0], $$s[0]); } else { for my $m (1 .. (1<<@$v) - 2) { my(@vv2, @ss2); poss(mask($v, ~$m), mask($s, ~$m), sub { my($v2, $s2) = @_; push @vv2, $v2; push @ss2, $s2; }); poss(mask($v, $m), mask($s, $m), sub { my($v1, $s1) = @_; for my $k (0 .. @vv2 - 1) { poss2($v1, $vv2[$k], $s1, $ss2[$k], sub { my($v3, $s3) = @_; &$f($v3, $s3); }); } }); } } } my(@NUMS, $TARGET); sub main { poss([@NUMS], [@NUMS], sub { my($r, $s) = @_; abs($r - $TARGET) < 1e-6 and print $r, " = ", $s, "\n"; }); } @NUMS = (1, 3, 4, 6); $TARGET = 24; main(); __END__

Update: as talexb notes, the description in the first paragraph has different digits then the code. I've corrected that.

The desired output of the code (and the real output of the ruby code) follows (I should have included this):

Replies are listed 'Best First'.
Re: Number from given digits puzzle
by Anno (Deacon) on Mar 30, 2007 at 23:47 UTC
    Sorry, I have nothing to contribute to tracking down your recursion error. I found the riddle intriguing and tried an independent solution. Here it is:
    my $target = 24; my @nums = ( 1, 3, 4, 6); for ( solve( $target, @nums) ) { my $val = eval; print "$_ = $val\n"; } sub solve { my ( $target, @nums) = @_; my @sols; for my $i ( 0 .. $#nums ) { my @rem = @nums; my $first = splice @rem, $i, 1; if ( @rem ) { for ( complements( $target, $first) ) { my ( $op, $try) = @$_; push @sols, map "$first $op $_", map m{[-+*/]} ? "($_)" : $_, solve( $try, @rem); } } else { push @sols, $first if $first eq $target; # sic! } } return @sols; } sub complements { my ( $t, $x) = @_; ( [ '+', $t - $x], [ '-', $x - $t], $x ? [ '*', $t/$x] : (), $t ? [ '/', $x/$t] : (), ); }
    Not thoroughly tested, but it finds the solution
    6 / (1 - (3 / 4)) = 24
    Anno
      That is freakin' genius! I love it!
        Why, thanks. Quite the blast from the past after almost two years :)

        Your praise is unjustified, however, because my solution is wrong. It doesn't find some valid results like (1 + 3)*(4 + 6) = 40 and others that can't be written without an initial parenthesis.

        The correct solution (as I believe) I came up with this time around is comparatively boring. Essentially it builds all arithmetic expressions that can be built from the original numbers, eval's them and sees if they match the target.

        In case anyone has picked up this ancient thread I'm appending it anyway.

        Anno

        sub solve { my ($target, @nums) = @_; return unless @nums; if ( @nums == 1 ) { return unless eval($nums[0]) eq $target; # eq for epsilon fuzz return @nums; } else { my @sol; for my $i ( 0 .. $#nums - 1 ) { for my $k ( $i + 1 .. $#nums ) { my @rem = @nums; my $nk = splice @rem, $k, 1; my $ni = splice @rem, $i, 1; for ( combine($ni, $nk) ) { unshift @rem, $_; push @sol, solve($target, @rem); shift @rem; } } } return @sol; } } sub combine { my ($x, $y) = @_; my ($vx, $vy) = map eval, $x, $y; my @combi = map operate($x, $y, $_), qw(+ *); push @combi, operate($x, $y, '-'); push @combi, operate($y, $x, '-') if $vx ne $vy; push @combi, operate($x, $y, '/') if $vy; push @combi, operate($y, $x, '/') if $vx and $vx ne $vy; @combi; } sub operate { my ($x, $y, $op) = @_; precedes($op, $_) and $_ = "($_)" for $x, $y; $op = " $op " if precedence($op) >= 3; "$x$op$y" } sub precedes { my ($op, $expr) = @_; precedence($op) < precedence($expr); } sub precedence { for ( shift ) { /\-/ and return 4; /\+/ and return 3; /\// and return 2; /\*/ and return 1; return 0; } } __END__
Re: Number from given digits puzzle
by talexb (Chancellor) on Mar 30, 2007 at 20:45 UTC
    @NUMS = (1, 3, 4, 6); $TARGET = 24; main();

    Shouldn't that '3' be a '2'?

    For the numbers 1, 2, 4 and 6, my brain tells me that (46/2)+1 would do the trick.

    No, I don't know how I did that. ;)

    Alex / talexb / Toronto

    "Groklaw is the open-source mentality applied to legal research" ~ Linus Torvalds

Re: Number from given digits puzzle
by pKai (Priest) on Mar 30, 2007 at 21:06 UTC
    No idea what the desired output is (No ruby here).

    Making 1 change to the perl code, gives me 3 lines of output:

    24 = (3 * (5 - 6)) 24 = (6 / (1 - (3 / 4))) 24 = ((5 - 6) * 3)
    While the 1st and the 3rd line are junk the 2nd is at least one (the?) solution. I got there by substituting the code construction (sub {...}) in the recursive poss2 call by a mere $f, reasoning that what is constructed as
    sub { my($v3, $s3) = @_; &$f($v3, $s3); }
    just looks like a wrapper to the input sub.

    Does that help in any way?


    Of course, I have to admit, I do not understand the algorithm (at the moment; but still trying to figure that part out), nor do I know ruby and can comment on your transforming the iterators ("yield") to perl closures :-/

    Hoping to learn more, when you resolve the case...

      Thanks. Making that change and another trivial one appears to fix the code. I still don't know what problem your change fixes, but I'll try to find it out (it might be something related to aliasing.

      The trivial change is that the second addittion operator in line 28 has to be replaced by a concatenation operator:

      &$f($v1 + 10 * $v2, $s1 . $s2);

      Here's the fixed code for convenience:

        With one more change, the code works fine:
        27,28c27,28 < $s1 =~ /^\d+$/ and $s2 =~ /^\d$/ and < &$f(10*$v1 + $v2, $s1 . $s2); --- > $s1 =~ /^\d+$/ && $s2 =~ /^\d$/ and > &$f($v1 + 10 * $v2, $s1 . $s2);
        and the output I see is
        24 = (3 * (14 - 6)) 24 = (6 / (1 - (3 / 4))) 24 = ((14 - 6) * 3)
        which seems quite OK.

        Now I haven't looked yet if that's an oneliner in some Perl Golf competition, but I'd bet there is something :)

        Would be nice to let us now if in the end you had some personal conclusions about your attempted Ruby/Perl comparison.

        Thanks,
        Krambambuli
Re: Number from given digits puzzle
by ambrus (Abbot) on Apr 01, 2007 at 19:09 UTC

    It turns out that there were three bugs in the code. Two are simple: a plus sign instead of a dot, as mentioned in Re^2: Number from given digits puzzle, and swapping two variables in the same line, as in Re^3: Number from given digits puzzle. The third problem however seems to be a bug in the perl interpreter about handling closures. They say that the bug is fixed from perl 5.9.0.

    Just mentioning a variable at the right point like this makes the problem disappear:

    poss(mask($v, $m), mask($s, $m), sub { my $unused = $f; # <------ my($v1, $s1) = @_; for my $k (0 .. @vv2 - 1) { poss2($v1, $vv2[$k], $s1, $ss2[$k], sub { my($v3, $s3) = @_; &$f($v3, $s3); }); } });