[comp.lang.perl] $# question

roy%cybrspc@cs.umn.edu (Roy M. Silvernail) (11/19/90)

I'm a little confused about a detail regarding $#...

The manual (PL18, MS-DOS) says that you may find the length of an array
by evaluating $#arrayname. It goes on to explain that $# is actually the
index of the last element, and mentions that most arrays have a 0'th
element.

OK, given an array of 5 elements such as

@foo = ('1','2','3','4','5')

$#foo, in this case, will be 4, and therin lies the confusion. The array is
obviously 5 elements long. Wouldn't $# make more sense if it were to
point to the array element _past_ the last element assigned? (i.e. to
the null element past '5' in the above example) Then it could be used
directly as an indication of size, and as well, you could do

while (<instream>) {
        @foo[$#foo] = $_;
}

to fill an array with an incoming stream of fields. (using @foo[$#foo +1]
works, but it's less intuitive to me)

It's possible that this has been addressed in later patch-levels, but
until PL41 comes around for DOS, I'm stuck at PL18. [ :-( ]

(not-quite-related question: how does one unlink() a file not in the
current directory? I've tried explicit pathnames with both / and \ as
separators, but to no avail)

Thanks in advance!
--
Roy M. Silvernail |+|  roy%cybrspc@cs.umn.edu  |+| #define opinions ALL_MINE;
main(){float x=1;x=x/50;printf("It's only $%.2f, but it's my $%.2f!\n",x,x);}
"This is cyberspace." -- Peter da Silva  :--:  "...and I like it here!" -- me

tchrist@convex.COM (Tom Christiansen) (11/20/90)

In article <LTaVs1w163w@cybrspc> roy%cybrspc@cs.umn.edu (Roy M. Silvernail) writes:
>I'm a little confused about a detail regarding $#...
>
>Wouldn't $# make more sense if it were to
>point to the array element _past_ the last element assigned? (i.e. to
>the null element past '5' in the above example) Then it could be used
>directly as an indication of size, and as well, you could do
>
>while (<instream>) {
>        @foo[$#foo] = $_;
>}
>
>to fill an array with an incoming stream of fields. (using @foo[$#foo +1]
>works, but it's less intuitive to me)

It's also wrong -- you're using @ for $, which is a no-no, and tended
to coredump earlier version of perl.  I used to get bug reports on this
one from my users all the time.  Remember: when you want ONE value, 
use a $, and when you want many, use an @, as in:

    print $foo[1];
    print @foo[1..3];



Anyway, while you could have written the above as:

    while (<INSTREAM>) {
	$foo[++$#foo] = $_;
    }

That's not idiomatic perl.  In <8225@jpl-devvax.JPL.NASA.GOV> Larry
wrote: (yes, I do collect these.)

    Whenever you see subscripts in a Perl script, it's a pretty strong
    indication that things aren't being done the Perl Way.

A cheaper way way to do this in perl would be

    while (<INSTREAM>) {
	push(@a, $_);
    }

This runs in 70% the time of the previous example on my machine.

This is nearly as fast as

    @a = <INSTREAM>;

Which runs in 65% of the original time.  Of course, you may not
want to do this on small-memory machines and/or big files.


--tom

roy%cybrspc@cs.umn.edu (Roy M. Silvernail) (11/21/90)

tchrist@convex.COM (Tom Christiansen) writes:

> It's also wrong -- you're using @ for $, which is a no-no, and tended
> to coredump earlier version of perl.

And I learn Yet Another Perl of Wisdom... ;-) I'm going to learn this
language yet!

> In <8225@jpl-devvax.JPL.NASA.GOV> Larry wrote: (yes, I do collect these.)
>
>     Whenever you see subscripts in a Perl script, it's a pretty strong
>     indication that things aren't being done the Perl Way.

Just to cover all bases, then... is there a preferred Perl Way to access
the last element in an array, besides $foo[$#foo]?

> A cheaper way way to do this in perl would be
>
>     while(<INSTREAM>) {
>       push(@a, $_);
>     }
>
> This runs in 70% the time of the previous example on my machine.
>
> This is nearly as fast as
>
>     @a = <INSTREAM>;
>
> Which runs in 65% of the original time. Of course, you may not
> want to do this on small-memory machines and/or big files.

My application reads <INSTREAM>, processes each line separately, and (if
the line qualifies) appends to an array. Thus, the push() call is just
what I needed.

Thanks, Tom, for your guidance. Until 'The Book' comes out, this
newsgroup is my greatest asset in learning Perl.
--
Roy M. Silvernail |+|  roy%cybrspc@cs.umn.edu  |+| #define opinions ALL_MINE;
main(){float x=1;x=x/50;printf("It's only $%.2f, but it's my $%.2f!\n",x,x);}
"This is cyberspace." -- Peter da Silva  :--:  "...and I like it here!" -- me

tchrist@convex.COM (Tom Christiansen) (11/22/90)

In article <69oys1w163w@cybrspc> roy%cybrspc@cs.umn.edu (Roy M. Silvernail) writes:
|tchrist@convex.COM (Tom Christiansen) writes:
|> In <8225@jpl-devvax.JPL.NASA.GOV> Larry wrote: (yes, I do collect these.)
|>     Whenever you see subscripts in a Perl script, it's a pretty strong
|>     indication that things aren't being done the Perl Way.
|
|Just to cover all bases, then... is there a preferred Perl Way to access
|the last element in an array, besides $foo[$#foo]?

If you just want to add or remove it, push and pop are pretty quick.
If you want to muck with it in place, $foo[$#foo] is fine.  

Just don't do this:

    for ($i = $[; $i <= $#a; $i++) {
	$a[$i] = ....
    }

when this is there begging to be used:

    foreach (@a) { 
	# now access each element (by reference) as $_
	# which is usually implicit anyway
    }

--tom

allbery@NCoast.ORG (Brandon S. Allbery KB8JRR) (11/24/90)

As quoted from <LTaVs1w163w@cybrspc> by roy%cybrspc@cs.umn.edu (Roy M. Silvernail):
+---------------
| $#foo, in this case, will be 4, and therin lies the confusion. The array is
| obviously 5 elements long. Wouldn't $# make more sense if it were to
| point to the array element _past_ the last element assigned? (i.e. to
| the null element past '5' in the above example) Then it could be used
| directly as an indication of size, and as well, you could do
+---------------

Using $# usually means you aren't doing it the Perl way.  To wit:

+---------------
| while (<instream>) {
|         @foo[$#foo] = $_;
| }
+---------------

while (<instream> {
    push(@foo);
}

is faster and easier to read (once you're used to Perl, at least):  "while you
can get something from <instream>, push it onto array `foo'".

+---------------
| It's possible that this has been addressed in later patch-levels, but
| until PL41 comes around for DOS, I'm stuck at PL18. [ :-( ]
+---------------

I think it's been as it is since Perl v1; as a result, it's a bit late to
change it.  I suspect a large number of scripts would break resoundingly.

+---------------
| (not-quite-related question: how does one unlink() a file not in the
| current directory? I've tried explicit pathnames with both / and \ as
| separators, but to no avail)
+---------------

Have you tried doubling the \?  Remember that \ is meaningful to Perl as an
escape character, so to get one into a string you must say something like
'\\dos\\command.com'.  (I assume the MS-DOS version of Perl doesn't use the
old MS-DOS v1.25 calls!)

++Brandon
-- 
Me: Brandon S. Allbery			    VHF/UHF: KB8JRR on 220, 2m, 440
Internet: allbery@NCoast.ORG		    Packet: KB8JRR @ WA8BXN
America OnLine: KB8JRR			    AMPR: KB8JRR.AmPR.ORG [44.70.4.88]
uunet!usenet.ins.cwru.edu!ncoast!allbery    Delphi: ALLBERY

roy%cybrspc@cs.umn.edu (Roy M. Silvernail) (11/24/90)

tchrist@convex.COM (Tom Christiansen) writes:

> Just don't do this:
> 
>     for ($i = $[; $i <= $#a; $i++) {
> 	$a[$i] = ....
>     }
> 
> when this is there begging to be used:
> 
>     foreach (@a) { 
> 	# now access each element (by reference) as $_
> 	# which is usually implicit anyway
>     }

(got me another trick... thanks!)

OK, now consider the following fragment:

for($x=0;$x<$#foo;$x++) {
        @aa = split(/\\/,@foo[$x]);
        @bb = split(/\\/,@foo[$x+1]);  # forward array reference
        $foo[$x] = '' if $bb[$#bb - 1] ne $aa[$#aa - 1];
}

Is there a spiffy Perl Way to handle the forward reference in each
iteration without the explicit variable-controlled loop? (or have I
happened upon the occasional exception that demands the for()
construct?)

(I'll bet a lot of this is in 'The Book'... guess what I'm getting _me_
for Christmas)

Thanks for the expert guidance.
--
Roy M. Silvernail |+|  roy%cybrspc@cs.umn.edu  |+| #define opinions ALL_MINE;
main(){float x=1;x=x/50;printf("It's only $%.2f, but it's my $%.2f!\n",x,x);}
"This is cyberspace." -- Peter da Silva  :--:  "...and I like it here!" -- me

lwall@jpl-devvax.JPL.NASA.GOV (Larry Wall) (11/25/90)

In article <Hg94s1w163w@cybrspc> roy%cybrspc@cs.umn.edu (Roy M. Silvernail) writes:
: Is there a spiffy Perl Way to handle the forward reference in each
: iteration without the explicit variable-controlled loop? (or have I
: happened upon the occasional exception that demands the for()
: construct?)

In general you do this with a backward reference in Perl:

	$prev = '';
	for (@foo) {
	    if ($_ eq $prev) {
	    }
	    ...
	    $prev = $_;
	}

Apart from that, there's no special way to do it.  Sometimes you just
hafta use a normal for().  That's what it's there for, therefore.

Larry

tchrist@convex.COM (Tom Christiansen) (11/26/90)

In article <Hg94s1w163w@cybrspc> roy%cybrspc@cs.umn.edu (Roy M. Silvernail) writes:
|OK, now consider the following fragment:
|
|for($x=0;$x<$#foo;$x++) {
|        @aa = split(/\\/,@foo[$x]);
|        @bb = split(/\\/,@foo[$x+1]);  # forward array reference
|        $foo[$x] = '' if $bb[$#bb - 1] ne $aa[$#aa - 1];
|}
|
|Is there a spiffy Perl Way to handle the forward reference in each
|iteration without the explicit variable-controlled loop? (or have I
|happened upon the occasional exception that demands the for()
|construct?)

Without trying to come up with something illegibly clever, direct
indexing seems the easiest route if you're going to be checking more
than the current element.  

I really do wish you wouldn't do things like this, though:

|        @aa = split(/\\/,@foo[$x]);

This kind of thing makes me nervous.  You see, split wants a scalar
expression there in the second element.  You just passed it a list of one
element.  While in this case, the difference is minimal, one of these
days, it's going to come back to haunt you.  Take my word on this one.

For example,

	@a = ('red', 'green', 'blue'); 
	@b[0] = @a;

Now the zeroth element of @b is 'red', because you've got arrays on both 
sides of the assignment, so you got an array copy.  Just because @b[0]
is only one element long doesn't undo its listishness.

Now, if you subscripted it the other way:

	$b[0] = @a;

Then the zeroth element of @b is '3', that is, the length of @a, because
you had a scalar context on the RHS of the assignment.  Catch that?

Moral of the story:  when you want to talk about a single element, use $.
When you might want more than one, use @.  Try not to confuse a list
that's one element long with a simple scalar value.  If you don't follow
this rule, it will someday come back to bite you where you're not
looking, and you won't be happy.  I promise.

--tom

roy%cybrspc@cs.umn.edu (Roy M. Silvernail) (11/28/90)

tchrist@convex.COM (Tom Christiansen) writes:

> I really do wish you wouldn't do things like this, though:
> 
> |        @aa = split(/\\/,@foo[$x]);
> 
> This kind of thing makes me nervous.

(ducking for cover....)

Actually, I had grabbed that snippet, and _then_ fixed the references to
scalars. (I did _so_ listen to you last time, Tom! :-) Thanks for
keeping me honest!

--
Roy M. Silvernail |+|  roy%cybrspc@cs.umn.edu  |+| #define opinions ALL_MINE;
main(){float x=1;x=x/50;printf("It's only $%.2f, but it's my $%.2f!\n",x,x);}
"This is cyberspace." -- Peter da Silva  :--:  "...and I like it here!" -- me