Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

About the Perl range operator

Tags:

perl

I have a question about the range operator in Perl. The code is here:

 #! /usr/bin/perl
 open FILE, "<test.txt";
 while (<FILE>) {
     print if (1 .. 5);
 }
 close FILE;

The contents in test.txt is something like:

 1
 2
 3
 4
 5

The result of the code is all five lines of the file. There is no difference when I use print if (1 ... 5). I think it should output four lines. Is there something I made a mistake in?

like image 950
ruanhao Avatar asked May 31 '13 07:05

ruanhao


3 Answers

The contents of the file actually don't matter here. What does matter, however, is the line number (of each processed line), as that's what gets checked against each operand of the .. operator. It's actually can be written as...

print if ($. == 1) .. ($. == 5);

By the rules of flip-flopping, print will start to work when line number becomes equal to 1, and finishes when line number becomes equal to 5. The only difference between .. and ... in flip-flop is about testing both operands on the same expression, which is not relevant in this case.

What you might have wanted to use was ...

print if $_ ~~ [1..5];

... that is, testing the contents of the line against a range. But there is no difference between .. and ... in a list context - they both return the same range:

print for (1..5); # 12345
print for (1...5); # 12345 as well

And now for something... well, not so completely different. ) Consider the following:

while (<DATA>) {
  print '..', $_  if 1..($. >= 1);
  print '...', $_ if 1...($. >= 1);
}
__DATA__
1
2
3

It prints...

..1
...1
...2

... and then nothing, because...

  • The .. operator checks both conditions on the same - first - line. Checking the first condition ($. == 1) switches it on (and makes the result of the whole .. expression equal to 1). But guess what? It's immediately switched off then, as $. >= 1 also evaluates to 1 (true). As flip-flop is switched off, all the next lines have to pass the first condition again, and that just can't happen.

  • The ... operator is more relaxed. For the first line, as the first condition is truthy, the gateway is opened - but the second expression is not checked here! It will be checked, though, for the second line, closing the gateway - yet 2 manages to get through.

like image 183
raina77ow Avatar answered Oct 15 '22 06:10

raina77ow


In your case it doesn't matter as .. can't do flip-flop for the same line number.

To quote perlop:

In scalar context, ".." returns a boolean value. The operator is bistable, like a flip-flop, and emulates the line-range (comma) operator of sed, awk, and various editors. Each ".." operator maintains its own boolean state, even across calls to a subroutine that contains it. It is false as long as its left operand is false. Once the left operand is true, the range operator stays true until the right operand is true, AFTER which the range operator becomes false again. It doesn't become false till the next time the range operator is evaluated. It can test the right operand and become false on the same evaluation it became true (as in awk), but it still returns true once. If you don't want it to test the right operand until the next evaluation, as in sed, just use three dots ("...") instead of two. In all other regards, "..." behaves just like ".." does.

The right operand is not evaluated while the operator is in the "false" state, and the left operand is not evaluated while the operator is in the "true" state. The precedence is a little lower than || and &&. The value returned is either the empty string for false, or a sequence number (beginning with 1) for true. The sequence number is reset for each range encountered. The final sequence number in a range has the string "E0" appended to it, which doesn't affect its numeric value, but gives you something to search for if you want to exclude the endpoint. You can exclude the beginning point by waiting for the sequence number to be greater than 1.

If either operand of scalar ".." is a constant expression, that operand is considered true if it is equal (==) to the current input line number (the $. variable).

like image 5
mpapec Avatar answered Oct 15 '22 05:10

mpapec


Thank you for your answers which enlighten me. After reading perldoc and doing some tests, I want to share my understanding here. If there is something wrong, please let me know. :)

print if (1 .. 5) is a shortcut for print if ($. == 1 .. $. == 5). The initial state of the expression is false, after reading the first line, $. == 1 is satisfied and the state turns to true. So the first line is printed. Then the second line, the third, the fourth.

When it comes to the fifth line, the state is still true. This is the state that decides whether to go into block. So the fifth line is also printed, then $. == 5 is satisfied, at this time, the next state becomes false. So if there were any line afterwards, they will not be printed.

There is no difference between .. and ... here, because the difference only takes place when the left operand returns true. Let me show it:

The example here is select from perlop. This example confuses me at first, but let me show my understanding.

 @lines = ("   - Foo",
           "01 - Bar",
           "1  - Baz",
           "   - Quux");
 foreach (@lines) {
     if (/0/ .. /1/) {
         print "$_\n";
     }
 }

When it comes to "01 - Bar", /0/ is satisfied, so the state becomes true. And at the same time, /1/ is also satisfied, so the next state suddenly becomes false, this is where .. is different from ..., because ... will not evaluate /1/ now, and the next state will still be true. So "1 - Baz" will not be printed out. Otherwise, when ... is used, this line can be printed out.

like image 1
ruanhao Avatar answered Oct 15 '22 04:10

ruanhao