Re: NEWBIE Brain Teaser
by Macphisto (Hermit) on Apr 15, 2001 at 05:50 UTC
|
First of all: ++ nysus and kudra for promoting learning Since nobody posted the reason why it does this I figured I'd do so. In fact, instead of waste my time, I just took this from "Learning Perl" written by Randal Schwartz, Published by OReilly Associates.
If the list you are iterating over is made of real variables rather than some function returning a list value, then the variable being used for iteration is in fact an alias for each variable in the
list instead of being merely a copy of the values. It means that if you change the scalar variable, you are also changing that particular element in the list that the variable is standing in for.
For example:
@a = (3,5,7,9);
foreach $one (@a) {
$one *= 3;
}
# @a is now (9,15,21,27)
Notice how altering $one in fact altered each element of @a. This is a feature, not a bug.
Macphisto the I.T. Ninja
Everyone has their demons....
| [reply] |
|
Thanks for the explanation. I've learned something new about Perl - which isn't too difficult for me!
I'm just wondering why it's set up this way? Is there an overall principle I can apply to lists, arrays, scalars, hashes, etc., that will help me understand behavior like this?
Or perhaps I should go back and finish reading "Learning Perl" instead of picking up little bits here and there as I need them. :-\
Chumley
| [reply] |
|
Well, and someone please correct me if I mislead on this post,
If you think about the code:
foreach $each (@list) {
$each *= 2;
$newlist$i = $each;
++$i;
}
From Programming Perl: Note that the loop variable becomes a reference to the element itself, rather than a copy of the element. Hence, modifying the loop variable will modify the original
array.
The snippet loops through the contents of @list and as each cell gets looked at, it is assigned to $each. $each is then modified by multiplying by 2. Modifying $each, modifies the value in the shell, due to the way foreach is constructed in perl. I believe that is all correct. If I have made any mistakes ( other than grammar, and spelling ), someone please tell me.
Macphisto the I.T. Ninja
Everyone has their demons....
| [reply] |
Re: NEWBIE Brain Teaser
by nysus (Parson) on Apr 14, 2001 at 23:15 UTC
|
Highlight the black box below to reveal the answer.
Answer
The code outputs the following five lines:
0
0
0
0
0
Is this what you said? Or did you expect...
1
2
3
4
5
...instead?
|
| [reply] [d/l] [select] |
Re: NEWBIE Brain Teaser
by nysus (Parson) on Apr 15, 2001 at 07:41 UTC
|
Now that the cat's out of the bag, I'll share the explanation I had written up...a bit more long winded:
HOW THE CODE WORKS
So what's going on here? At first glance, this bit of code seems pretty straightforward. Line 1 assigns numbers 1 thru 5 to the first 5 elements of the @list array. Lines 3-6 do nothing but declare variables. The foreach loop starting on line 8 assigns each element of @list to $each and multiplies the contents of $each by two. It then assigns each $each to an element of the @newlist array. Finally, the for loop starting on line 14 simply steps through each element of both the @newlist and @list arrays simultaneously and subtracts one array from the other. You would expect the first iteration of the for loop to be 2-1 yielding "1", the next iteration to give 4-2 yielding "2", and so on. So what's the problem here?
The answer lies in the first statement of the foreach block, $each *= 2;. What happens is that the @list array gets aliased through the $each variable. What does this mean? It means that whatever you do to the $each variable in the foreach loop, you also do to the corresponding element of the @list array. So, for example, when $each is equals "3" and it gets multiplied by "2", the third element of @list gets multiplied by "2", too, setting it's value to "6". And there you have the reason why the output of this code is nothing but 5 zeroes! | [reply] [d/l] [select] |
|
Alright, I also expected a return of 1,2,3,4, and 5 as the values printed out. However, I think I have learned where I went wrong. I just wanted to make sure I understood the last paragraph of HOW THE CODE WORKS.
If I understand this correctly, the $each variable actually acts as a reference to the current place in the array that the foreach loop is currently at. So in effect we are actually altering the first array with the $each *= 2; and then simply setting the $i place in the @newlist to be the same value. This is the reason why we are subtracting, in the last loop, the exact same values from each other.
| [reply] [d/l] [select] |
|
Yes, although I do not know if "referencing" is the accurate term. As the post above mine pointed out, the book "Learning Perl" uses the term alias. How that is different from referencing, I'm not entirely certain and maybe a more monkish Monk than I could answer that.
| [reply] |
|
|
|
|
|
Re: NEWBIE Brain Teaser
by physi (Friar) on Apr 15, 2001 at 01:01 UTC
|
This is a very good example for a very confussing, but very OK behavior.
I'm happy to use ptkdb to figure it out :-)
Go on nysus, it's a very good idea to create this brain teaser! ++ to you
-----------------------------------
--the good, the bad and the physi--
-----------------------------------
| [reply] [d/l] |
Re: NEWBIE Brain Teaser
by spacewarp (Pilgrim) on Apr 15, 2001 at 07:53 UTC
|
I think that the points about printing an explanation for the results are valid, but I would recommend taking it a step further. Since these teasers have a non-obvious result, I think people would also like to see code that does give the answer that was originally expected. In this case, for instance, how to modify the code so that it prints "1 2 3 4 5". After all, I'm sure we've all experienced the frustration of bugs in our logic; for a newbie the frustration would be worse.
Spacewarp
DISCLAIMER:
Use of this advanced computing technology does not imply an endorsement
of Western industrial civilization.
| [reply] |
Re: NEWBIE Brain Teaser
by kha0z (Scribe) on Apr 15, 2001 at 01:09 UTC
|
I expected:
1
2
3
4
5
Update:I understand what is going on now. Thanks for the clarification kundra.
Another Update:I just want to make sure that I understand this. In the "foreach" loop $each is assigned a reference to a scalar value in @list. So when $each is modified in the loop what is actually being modified is $list[$i] which happens to be same value as $newlist[$i]. Right?
Good Hunting,
kha0z | [reply] [d/l] [select] |
|
If you want to understand what's going on, you can
try adding some print statements. This should make
it pretty clear what's happening.
use strict;
my @list = qw(1 2 3 4 5);
my $each = "";
my $i = 0;
my @newlist = ();
foreach $each (@list) {
print "Each was: $each\n"; #new
$each *= 2;
print "Each is: $each\n"; #new
$newlist[$i] = $each;
print "New list is: $newlist[$i]\n"; #new
print "Old list is $list[$i]\n\n"; #new
++$i;
}
for ($i= 0; $i<5; ++$i) {
print ($newlist[$i] - $list[$i], "\n");
}
And maybe in the future it would be a good idea for the
reason for what happens to be included in the 'black box
of enlightenment' so that people won't just be puzzled,
but will learn, too? (Or maybe not; it's good debugging
practice to figure out where the unexpected happens.
Why have the box at all? There isn't much point to just
show the output, since people can just run the
program to see the output.)
| [reply] [d/l] |
|
| [reply] |
|
Thanks for the feedback. I was debating whether or not to post the solution or some kind of hint along with the answer. I'll try some different things in the future and see what seems to be the most helpful to people. For this one, I plan on posting the answer tomorrow.
I think if people can immediately check the answer and see that their first instinct was wrong, this will tend to get them involved more and make them curious...I think they'll start wondering where they went wrong and figure it out for themselves.
Why the black box? Just for aesthetic reasons...makes it a little more fun, I think.
| [reply] |
A reply falls below the community's threshold of quality. You may see it by logging in. |