http://www.perlmonks.org?node_id=64830

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

Or, which would you rather see in code you're maintaining:
$_ = 'hi' for @list[map {$_ * 2} 0..$#list/2];
Or...
for (my $i = 0; $i <= $#list; $i += 2) { $list[$i] = 'hi'; }
Does the first loop go "too far" down the path of idiomatic perl? Does the second loop give you nasty C flashbacks? Assuming efficiency is not a major concern, did I leave out a more elegant solution?
   MeowChow                                   
               s aamecha.s a..a\u$&owag.print

Replies are listed 'Best First'.
Re: What would you do?
by merlyn (Sage) on Mar 16, 2001 at 03:15 UTC
      I would like to underscore the wisdom of merlyn's comment comment.

      Not only does his comment exist, but it is a succinct explaination of the code. (It is not an English translation of the statement like: do nothing if default variable is not mod 2 otherwise set the list of the default variable to 'hi' for the size of the list.).

      Good example - good style. I wish more people would follow this example! :)

      How is the comment mandatory? It works fine without it.
Re (tilly) 1: What would you do?
by tilly (Archbishop) on Mar 16, 2001 at 04:31 UTC
    I would write it like merlyn did except less tersely:
    foreach my $i (0..$#list) { $list[$i] = "hi" unless $i%2; # Change evens }
Re: What would you do?
by sierrathedog04 (Hermit) on Mar 16, 2001 at 20:29 UTC
    A wise monk once advised me to avoid assigning directly to $_.

    He counseled me that Perl uses $_ in a variety of ways that fledgelings such as myself may not understand. When through youthful exuberance we blow away the existing values stored in $_ we create problems for ourselves later.

    For instance, what if the code you are writing is inside a loop which already uses $_ for something else? For instance:

    while (<MYFILEHANDLE>){ ...your code here... print; }

    All of a sudden your first way of doing it, which assigns to $_, breaks the existing program so that it no longer prints out all of the lines in a file.

    "Does the second loop give you nasty C flashbacks?"

    I would argue that we are programming in Perl. We are not programming in "Not-C". The second method is perfectly proper Perl and is preferable for those of us who are concerned about modifying $_ in a way which may interfere with other parts of a program.

    Update: japhy informs me below that for(split ' '){} inside a  while (<FH>){} creates a local copy of $_ and does not use the same one that the while() uses. Hence, the use of the first method does not break the program I posted.

    Since posting the above I have also noticed that LW, Merlyn et al in the Camel book sometimes assign directly to $_. For example, on page 103 of the second edition they say:

    while (<>) { chomp; if (s/\\$//) { $_ .= <>; redo; } # now process $_ }

    One ought not to be more Catholic than the Pope. If LW and Merlyn think that assigning to $_ is safe and proper Perl style then so be it. </code>

      Not quite. The first method does not assign to the same $_ that the while loop uses. If it did, you couldn't use $_ in that for loop at all.
      while (<FH>) { # localized $_ for (split ' ') { # localized $_ } # previous $_ }
      It's when you start doing silly and dangerous things like:
      sub foo { $_ = shift; # TSK! local $_ tr/aeiou/AEIOU/; return $_; }
      that problems occur. It is a bad idea to assign to a global $_ -- where "global" means "not localized".

      japhy -- Perl and Regex Hacker
        Your comment and several others I have seen here suggest that while(FH) and foreach automatically localize $_. But the folowing code seems does not revert to the old $_ after leaving the while loop. What am I missing? Thanks.
        use strict; use diagnostics; $_ = "Before"; print "Before loop= $_\n"; while (<>){ print "In while loop= $_\n"; last; } print "After while loop= $_\n"; Output: Before loop= Before myinput (entered from the keyboard) In while loop= myinput After while loop= myinput
Re: What would you do?
by stephen (Priest) on Mar 16, 2001 at 03:18 UTC
    Update: I failed to understand the original, and thus even my emended version was stupid. So, I've gotten rid of my original, and kept only the annoying pedantry...

    Of course, what I'd like most of all to see would be:

    fill_even_members($fill_value, \@list); ## ## fill_even_members() ## ## Arguments: ## $fill_value: scalar The value we should fill in ## $list: array ref The list we're to fill ## ## Returns: void ## ## Fills even values of @$list with $fill_value. ## sub fill_even_members { ## Whichever you want }
    Then you could use whatever code you wanted.

    stephen

      Ah, stephen will in the future use the amazing technology of reading the question before answering it. My apologies.

      stephen

      That's fine as long as you don't mind the other elements being undef'ed. But if you're overwriting existing values, you could end up in big trouble...

      buckaduck

      This solution does not do quite the same thing. I would like to keep other elements in @list intact, instead of undefing them.
Re: What would you do?
by bjelli (Pilgrim) on Mar 16, 2001 at 18:56 UTC

    doubling a number seems simpler than doing the modulo, thus:

    # change even-numberd list entries to 'hi' foreach $i (0..@list/2) { $double = $i * 2; $list[$double] = 'hi'; }

    of course you could do away with the extra variable "$double". loose the variable, insert another line of comment to explain what happens. same difference.

    --
    Brigitte    'I never met a chocolate I didnt like'    Jellinek
    http://www.horus.com/~bjelli/         http://perlwelt.horus.at
      Just a little tighter, without getting too obfuscated:

      $list[$_ * 2] = 'hi' for 0 .. @list / 2;

(tye)Re: What would you do?
by tye (Sage) on Mar 17, 2001 at 02:46 UTC

    Ah, I finally realized what all these methods were missing: grep! @list[grep{!$_&1}0..$#list]= ('hi')x@list;

    Yes, I intentionally left off (.../2).

            - tye (but my friends call me "Tye")
Re: What would you do? (benchmarks...)
by MeowChow (Vicar) on Mar 17, 2001 at 03:18 UTC
    I know, I said efficiency didn't matter, but I couldn't help myself:
    use strict; use warnings; use Benchmark qw(cmpthese); my @list = (0..100); cmpthese (-3, { c_style => sub { for (my $i = 0; $i <= $#list; $i += 2) { $list[$i] = 'hi'; } }, slice => sub { $_ = 'hi' for @list[map {$_ * 2} 0..$#list/2] }, modulo => sub { $_ % 2 or $list[$_] = 'hi' for 0..$#list; }, doubler => sub { $list[$_ * 2] = 'hi' for 0..@list/2; }, evilgrep => sub { @list[grep{!$_&1}0..$#list]= ('hi')x@list; } }); ### RESULTS ### evilgrep 8006/s -- -5% -17% -39% -41% modulo 8453/s 6% -- -12% -35% -37% slice 9630/s 20% 14% -- -26% -29% c_style 13041/s 63% 54% 35% -- -3% doubler 13509/s 69% 60% 40% 4% --

      I modified "evilgrep" to use (@list/2) instead of @list since the whole purpose of these benchmarks was obviously to make me look stupid. ;)

      Rate evilgrep slice modulo grep c_style doubler evilgrep 4708/s -- -16% -20% -20% -45% -54% slice 5584/s 19% -- -5% -5% -34% -46% modulo 5865/s 25% 5% -- -0% -31% -43% grep 5893/s 25% 6% 0% -- -31% -43% c_style 8524/s 81% 53% 45% 45% -- -17% doubler 10320/s 119% 85% 76% 75% 21% --
      So I just wanted to note that I'm now in third place instead of last and that I did this with my wimpy office machine instead of my kick-arse home machine or my runs/second would have been much higher than yours as well! q-:

              - tye (but my friends don't call me)
        Well, I'm honor-bound to reply that your seeming algorithmic superiority is directly attributable to your pernicious use of bitwise operations. You also have a subtle precedence bug in evilgrep that is fixed in the code below, and invalidates all your results. Furthermore, all your benchmarks are belong to me:
        use Benchmark qw(cmpthese); my @list = (0..100); cmpthese (-3, { c_style => sub { for (my $i = 0; $i <= $#list; $i += 2) {$list[$i] += 'h1' } }, slice => sub { $_ = 'h2' for @list[map {$_ << 1} 0..$#list/2] }, modulo => sub { $_ & 1 or $list[$_] = 'h3' for 0..$#list }, doubler => sub { $list[$_ << 1] = 'h4' for 0..@list/2 }, evilgrep => sub { @list[grep{!($_&1)}0..$#list]= ('h5')x @list }, grep => sub { @list[grep{!($_&1)}0..$#list]= ('h6')x(@list/2 + 1 +) } }); ### RESULTS ### evilgrep 7467/s -- -19% -40% -42% -43% -65% grep 9244/s 24% -- -25% -28% -30% -57% c_style 12398/s 66% 34% -- -3% -6% -42% modulo 12811/s 72% 39% 3% -- -3% -40% slice 13211/s 77% 43% 7% 3% -- -38% doubler 21289/s 185% 130% 72% 66% 61% --
        ps. My system is currently underclocked, not to mention having slow timings, 4-way interleaving, and CAS2 disabled. If and when I reboot and tweak, I welcome you to bring it! :P

        pps. Assuming of course, my computer boots up...

           MeowChow                                   
                       s aamecha.s a..a\u$&owag.print
Re: What would you do?
by Anonymous Monk on Mar 17, 2001 at 02:15 UTC
    I get the same result with:
    # set even numbered elements of the list to hi<br> map { $list [$_ * 2] = 'hi'; } (0..$#list/2);<br><br>
    I am wondering if there is any difference in efficiency for Perl?
    I did find two $_'s is one line of code very edifying.

    Edit 2001-03-16 by tye

      This is effectively the same thing as bjelli's solution above, with a map instead of a for, and no temporary variable. It is somewhat less efficient due to the use of map in a void context, which is usually considered bad programming practice.