Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Array of operators ...

by janDD (Acolyte)
on Sep 12, 2013 at 19:40 UTC ( [id://1053789]=perlquestion: print w/replies, xml ) Need Help??

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

Hi, is it possible to loop through an array of operators and "apply" them? Example:
my @ops = ("+","-","*","/"); foreach my $op (@ops){ print "4 $op 5 = "; }
I want the code to print
4+5=9 4-5=-1 4*5=20 4/5=0.8
I tried to define the operators as references ... but that didn't work: my @ops = (\+,\,\*,\/); Thanks for any help/idea Jan

Replies are listed 'Best First'.
Re: Array of operators ...
by tobyink (Canon) on Sep 12, 2013 at 20:18 UTC

    You can't create a reference to an operator, but you can create a reference to a sub.

    my @term = (4, 5); my @ops = ( [ '+' => sub { $_[0] + $_[1] } ], [ '-' => sub { $_[0] - $_[1] } ], [ '*' => sub { $_[0] * $_[1] } ], [ '/' => sub { $_[0] / $_[1] } ], ); for my $op (@ops) { my ($symbol, $coderef) = @$op; printf( "%s %s %s = %s\n", $term[0], $symbol, $term[1], $coderef->(@term), ); }

    That's arguably a little cleaner and safer than using eval, but eval might be simpler and may be sufficient for your needs.

    use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name
      I used this exact approach when I was duplicating the PHP "date" function in Perl. Given, as you go to longer mathematical expressions, it'll probably be simpler to just pattern match for non-allowed characters (anything that isn't a number or limited set of operators) and then eval if everything looks ok.
Re: Array of operators ...
by Your Mother (Archbishop) on Sep 12, 2013 at 19:52 UTC

    Try something along these lines-

    ... my $statement = "4 $op 5"; print join(" = ", $statement, eval $statement), $/; ... __END__ 4 + 5 = 9 4 - 5 = -1 4 * 5 = 20 4 / 5 = 0.8

    eval can be dangerous since it executes any code so be careful with it.

Re: Array of operators ...
by kcott (Archbishop) on Sep 13, 2013 at 06:00 UTC

    G'day janDD,

    Here's another way to do it (built upon ++tobyink's solution).

    $ perl -Mstrict -Mwarnings -le ' { my %op_func = ( "+" => sub { $_[0] + $_[1] }, "-" => sub { $_[0] - $_[1] }, "*" => sub { $_[0] * $_[1] }, "/" => sub { $_[0] / $_[1] }, ); sub expr { my ($lhs, $op, $rhs) = split " ", shift; $op_func{$op}->($lhs, $rhs); } } my @ops = ("+","-","*","/"); foreach my $op (@ops){ print "4 $op 5 = ", expr("4 $op 5"); } ' 4 + 5 = 9 4 - 5 = -1 4 * 5 = 20 4 / 5 = 0.8

    -- Ken

      This also works nicely with state built-in of 5.10+ for tighter encapsulation, compile-time evaluation of hash:

      >perl -wMstrict -le "use 5.010; ;; sub expr { state $op_func = { '+' => sub { $_[0] + $_[1] }, '-' => sub { $_[0] - $_[1] }, '*' => sub { $_[0] * $_[1] }, '/' => sub { $_[0] / $_[1] }, }; ;; my ($lhs, $op, $rhs) = split ' ', shift; return $op_func->{$op}->($lhs, $rhs); } ;; for my $op (qw(+ - * /)) { my $exp = qq{4 $op 5}; print $exp, ' = ', expr($exp); } " 4 + 5 = 9 4 - 5 = -1 4 * 5 = 20 4 / 5 = 0.8

        For my own code (I'm running 5.18.1), I probably would have used state as you've done there: your post got a ++ from me. However, unless the question specifically involves a more recent Perl, or I know the person I'm responding to is familiar with 5.10.0+, I find providing a pre-5.10 (or, more strict, an any Perl5) solution to be a lot less hassle: that would be non-virtuous laziness on my part. :-)

        While there may be some subtlety I'm missing, which I'm more than happy to learn about, I really don't see any "tighter encapsulation". In both cases, the hash is only evaluated once and only visible by the expr subroutine. From a visual perspective, the hash is clearly more closely associated with the subroutine: in fact, that would be one of my reasons for preferring it.

        There's another issue which I'd forgotten about until I ran a few tests. You can't simply substitute my with state in my %op_func = (...);. If you try this, you get a compilation error:

        "Initialization of state variables in list context currently forbidden"

        So, you need to code state $op_func = {...}; as you have here. Having to dereference $op_func incurs a minimal overhead which, in general, wouldn't prevent me from using it unless efficiency was of particular concern. I found the %op_func solution to be consistently 2% faster than the $op_func solution:

        $ perl -Mstrict -Mwarnings -E ' use Benchmark qw{cmpthese}; { my %op_func1 = ( "+" => sub { $_[0] + $_[1] }, "-" => sub { $_[0] - $_[1] }, "*" => sub { $_[0] * $_[1] }, "/" => sub { $_[0] / $_[1] }, ); sub expr1 { my ($lhs, $op, $rhs) = split " ", shift; $op_func1{$op}->($lhs, $rhs); } } sub expr2 { state $op_func2 = { "+" => sub { $_[0] + $_[1] }, "-" => sub { $_[0] - $_[1] }, "*" => sub { $_[0] * $_[1] }, "/" => sub { $_[0] / $_[1] }, }; my ($lhs, $op, $rhs) = split " ", shift; $op_func2->{$op}->($lhs, $rhs); } my @ops = ("+","-","*","/"); cmpthese(-1, { with_my => sub { expr1("4 $_ 5") for @ops }, with_state => sub { expr2("4 $_ 5") for @ops }, }); ' Rate with_state with_my with_state 112733/s -- -2% with_my 114840/s 2% --

        -- Ken

      Thanks a lot to everyone ... :)
Re: Array of operators ...
by abualiga (Scribe) on Sep 13, 2013 at 03:11 UTC
      looks like exactly the same task.

      Perl classes or interview question?

      Cheers Rolf

      ( addicted to the Perl Programming Language)

        Plain curiosity in my case.

Re: Array of operators ...
by LanX (Saint) on Sep 13, 2013 at 11:16 UTC
    IMHO an easier compromise between elegant eval and security issues is a hash with '%allowed_ops':

    DB<137> %allowed_ops=map { $_=>$_} qw# + - * / # => ("+", "+", "-", "-", "*", "*", "/", "/") DB<138> $op ="+" => "+" DB<139> $statement = "4 $allowed_ops{$op} 5"; => "4 + 5" DB<140> print "$statement = ", eval $statement => 1 4 + 5 = 9

    you can also explicitly check for exists in the hash to catch errors beforehand and you are free to "invent" new operators.

    Cheers Rolf

    ( addicted to the Perl Programming Language)

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others browsing the Monastery: (8)
As of 2024-04-18 08:36 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found