Re: Perl 'Medium' Quiz-of-the-Whatever for 2009-08-11 : Plusified Equations

Shlomi Fish <shlomif-ik1l9ssToec+JF/[email protected]> Wed, 07 Oct 2009 15:20:15 +0200
Newsgroups gmane.comp.lang.perl.qotw.discuss
Message-ID <[email protected]>
On Tuesday 11 August 2009 17:55:06 Shlomi Fish wrote:
> IMPORTANT: Please do not post solutions, hints, or other spoilers
> until at least 60 hours after the date of this message.  Thanks.
>
> ---------------
>
> We are given two positive integers $L and $R we need to find Plusified
> expressions of both for which Eval($E_L) == Eval($E_R). So what is a
> plusified expression? It is an expression where we can choose whether to
> add a single "+" between any consecutive digit. So for example the number
> 123 has the following plusified expression:
>
> * 123
> * 12+3
> * 1+23
> * 1+2+3
>
>
> So if we are given 123 and 96 we can form the following plusified equation:
>
> * 12+3 == 9+6
>
> Your mission is to write a Perl program (or an equivalent program in any
> programming language) that will find all solutions to the plusified
> equation of two numbers given as input. To normalise the output we'll rule
> that:
>
> 1. The equations should be given one at each line.
>
> 2. They will be sorted so consecutive digits will take precedence over
> "+"'s.
>
> 3. A "+" has no surrounding spaces.
>
> 4. The = sign does have a preceding and following space.
>
> As an example:
>
> {{{{{{{{{{{{{
> $ ./plusified-equation 12341234 1010
> 1+23+41+2+34 = 101+0
> 1+2+3+4+1+2+3+4 = 10+10
> }}}}}}}}}}}}}
>

I have a few solutions. The first one I wrote in Perl using Moose and a lot of 
stuff that will be suitable for C++. I later translated it to C++ because the 
kid who asked me for a solution doesn't know Perl. Then I wrote a different 
Perl implementation without using Moose, but with using eval. The first Perl 
solution was kinda slow , the C++ version was very fast, and the third Perl 
implementation was somewhere in between.

Moose version:

[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
#!/usr/bin/perl 

package State::Side;

use strict;
use warnings;

use Moose;

has 'last_digits' => (is => "rw", isa => "Str");
has 'last_sum' => (is => "rw", isa => "Int");
has 'next_digits' => (is => "rw", isa => "Str");
has 'terminator_cb' => (is => "rw", isa => "CodeRef");
has 'formula' => (is => "rw", isa => "Str");

sub recurse
{
    my $self = shift;

    my $num_left = length($self->next_digits()) ;

    if ($num_left == 0)
    {
        $self->last_sum(
            $self->last_sum() + $self->last_digits()
        );
        $self->last_digits("");
       
        return $self->terminator_cb()->(
            $self
        );
    }

    my $next_digit = substr($self->next_digits(), 0, 1);
    # Handle the not plus
    blessed($self)->new(
        last_sum => $self->last_sum(),
        last_digits => 
            ($self->last_digits() . $next_digit),
        next_digits => substr($self->next_digits(), 1),
        terminator_cb => $self->terminator_cb(),
        formula => $self->formula() . $next_digit,
    )->recurse();

    # Handle the with plus.

    # No leading plus.
    if ($self->last_digits() ne "")
    {
        blessed($self)->new(
            last_sum => $self->last_sum()+$self->last_digits(),
            last_digits => $next_digit,
            next_digits => substr($self->next_digits(), 1),
            terminator_cb => $self->terminator_cb(),
            formula => $self->formula() . "+" . $next_digit,
        )->recurse();
    }

    return;
}

package main;

sub solve_for_right
{
    my ($left_state, $right) = @_;

    State::Side->new(
        last_sum => 0,
        last_digits => "",
        next_digits => $right,
        terminator_cb => sub { 

            my ($right_state) = @_;

            if ($left_state->last_sum() == $right_state->last_sum())
            {
                print $left_state->formula(), " = ", 
                      $right_state->formula(), "\n"
                      ;
            }

            return;
        },
        formula => "",
    )->recurse();

    return;
}

sub solve_for
{
    my ($left, $right) = @_;

    State::Side->new(
        last_sum => 0,
        last_digits => "",
        next_digits => $left,
        terminator_cb => sub { 
            my ($left_state) = @_;

            solve_for_right($left_state, $right);
            return;
        },
        formula => "",
    )->recurse();

    return;
}

my ($l, $r) = @ARGV;
solve_for($l, $r);


]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]

C++ version:
------------

