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

In the code below, the sub article_title1 shows the error I made and sub article_title2 shows the hack that fixed it. Given this, I've two questions:

  1. Exactly what is going wrong in the first sub?
  2. Is there an idiomatic version of the second sub?
It looks as though the internal pointer to the array element is getting trashed by the dual action of for and shift, but I'm not clear on the details.

#!/perl/bin/perl # use strict; use warnings; use diagnostics; my @text_master = <DATA>; chomp @text_master; my @text = @text_master; article_title1(); print "\n"; @text = @text_master; article_title2(); sub article_title1 { local $_ = shift(@text); # left in for side effect if (@text) { for (@text) { print "# TEXT-1 = '$_'\n"; last if (/^$/); shift(@text); } } print "# TEXT-2 = '$_'\n" for @text; } sub article_title2 { local $_ = shift(@text); # left in for side effect if (@text) { my @text_copy = @text; for (@text) { print "# TEXT-1 = '$_'\n"; last if (/^$/); shift(@text_copy); } @text = @text_copy; } print "# TEXT-2 = '$_'\n" for @text; } __DATA__ ==Play Chess: Click the join icon to play Matches_Sought.gif Matches Sought Graph You can also start a game by clicking a dot in the Matches Sought grap +h. Rest the mouse pointer over a dot. Games on ICC are often described by their time limits.

Which results in:

# TEXT-1 = 'Click the join icon to play'
# TEXT-1 = 'Matches_Sought.gif Matches Sought Graph'
# TEXT-1 = 'You can also start by clicking a dot in the graph.'
# TEXT-1 = 'Rest the mouse pointer over a dot.'
# TEXT-1 = 'Games on ICC are often described by their time limits.'
# TEXT-2 = ''
# TEXT-2 = 'Rest the mouse pointer over a dot.'
# TEXT-2 = ''
# TEXT-2 = 'Games on ICC are often described by their time limits.'

# TEXT-1 = 'Click the join icon to play'
# TEXT-1 = ''
# TEXT-2 = ''
# TEXT-2 = 'Matches_Sought.gif Matches Sought Graph'
# TEXT-2 = ''
# TEXT-2 = 'You can also start by clicking a dot in the graph.'
# TEXT-2 = ''
# TEXT-2 = 'Rest the mouse pointer over a dot.'
# TEXT-2 = ''
# TEXT-2 = 'Games on ICC are often described by their time limits.'

--hsm

"Never try to teach a pig to sing...it wastes your time and it annoys the pig."

Replies are listed 'Best First'.
Re: for mistake with shift
by cees (Curate) on Dec 31, 2003 at 05:32 UTC

    Straight from the perlsyn documentation:

    If any part of LIST is an array, "foreach" will get very confused if you add or remove elements within the loop body, for example with "splice". So don't do that.

    In your first function you are altering the array that you are iterating over with foreach inside the foreach block. This is not supported behaviour. The reason it doesn't happen in your second function is because you are altering a copy of the array.

    - Cees

      That's the reference I'd forgotten! Thanks much---I guess I'm so used to a 'forgiving' language, that I went ahead and did the unreasonable.

      --hsm

      "Never try to teach a pig to sing...it wastes your time and it annoys the pig."
Re: for mistake with shift
by NetWallah (Canon) on Dec 31, 2003 at 05:52 UTC
    You are breaking perl by doing the vorboten:
    From perldoc perlsyn:

    If any part of LIST is an array, foreach will get very confused if you add or remove elements within the loop body, for example with splice. So don't do that.

    The shift within the for loop is doing just that.

    Here is an alternate version of the sub that behaves the way I think you intend, which does not use the shift:

    sub article_title1 { local $_ = shift(@text); # left in for side effect my $BlankSeen=0; for (@text){ $BlankSeen ? print "# TEXT-2 = '$_'\n" : print "# TEXT-1 = '$_'\n"; $BlankSeen = 1 if (/^$/); } }

    "When you are faced with a dilemma, might as well make dilemmanade. "
Re: for mistake with shift
by Roger (Parson) on Dec 31, 2003 at 11:09 UTC
    The equivalent of article_title2
    sub article_title3 { local $_ = shift(@text); # left in for side effect while (defined ($_ = shift @text)) { print "# TEXT-1 = '$_'\n"; unshift(@text, $_), last if /^$/; # put empty line back } print "# TEXT-2 = '$_'\n" for @text; }

      Yes! I like that version, it does exactly what I want without the overhead of the duplicate array. I wonder if while (shift(@text)) { wouldn't work as well? Thanks!

      --hsm

      "Never try to teach a pig to sing...it wastes your time and it annoys the pig."

        To answer my own less than bright question, Yes, but then the unshift would have to be outside of the loop. Something like:

        while(shift @text) { # do whatever... } unshift(@text,'') if @text;
        I Think I like the while (defined (shift @text)) { better.

        --hsm

        "Never try to teach a pig to sing...it wastes your time and it annoys the pig."
Re: for mistake with shift
by Roy Johnson (Monsignor) on Dec 31, 2003 at 19:40 UTC
    I'm a little late to the party, but I thought this was a good use for some of the more obscure operators.
    sub article_title1 { shift(@text); @text = grep { print "# TEXT-1 = '$_'\n" if ?^?../^$/; /^$/..0; } @text; print "# TEXT-2 = '$_'\n" for @text; }
    Here's a shortcircuiting version that avoids unshifting:
    sub article_title2 { shift(@text); while (@text) { print "# TEXT-1 = '$text[0]'\n"; last if ($text[0] eq ''); shift(@text); } print "# TEXT-2 = '$_'\n" for @text; }

    The PerlMonk tr/// Advocate