Beefy Boxes and Bandwidth Generously Provided by pair Networks Bob
Don't ask to ask, just ask
 
PerlMonks  

'+' to +

by NateTut (Deacon)
on May 29, 2005 at 12:07 UTC ( #461499=perlquestion: print w/ replies, xml ) Need Help??
NateTut has asked for the wisdom of the Perl Monks concerning the following question:

Consider the following snippet:
$Problem =~ /(\d*)\s(.)\s(\d*)/; my $Number1 = $1; my $Number2 = $3; my $Operator = $2; my $Answer; if($Operator eq '+') { $Answer = $Number1 + $Number2; } elsif($Operator eq '-') { $Answer = $Number1 - $Number2; } elsif($Operator eq '*') { $Answer = $Number1 * $Number2; } elsif($Operator eq '/') { $Answer = $Number1 / $Number2; }
$Problem is in a format like: 1 + 1 =

The $Operator can be one of the following: +-*/

The code works fine but I was wondering if there is a better (or at least more compact or even different) way?

Comment on '+' to +
Download Code
Re: '+' to +
by monarch (Priest) on May 29, 2005 at 12:16 UTC

    I'm sure there are better ways than the one I'm about to suggest.

    Firstly you've used a regexp to clean up the input, very good, you're protecting yourself against potentially nasty input from the source.. because real-world programmers know real-world people try and stick anything into our machines!

    Perhaps you could make the regexp ensure the operator is only one of the four specified operations and then performing an eval on the reconstructed operation?

    e.g.

    $Problem =~ /(\d+) # one or more digits \s* # zero or more whitespace ([-+\/*]) # operator (1 char only) \s* # zero or more whitespace (\d+)/x; # one or more digits my $Number1 = $1; my $Number2 = $3; my $Operator = $2; my $Answer; my $Code = "$1 $2 $3"; # we're confident this is safe $Answer = eval "$Code";

    Of course if statements should be littered about to ensure the regexp actually matches etc.

    Update: as wfsp pointed out below I have now escaped the forward slash in the regexp because the forward slash is the regexp delimiter.

      Perhaps you could make the regexp ensure the operator is only one of the four specified operations and then performing an eval on the reconstructed operation?

      I agree about laundering the input but I'd want to check that the whole input matches the expected pattern.

      if ( my ($Code) = $Problem =~ /^\s*(\d+\s*[-+/*]\s*\d+)\s*=\s*$/ ) { if ( defined ( my $Answer = eval $Code ) ) { # do stuff with $Answer } else { # do stuff with $@ } } else { # Complain about invalid format }

      I'd even tend to be a bit more liberal with my laundering to let though any simple arithmetic /^([-+/*\s\d().]+)=\s*$/.

      I should explain that this snippet is from a little client/server math game I'm writing for my kids to practice their math skills. Therefore I know the format of $Problem precisely because I generated it in the game server. That's why I didn't bother with a lot of validation.

      What I am looking for is a cool way to convert the string '+' to the operator +.
Re: '+' to +
by wfsp (Abbot) on May 29, 2005 at 12:51 UTC
    I believe checking a regex for failure can prevent many headaches later.
    #!/usr/bin/perl use strict; use warnings; my $problem = '1 / 2 = '; my ($number1, $number2, $operator); if ($problem =~ /(\d+)\s*([-+*\/])\s*(\d+)/){ ($number1, $number2, $operator) = ($1, $2, $3); print "$number1 $number2 $operator\n"; } else{ print "regex failed\n"; }
    I also agree with monarch that as you know what the operator can be you can use a character class.

    FWIW. I always have to check the docs to check which characters need escaping. They say that:

    The special characters for a character class are -]\^$ and are matched using an escape...
    Now I'm sure that's a backslash in there. But I couldn't get the regex above to work without escaping the forward slash.

    Anyone see what I'm missing here?

    Update:

    It's because I used the forward slash as the quote for the regex!

    Many thanks to the monks in the CB.

Re: '+' to +
by ambrus (Abbot) on May 29, 2005 at 13:01 UTC
    #!perl use warnings; use strict; my %arith; $arith{"+"} = sub { $_[0] + $_[1] }; $arith{"-"} = sub { $_[0] - $_[1] }; $arith{"*"} = sub { $_[0] * $_[1] }; $arith{"/"} = sub { $_[0] / $_[1] }; my $Problem = "6 * 8"; $Problem =~ /^(\d*)\s(.)\s(\d*)$/; my $Number1 = $1; my $Number2 = $3; my $Operator = $2; my $Op = $arith{$2} or die qq[wrong operator: "$Operator"]; my $Answer; $Answer = &$Op($Number1, $Number2); print $Answer, "\n"; __END__

    And take care with division by zero errors.

      Very Cool!

      I really like this solution, and decided to play around with it a little. Here it is with a little enhancement...

      use warnings; use strict; my %arith; $arith{"+"} = sub { $_[0] + $_[1] }; $arith{"-"} = sub { $_[0] - $_[1] }; $arith{"*"} = sub { $_[0] * $_[1] }; $arith{"/"} = sub { $_[0] / $_[1] }; my $problem = "6 + 1"; my $answer; if ( $problem =~ /^(\d+)\s*(\S)\s*(\d+)$/ and exists $arith{$2} ) { eval { $answer = $arith{$2}->( $1, $3 ); } } else { die "Oops!\nOperation syntax not understood.\n"; } die "Woops!\n$@" if $@; print "$answer\n";

      • Modified regexp to require two operands and to make whitespace optional.
      • Added basic validity/syntax checking to the results of the pattern match.
      • Trapped (and dealt with) possible errors resulting from mathematical operation using the block version of eval. Note, this is the safe version, we're not evaluating quoted text, we're evaluating known code to trap potential errors resulting from mathematical operation errors such as divide by zero.
      • Added more robust checking for the existance of a particular mathematical operator in the hashtable.
      • Switched to $funcref->(params) notation instead of &$funcref(params).

      It's a long way from being a full fledged algeraic expression parser, but it is a little more robust.


      Dave

Re: '+' to +
by eyepopslikeamosquito (Canon) on May 29, 2005 at 13:35 UTC
      There was an article about Math::Expression on the February issue of The Perl Journal called "Implementing Symbolic Algebra & Calculus in Perl". Really interesting.
      ----------
      kral
      (I apologise for my english!)
Re: '+' to +
by sh1tn (Priest) on May 29, 2005 at 17:03 UTC
    Maybe:
    my $problem = qr{ (\d+ \s+ [\+|\-|\*|\/] \s+ \d+) }x; my $evaluation = '10 / 2'; { local $_ = $evaluation; s/$problem/$1/ee; print }
    STDOUT: 5


Re: '+' to +
by polettix (Vicar) on May 29, 2005 at 23:06 UTC
    Maybe it's a little overkill, but if you plan making your grammar a bit more - er - complicated you can take a look to Parse::RecDescent and satellite stuff. In particular, a problem very similar to yours is dealt with in this tutorial at perl.com.

    Flavio (perl -e 'print(scalar(reverse("\nti.xittelop\@oivalf")))')

    Don't fool yourself.
Re: '+' to +
by ambrus (Abbot) on May 30, 2005 at 08:24 UTC

    This one involves a bit of magic, but it does not use eval, nor does it explicitly list the four operators.

    use Math::BigFloat; $big = Math::BigFloat->bzero; $Problem = "12 - 5"; $Problem =~ /^(\d+)\s(\S)\s(\d+)$/; ($Number1, $Operator, $Number2) = +($1, $2, $3); $Answer = ($big + $Number1)->${\("(" . $Operator)}($Number2); print $Answer, "\n";

      The same idea, except that it's a bit faster, still using only core modules.

      use Math::Complex; $big = 0*i; $Problem = "6 * 8"; $Problem =~ /^(\d+)\s(\S)\s(\d+)$/; ($Number1, $Operator, $Number2) = +($1, $2, $3); $Answer = ($big + $Number1)->${\("(" . $Operator)}($Number2); print $Answer, "\n";
Re: '+' to +
by NateTut (Deacon) on May 31, 2005 at 15:17 UTC
    Thank you all! I knew that there would be at least one TMTOWTDI.

    I need to learn more about eval, I've used it primarly to trap errors.

    I must admit I'm still trying to grok ambrus's solution...

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://461499]
Approved by castaway
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others cooling their heels in the Monastery: (14)
As of 2014-04-23 18:13 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (552 votes), past polls