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
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
| [reply] [d/l] [select] |
|
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.
| [reply] |
Re: Array of operators ...
by Your Mother (Archbishop) on Sep 12, 2013 at 19:52 UTC
|
...
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.
| [reply] [d/l] [select] |
Re: Array of operators ...
by kcott (Archbishop) on Sep 13, 2013 at 06:00 UTC
|
$ 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
| [reply] [d/l] |
|
>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
| [reply] [d/l] |
|
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% --
| [reply] [d/l] [select] |
|
|
|
Thanks a lot to everyone ... :)
| [reply] |
Re: Array of operators ...
by abualiga (Scribe) on Sep 13, 2013 at 03:11 UTC
|
| [reply] |
|
| [reply] |
|
| [reply] |
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)
| [reply] [d/l] [select] |
|
|