Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Map: Thou Has't Confounded Me For The Last Time! (Referring To The Array You Are Assigning To In Map)

by Revelation (Deacon)
on Feb 14, 2005 at 05:34 UTC ( [id://430695]=perlquestion: print w/replies, xml ) Need Help??

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

In perldoc -f map, one may read:  %hash = map { getkey($_) => $_ } @array; is just a funny way to write
%hash = (); foreach $_ (@array) { $hash{getkey($_)} = $_; }
Under this pretense, this morning I wrote something that roughly looked like ({grin} I'm not using map in a void context, by the way ):
my %res = map { $_ => do { my $re; $re .= $translations{$_} for split //; $re; } if !$res{$_}; } @words;
which I assumed, in accordance with perldoc, would roughly translate to:
my %res; foreach my $word (@rw) { $res{$word} ||= do { my $re; $re .= $translations{$_} for split //,$word; $re; } }
Obviously this isn't a literal translation, but that's unimportant right now. What confused me was that Perl threw an error (using strict), telling me that I needed to define %res in the line:   } if !$res{$_}; Thus, adding a  my %res to the beginning of my map code solved that problem. This was indicative of a larger problem, however; when benchmarking the two hash generations, it became rather obvious that Perl wasn't checking to see if that key already existed in my hash:
Benchmark: running foreach, map_it for at least 5 CPU seconds... foreach: 6 wallclock secs ( 5.34 usr + 0.00 sys = 5.34 CPU) @ 28 +6.87/s (n=1531) map_it: 5 wallclock secs ( 5.15 usr + 0.01 sys = 5.16 CPU) @ 13 +4.77/s (n=695)
upon investigation, it turns out that when inside map, you can not refer to the array (or hash) you are assigning to at all:
my @b = (7,5,5,6); my @a; @a = map { $_ > 5 ? $_ : $a[0] } @b;
simply throws an error, because  $a[0] will return undefined until the map call is completed. As far as I can tell this behavior is not documented and unexplainable. Perl -MO=Deparse did not provide any explanation either. I have always been under the impression that map assigns to a list, like foreach does, but this doesn't seem to be true. In truth, judging from my experiences as well as the form of a map call, map returns a list that the array or hash you have created is _then_ set to.

This, however, doesn't parallel Perl's documentation. Am I doing something stupid? Is Perl's documentation wrong? Are we both wrong? I'd love for somebody to explain this behavior with respect to map as well as any other general quirks of map's behavior if there are some.

Update: I was not commenting on this functionality of map being counterintuitive (I understand that map is returning a list that is then assigned to an array); rather, that it didn't paralel Perl's documentation (which, to me, indicated that map had some magic associated with it, that changed this more 'natural' behavior, so that @b = map { $_ } @a would translate to  push(@b,$_) for @a;.) While PodMaster seems to think that I've taken the documentation out of context, in the context I read it in--which I assume is consistent with an experienced (albeit self-taught and in no way an expert) Perl programmer would read it, this behavior was a bit confusing. I was hoping that my post would yield an explanation as to how the differences in behavior link to the opcodes associated with the these different functions.

Gyan Kapur
The Worst Node Upvoter

Replies are listed 'Best First'.
Re: Map: Thou Has't Confounded Me For The Last Time! (Referring To The Array You Are Assigning To In Map)
by ikegami (Patriarch) on Feb 14, 2005 at 09:11 UTC

    There was a detailed thread on this before. I don't have time to search for it right now, but it was determined that

    %hash = map { getkey($_) => $_ } @array;

    is closer to

    @anon_temp = (); foreach (@array) { push(@anon_temp, getkey($_), $_); } %hash = @anon_temp;

    than to

    %hash = (); foreach (@array) { $hash{getkey($_)} = $_; }

    and that the %hash = @anon_temp; accounts for the speed difference.


    As for your other question, my $something = $value if $something is similar to (or the same as?) (my $something = $value) if $something. The my in executes only if $something was true before this statement. Never use if, unless, etc on a my.

      I feel that ikegami has provided the most accurate explanation of the map operator.

      This is the beginning of the road to understanding the difference between a list and an array, by the way. You'll see both terms in the perldocs, but you won't see a lot to explain the difference. An array is a variable type. A list is what an array contains, but lists often exist without any array at all. The @anon_temp in ikegami's example is one way of explaining such a list.

      Note that foreach is a statement type, while map is an operator. This operator takes one list, and transforms it into a completely new list, element by element.

      --
      [ e d @ h a l l e y . c c ]

        For future reference, there is a good explanation in the perldocs. It's in perlfaq4, accessible directly via perldoc -q 'list and':

        Found in /usr/share/perl/5.8/pod/perlfaq4.pod What is the difference between a list and an array? # contents snipped to avoid massive annoyance
Re: Map: Thou Has't Confounded Me For The Last Time! (Referring To The Array You Are Assigning To In Map)
by Eimi Metamorphoumai (Deacon) on Feb 14, 2005 at 06:04 UTC
    I have always been under the impression that map assigns to a list, like foreach does, but this doesn't seem to be true. In truth, judging from my experiences as well as the form of a map call, map returns a list that the array or hash you have created is _then_ set to.
    That's exactly what's happening. First the map is fully evaluated. It returns a list, and then that list is stored into the array. Similarly, you could use map without storing it (say, in void context, or passing the result to a function like print, or...) and it would evaluate it fully, and then do whatever it's supposed to with it. Put another way, if you were to write
    $a = $a + 1;
    would you be surpised that it doesn't blow up ("But $a can never be one more than itself!")? No, it first evaluates the new value, and then does the assignment.
Re: Map: Thou Has't Confounded Me For The Last Time! (Referring To The Array You Are Assigning To In Map)
by injunjoel (Priest) on Feb 14, 2005 at 05:53 UTC
    Greetings,
    Here is how I understand it.
    in your code
    my %res = map { $_ => do { my $re; $re .= $translations{$_} for split //; $re; } if !$res{$_}; } @words;

    you are refering to the list being created, in this case the %res hash, from within the block doing the assignment, the  map {$_ => do {#code} if !$res{$_};}@words; line. If you take out the  if !$res{$_}; part your resulting %res hash will have the value of the last of any possible identical values in the @words array. My gut feeling is that its more the !$res{$_} part than it is the use of the %res hash on both sides... but then there is your second example of
    my @b = (7,5,5,6); my @a; @a = map { $_ > 5 ? $_ : $a[0] } @b;

    Which again makes me think it's the use of %res on both sides of the = sign. So a basic rule of thumb to use might be that map only works on the contents of @b and never questions @a, only gives (given your example above).
    ...that made more sense when I started.
    Anyone else with a more concise explanation?

    -InjunJoel

    Update!
    Perhaps its the getkey() that is making it unclear? Have a look at perldoc question (how to get info on getkey). The getkey() call was tripping me up in the map documentation when I read it. So assuming that getkey() is only working on $_ from @array and not anything related to %hash(for it may only giveth) it all makes sense...

    "I do not feel obliged to believe that the same God who endowed us with sense, reason and intellect has intended us to forego their use." -Galileo
beware of funny ways
by PodMaster (Abbot) on Feb 14, 2005 at 09:35 UTC
    ... This, however, doesn't parallel Perl's documentation. Am I doing something stupid? Is Perl's documentation wrong? Are we both wrong?
    You're wrong because you're taking an analogy past its limits, ignoring documentation in the interim.
    `perldoc -f map'
    Evaluates the BLOCK or EXPR for each element of LIST (locally setting $_ to each element) and returns the list value composed of the results of each such evaluation...
    %hash = map { getkey($_) => $_ } @array;
    is just a funny way to write
    %hash = (); foreach $_ (@array) { $hash{getkey($_)} = $_; }
    The assignment operator (which is discussed through out the perl documentation) is waiting for map (a list operator) to return a list, before it can proceed with the assignment. To get
    my %res = map { $_ => do { my $re; $re .= $translations{$_} for split //; $re; } if !$res{$_}; } @words;
    to work as you intended , you need to change it to
    my %res; my @res = map { $_ => do { my $re; $re .= $translations{$_} for split //; $re; } if !$res{$_}; } @words; %res = @res;
    update: or even
    my %res; %res = @_ =map { $_ => do { my $re; $re .= $translations{$_} for split //; $re; } if !$res{$_}; } @words;

    MJD says "you can't just make shit up and expect the computer to know what you mean, retardo!"
    I run a Win32 PPM repository for perl 5.6.x and 5.8.x -- I take requests (README).
    ** The third rule of perl club is a statement of fact: pod is sexy.

      I'm pretty sure neither of those solutions will work. As the OP stated, the problem is that map returns a list; you are assigning an array to that value. Thus,  %res or  @_ is not populated until the map returns the whole list (unless you've inserted some magic I'm unaware of.)
        Thus, %res or @_ is not populated until the map returns the whole list
        That's right, and I've said as much (is waiting for map (a list operator) to return a list).
        (unless you've inserted some magic I'm unaware of.)
        Nope, just being funny (which is inately magical).

        MJD says "you can't just make shit up and expect the computer to know what you mean, retardo!"
        I run a Win32 PPM repository for perl 5.6.x and 5.8.x -- I take requests (README).
        ** The third rule of perl club is a statement of fact: pod is sexy.

Re: Map: Thou Has't Confounded Me For The Last Time! (Referring To The Array You Are Assigning To In Map)
by Jasper (Chaplain) on Feb 14, 2005 at 11:55 UTC
    upon investigation, it turns out that when inside map, you can not refer to the array (or hash) you are assigning to at all

    Of course you can't. It doesn't exist yet.

    $a = $a + 7;

    This code evaluates the RHS first, then assigns to $a. In the warped logic you propose, $a = ($a = $a + 7) + 7 ad infinitum :)

    You can of course refer to the array in an assignment when values already exist for the parts of the array you're referring to.
Re: Map: Thou Has't Confounded Me For The Last Time! (Referring To The Array You Are Assigning To In Map)
by Aristotle (Chancellor) on Feb 19, 2005 at 05:52 UTC

    Noting a point which hasn't come up yet:

    which I assumed, in accordance with perldoc, would roughly translate to:

    my %res; foreach my $word (@rw) { $res{$word} ||= do { my $re; $re .= $translations{$_} for split //,$word; $re; } }

    No, it doesn't. You are using || here; that shortcircuits, ie if $res{$word} already exists (and is true), then the do {} won't even execute. There's no way to duplicate this with map.

    Btw, this:

    my $re; $re .= $translations{$_} for split //,$word; $re;

    is more succintly expressed as

    join '', @translations{ split //, $word };

    Makeshifts last the longest.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://430695]
Approved by jbrugger
Front-paged by jbrugger
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (7)
As of 2024-03-28 15:10 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found