Re: tr operator in eval -- updated
by haukex (Archbishop) on Sep 26, 2020 at 08:37 UTC
|
The example on p.76 of the camel book
What edition of the book are you looking at? In the fourth edition, the example that I think you're referring to is on page 191, and in the third edition, the page number is similarly high. From the 4th ed, it's:
$count = eval "tr/$oldlist/$newlist/";
die if $@; # propagates exception from illegal eval contents
a) eval " ... code ... " as shown in the book gives an eval error message. and eval "tr/$oldlist/$newlist/;" This does not work.
No, there is nothing wrong with that example code in the second, third, and fourth editions of the book. (Nitpicks: under strict, the variables obviously need to be predeclared, and the if $@ pattern is not recommended.)
This means that the code you're running is not exactly the code from the book, and you haven't shown an example of the code that does produce the error you're referring to, nor the error message itself. How do I post a question effectively? and I know what I mean. Why don't you?
even putting the code in braces it does not work.
There is a significant difference between eval STRING and eval BLOCK. The latter is basically just an exception trapping mechanism, it does not delay the compilation of the code inside the block until runtime, which is what the former does, and it is required in this case, since as the book explains, the pattern is built at compile time.
The problem is that $kards should be set to the result of the eval execution, not put inside the eval execution.
Well, that at least gives a hint as to what might have been going wrong. So you probably wrote eval "$kards = $bad =~ tr/$card//;"? Note that what eval STRING does is compile a string of Perl code and execute it. If you take a close look at the string you're giving it, "$kards = $bad =~ tr/$card//;", note that since it's in double quotes, all the variables inside are interpolated. In other words, using the values you've shown here, the string you're actually handing to eval to compile and run is '-99 = AKQJT98765432KKKK =~ tr/K//;', which of course doesn't make sense. You meant for eval to execute the Perl code '$kards = $bad =~ tr/K//;', i.e. only the value of $card should be interpolated, which means you need to prevent the other two variables from being interpolated, for example by putting backslashes in front of the sigils, as in "\$kards = \$bad =~ tr/$card//;".
This also means that your second example code, $kards = eval "$bad =~ tr/$card//;";, has the same issue, because you're asking Perl to execute the piece of code 'AKQJT98765432KKKK =~ tr/K//;'. This only happens to work if you're not using strict, because Perl is taking AKQJT98765432KKKK as a "bareword", a feature that is not allowed under strict "subs". And since one shoud always Use strict and warnings, you should escape $bad in this code as well. | [reply] [d/l] [select] |
|
Thank you very much for the detailed explanation haukex. It is very complete.
To answer some of your other questions I am referring to 2nd Edition (1999) of Camel book. The example on p.76 is in the explanation of tr, not in the explanation of eval. the literal text from that page is : eval "tr/$oldlist/$newlist/"; die $@ if $@;
I knew that perl had progressed some since 1999 but it did not occur to me that there would of course be further editions of the Camel book.
Thanks again. pgmer6809
| [reply] [d/l] |
Re: tr operator in eval -- updated
by tobyink (Canon) on Sep 26, 2020 at 09:42 UTC
|
#<====== Note the ; " ; at the end. This seems required.
No, only the second semicolon is required. And why wouldn't it be? Statements generally need semicolons to separate them from the next statement, apart from block-like statements like if ($cond} { BLOCK } which don't need a semicolon after.
The difference between the two eval uses can be summed up like this:
use strict;
use warnings;
use feature 'say';
my $var = "Hello";
eval " say '$var' ";
eval { say '$var' };
If you understand why those two have different results, you will understand why stringy eval is working for you and block eval is not.
Understanding why this works but you couldn't do it with the block form of eval might also help:
use strict;
use warnings;
my $var = q(Hello',"\n");
eval "print '$var";
| [reply] [d/l] [select] |
Re: tr operator in eval -- updated
by AnomalousMonk (Archbishop) on Sep 26, 2020 at 16:43 UTC
|
When using an eval STRING; statement, I
find it very helpful to build the string separately so that it can be examined with the Perl debugger (see
perldebug) or even with a simple print point.
Win8 Strawberry 5.30.3.1 (64) Sat 09/26/2020 11:37:04
C:\@Work\Perl\monks
>perl
use strict;
use warnings;
my $kards;
my $bad = 'AKQJT98765432KKKK';
my $card = 'K';
my $e_string = "$kards = $bad =~ tr/$card//";
# print "e_string: >$e_string< \n"; # for debug
eval $e_string;
print "1: Num of '$card' in $bad is == $kards \n";
my $kings = $bad =~ tr/K//;
print "2: Num of '$card' in $bad is == $kings \n";
__END__
Use of uninitialized value $kards in concatenation (.) or string at -
+line 8.
Use of uninitialized value $kards in concatenation (.) or string at -
+line 12.
1: Num of 'K' in AKQJT98765432KKKK is ==
2: Num of 'K' in AKQJT98765432KKKK is == 5
Uncomment the print point and the problem with the
eval becomes obvious. (Enabling warnings helps too!)
On another topic...
You seem to have been going over code examples as a self-teaching
exercise. That's commendable, but it should be pointed out that in
practice, there are arguably better ways of handling the "count the
occurrences of an arbitrary set of characters in a string" problem.
eval is expensive, and it's better to avoid the guillotine
when a scalpel will do. A couple of examples (of, I'm sure, many).
Win8 Strawberry 5.30.3.1 (64) Sat 09/26/2020 12:14:28
C:\@Work\Perl\monks
>perl
use strict;
use warnings;
my $n_found;
my $string = 'AKQJT98765432KKKK';
my $set = 'AQK';
# s/// probably fastest in practice.
my $destroy = $string; # s/// alters its target
$n_found = $destroy =~ s{ [\Q$set\E] }{}xmsg;
print "s///: Num of '$set' in '$string' == $n_found \n";
# m// probably a bit slower, use of =()= a bit tricky.
$n_found =()= $string =~ m{ [\Q$set\E] }xmsg;
print "m//: Num of '$set' in '$string' == $n_found \n";
# tr/// is fastest, but needs expensive eval for arbitrary sets.
$n_found = $string =~ tr/KAQ//;
print "tr///: Num of 'KAQ' in '$string' == $n_found \n";
__END__
s///: Num of 'AQK' in 'AKQJT98765432KKKK' == 7
m//: Num of 'AQK' in 'AKQJT98765432KKKK' == 7
tr///: Num of 'KAQ' in 'AKQJT98765432KKKK' == 7
This code also works under Perl version 5.8.9.
Give a man a fish: <%-{-{-{-<
| [reply] [d/l] [select] |
|
Thanks very much. There is a lot I don't know about perl, and even more so about 'modern' perl since I only dive into programming about once per year.
I had in fact discovered the s/char//g trick after making the OP.
Those 'set' operations look very useful. I will have to study up on them.
In this case I am not (yet) sure they will work, since a suit like "AKQxxx" is valid but one like "AAKxxx" is not. (There is only one Ace of spades for example.) So assuming counting members of the set AKQ would return 3 in both cases(??), but the second is invalid.
But no doubt I will find a use for set operations now that I know they are available.
pgmer6809
PS.
I have 'discovered' (I am sure I am not the first!) that a way to avoid destruction of the original string is to use $&
as in $deal =~ s{AKQJ}{$&}xg; will count the number of top cards in $deal, but leave it unchanged:
</code> $deal="AQTxx"; $c = $deal =~ s{AKQJ}{$&}xg; print "$c,$deal\n";
output: 2,AQTxx
| [reply] |
|
Those 'set' operations ...
Please note that the term "set" is used very loosely in
this post. :)
... a suit like "AKQxxx" is valid but one like "AAKxxx" is not.
(There is only one Ace of spades ....)
This seems like a two-phase problem. Once you verify that a string
is in a standard format with, say, no character occuring twice,
it may be very easy to count characters unambiguously.
Within reasonable limits, a single regex could be very handy for
verifying that a string:
- has min to max characters from a
given set of characters;
- that the characters in the string are only from the set;
- that each character from the set occurs only once in the string; and
- that the characters in the string are in a required order.
It may be enough just to verify that no character occurs more than
once in a string. For that,
length $string == uniq split '', $string
or
$string !~ m{ (.) (?= .*? \1) }xms
does the trick.
And there are many other conceivable approaches, all depending on your precise requirements.
Update: Added another simple no-repeat test.
Give a man a fish: <%-{-{-{-<
| [reply] [d/l] [select] |
Re: tr operator in eval
by AnomalousMonk (Archbishop) on Sep 26, 2020 at 03:15 UTC
|
Win8 Strawberry 5.30.3.1 (64) Fri 09/25/2020 23:09:19
C:\@Work\Perl\monks
>perl
my $kards = -99;
my $bad='$card$card';
my $card='K';
eval {print "eval is testing for $card in $bad \n";
$kards = $bad =~ tr/$card//; # tried also tr/$card/$card/; same
+ bad result.
};
print "1: Num of $card in $bad = $kards \n";
$king = $bad =~ tr/$card//;
print "2: Num of K in $bad is = $king \n";
__END__
eval is testing for K in $card$card
1: Num of K in $card$card = 10
2: Num of K in $card$card is = 10
Here, tr is counting the number of characters in the
'$cards' literal character set that are in the $bad string.
Give a man a fish: <%-{-{-{-<
| [reply] [d/l] [select] |
Re: tr operator in eval -- updated
by tybalt89 (Monsignor) on Sep 26, 2020 at 07:49 UTC
|
#!/usr/bin/perl
use strict; # https://perlmonks.org/?node_id=11122227
use warnings;
my $kards = -99;
my $bad="AKQJT98765432KKKK";
my $card='K';
# this next bit works. xtr is just so I can print what eval is eval in
+g.
my $xtr = "$bad =~ tr/$card//;" ;
$kards = eval "\$bad =~ tr/$card//"; # missing \ , no ; needed inside
+eval
print "xtr kards = $kards from eval cmd : $xtr\n";
| [reply] [d/l] |
Re: tr operator in eval -- updated
by BillKSmith (Monsignor) on Sep 26, 2020 at 17:04 UTC
|
The documentation for tr clearly states that it does not interpolate. As a workaround, we can interpolate into a string and then evaluate that string. The first two tests below demonstrate that we build the expected string and that it evaluates correctly. As a notational convenience, we can combine these two steps into one statement. The third test below demonstrates that form.
use strict;
use warnings;
use Test::Simple tests=>3;
my $kards;
my $bad = 'AKQJT98765432KKKK';
my $card = 'K';
my $string = "\$bad =~ tr/$card//";
ok($string eq '$bad =~ tr/K//', "string = '$string'");
$kards = eval $string;
ok($kards == 5, "Number of K's in $bad is $kards");
$kards = eval "\$bad =~ tr/$card//";
ok($kards == 5, "Combined form");
OUTPUT:
1..3
ok 1 - string = '$bad =~ tr/K//'
ok 2 - Number of K's in AKQJT98765432KKKK is 5
ok 3 - Combined form
| [reply] [d/l] [select] |
|
Thanks Bill. I was aware that tr did not interpolate, but the (very old) edition of Camel book said I could achieve the effect I wanted with eval. The example they gave omitted a few key characters, and a more complete statement. I missed the necessity of having the backslash escape before the source string and also having ; characters to terminate both the expression to be eval'ed and also the eval statement itself.
Thanks to the various posts here I am now more enlightened.
Appreciated your use of Test:: in your post. I need to make better use of that package.
pgmer6809
| [reply] |
|
I missed the necessity of ... having ; characters to terminate
both the expression to be eval'ed and also the eval statement
itself.
A ; (semicolon) is not necessary to terminate the last
statement in a file, block, or an eval string derived from an
expression; if any of these have only a single statement, no semicolon
termination at all is necessary. The eval statement itself
must be ;-terminated because other statements follow it.
Update: Another, more general and perhaps better way to put
it is that every statement must be terminated, either by a
semicolon or by the end of the block, file or eval string
containing it. And, of course, extra semicolons have no effect.
Give a man a fish: <%-{-{-{-<
| [reply] [d/l] [select] |
|
Try removing the first backslash from my post and run it again. Study the output. It should be very clear that the first two tests are "not ok". Could you have found the problem yourself if you had seen this before you posted? After the first test fails, it should be clear that the second test does not stand a chance because it is evaluating the wrong string. Look at the string in the output. Note that both variables are interpolated. If you could not solve the problem at this point you could at least have asked the right question: "How can I interpolate one variable in a string, but not the other?" (The answer of course is to escape the dollar sign so that it will be treated as a literal character, not as the sigil of a variable.)
| [reply] |
Re: tr operator in eval -- updated
by LanX (Saint) on Sep 26, 2020 at 08:11 UTC
|
You seem to be confusing eval BLOCK and eval EXPR. They do very different things.
see eval
| [reply] |
Re: tr operator in eval
by pgmer6809 (Sexton) on Sep 26, 2020 at 03:54 UTC
|
I don't think that answer is correct. I know that tr does not interpolate, the patterns are defined at compile time according to the camel book. But the book says that you can put variables in the tr statement if you then use eval to execute the statement. the example in the book (p.76) is eval "tr/$oldlist/$newlist/;"
This does not work.
this two step however does work:
# this next two step works.....
$xtr = "$bad =~ tr/$card//;" ;
$kards = eval "$xtr;";
print "xtr kards = $kards from eval cmd : $xtr\n";
#output: xtr kards = 5 from eval cmd : AKQJT98765432KKKK =~ tr/K//
+;
| [reply] [d/l] [select] |
|
Win8 Strawberry 5.30.3.1 (64) Sat 09/26/2020 0:05:59
C:\@Work\Perl\monks
>perl
use strict;
use warnings;
my $kards;
my $bad = 'AKQJT98765432KKKK';
my $card = 'K';
eval "\$kards = \$bad =~ tr/$card//";
print "1: Num of '$card' in $bad is == $kards \n";
my $kings = $bad =~ tr/K//;
print "2: Num of '$card' in $bad is == $kings \n";
__END__
1: Num of 'K' in AKQJT98765432KKKK is == 5
2: Num of 'K' in AKQJT98765432KKKK is == 5
Note that \$kards \$bad in the eval string are
escaped (update: and $card is not). Why?
Update: Just read your update to the OP and I
think you pretty much have the idea I was trying to get across.
However, now try running the OP update code with strict and
warnings enabled.
Give a man a fish: <%-{-{-{-<
| [reply] [d/l] [select] |