[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[

#include <list>
#include <iostream>
#include <cstdlib>


namespace PlusEquation
{
    using namespace std;

    class StateSide;

    typedef list<char> Str;
    typedef void (*terminator_cb_t)(StateSide * self, void * context);

    int int_val(Str & s)
    {
        Str::iterator i = s.begin();
        int ret = 0;
        while (i != s.end())
        {
            ret = ret*10 + (*i-'0');
            i++;
        }
        return ret;
    }

    Str Str_val(int val)
    {
        Str ret;

        while (val)
        {
            ret.push_front('0'+val%10);
            val /= 10;
        }

        return ret;
    }

    ostream& operator<<(ostream& cout, Str & s)
    {
        Str::iterator i = s.begin();

        while (i != s.end())
        {
            cout << (*i);
            i++;
        }

        return cout;
    }

    class StateSide
    {
        protected:
        Str last_digits;
        int last_sum;
        Str next_digits;
        Str formula;
        terminator_cb_t terminator_cb;
        void * context;
        
        public:
        
        StateSide(
                int new_last_sum,
                Str & new_last_digits,
                Str & new_next_digits,
                terminator_cb_t new_terminator_cb,
                void * new_context,
                Str & new_formula
                )
            : last_sum(new_last_sum), context(new_context), 
              terminator_cb(new_terminator_cb)
        {
            last_digits = new_last_digits;
            next_digits = new_next_digits;
            formula = new_formula;
        };
        void recurse(void);
        Str & get_formula(void) { return formula; }
        int get_last_sum(void) { return last_sum; }
    };

    void StateSide::recurse(void)
    {
        int num_left = next_digits.size();

        if (num_left == 0)
        {
            last_sum += int_val(last_digits);
            last_digits = Str();
            terminator_cb(
                this,
                context
            );
            return;
        }
        char next_digit = next_digits.front();

        {
            // Handle the not plus
            Str new_last_digits(last_digits);
            new_last_digits.push_back(next_digit);

            Str new_next_digits(next_digits);
            new_next_digits.pop_front();

            Str new_formula(formula);
            new_formula.push_back(next_digit);

            
            StateSide(
                    last_sum,
                    new_last_digits,
                    new_next_digits,
                    terminator_cb,
                    context,
                    new_formula
            ).recurse();
        }

        // Handle the with plus
        
        // No leading plus.
        if (! last_digits.empty())
        {
            Str new_last_digits;
            new_last_digits.push_back(next_digit);

            Str new_next_digits(next_digits);
            new_next_digits.pop_front();

            Str new_formula(formula);
            new_formula.push_back('+');
            new_formula.push_back(next_digit);

            StateSide(
                    last_sum + int_val(last_digits),
                    new_last_digits,
                    new_next_digits,
                    terminator_cb,
                    context,
                    new_formula
            ).recurse();
        }

        return;
    }

   

    void solve_final_callback (StateSide * right_state, void * context)
    {
        StateSide * left_state = (StateSide *)context;

        if (left_state->get_last_sum() == right_state->get_last_sum())
        {
            cout << left_state->get_formula()  << " = "
                 << right_state->get_formula() << endl
                 ;
        }
    }

    void solve_for_right (StateSide * left_state, void * context)
    {
        int right = *(int *)context;

        Str last_digits;
        Str formula;
        Str right_str = Str_val(right);

        StateSide(
            0,
            last_digits,
            right_str,
            solve_final_callback,
            ((void *)left_state),
            formula
        ).recurse();
    }

    void solve_for(int left, int right)
    {
        Str last_digits;
        Str formula;
        Str left_str = Str_val(left);

        StateSide(
            0,
            last_digits,
            left_str,
            solve_for_right,
            ((void *)&right),
            formula
        ).recurse();
    }
};

int main(int argc, char * argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: ./fondi-plus [num1] [num2]" << std::endl;
        return -1;
    }
    
    PlusEquation::solve_for(atoi(argv[1]), atoi(argv[2]));

#if 0
    PlusEquation::Str s;

    s.push_back('1');
    s.push_back('0');
    s.push_back('5');

    std::cout << PlusEquation::int_val(s) << std::endl;
#endif

    return 0;
}

]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]

Perl version using eval:

[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[
#!/usr/bin/perl 

use strict;
use warnings;

my @n = @ARGV;
die "Wrong" if grep { !/\A\d+\z/ } @n;

r("", [map{ [split//, $_] } @n], []);

sub myeval
{
    my $s = shift;
    $s =~ s{(\d+)}{int($1)}eg;
    return eval $s;
}

sub r
{
    my ($expr, $list_of_rests, $list_of_exprs) = @_;
    
    if (!@$list_of_rests)
    {
        print join(" = ", @$list_of_exprs), "\n";
    }
    else
    {
        my $rest = shift(@$list_of_rests);
        if (!@$rest)
        {
            if ((!@$list_of_exprs) || 
                (myeval($expr) == myeval($list_of_exprs->[0])))
            {
                r("", 
                    [map { [@$_] } @$list_of_rests], 
                    [@$list_of_exprs, $expr]
                );
            }
            return;
        }
        else
        {
            my $first = shift(@$rest);
            for my $suffix ("", (length($expr)?"+":()))
            {
                r(
                    $expr.$suffix.$first, 
                    [map { [@$_] } $rest,@$list_of_rests],
                    [@$list_of_exprs]
                );
            }
        }
    }
    return;
}


]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]

Regards,

	Shlomi Fish

> Enjoy!
>
> Regards,
>
> 	Shlomi Fish

-- 
-----------------------------------------------------------------
Shlomi Fish       http://www.shlomifish.org/
"The Human Hacking Field Guide" - http://xrl.us/bjn8q

God gave us two eyes and ten fingers so we will type five times as much as we
read.