Idiom for array index that I'm foreach'ing over?

T

Tim Shoppa

OK, I'm a former (and current!) Fortran programmer. But often I end up
with Perl data structures where I not only have to step through each element of
an array, but also must know the index into the array.

I end up writing code that looks like

for my $i (0..$#a) {
my $e = $a[$i];
# Do stuff with $i and $e here
}

That's a bit ugly, I have a numeric loop, an extra variable, that funny
(0..$#a) construct, etc. And if I have to "last" to bail out of the loop
I've lost my index and the array element that I bailed out at.

I'd rather write something like

for (@a) {
# Do stuff with $_ and some magical variable telling me the index
}

in the same way that I can

while (<>) {
# Do stuff with $_ and the line number $. here
}

Is there some magical variable like $. for array loops? Any better
idioms than what I'm currently doing?

Tim.
 
L

Lack Mr G M

|>
|> OK, I'm a former (and current!) Fortran programmer.

Ah, yes. Real FORTRAN programmer can write FORTRAN in any language :).

|> But often I end up
|> with Perl data structures where I not only have to step through each element of
|> an array, but also must know the index into the array.
|>
|> I end up writing code that looks like
|>
|> for my $i (0..$#a) {
|> my $e = $a[$i];
|> # Do stuff with $i and $e here
|> }
|>
|> That's a bit ugly, I have a numeric loop, an extra variable, that funny
|> (0..$#a) construct, etc. And if I have to "last" to bail out of the loop
|> I've lost my index and the array element that I bailed out at.

So:

my $i;
for ($i = 0; $i < @a; $i++) {
my $e = $a[$i];
# Do stuff with $i and $e here
last if ($e > 20_000); # eg: exit statement
}

And now, after the loop, $i is still set to the value causing the exit

|> Is there some magical variable like $. for array loops? Any better
|> idioms than what I'm currently doing?

Well, possibly better is:

my $exiter; # So it is undef
for (my $i = 0; $i < @a; $i++) {
my $e = $a[$i];
# Do stuff with $i and $e here
if ($e > 20_000) { # eg: exit statement
$exiter = $i;
last;
}
}

And now, after the loop, $exiter is set to the value causing the exit
unless you reached the end of the array, in which case it is still
undefined.
 
A

Anno Siegel

Tim Shoppa said:
OK, I'm a former (and current!) Fortran programmer. But often I end up
with Perl data structures where I not only have to step through each element of
an array, but also must know the index into the array.

I end up writing code that looks like

for my $i (0..$#a) {
my $e = $a[$i];
# Do stuff with $i and $e here
}

That's a bit ugly, I have a numeric loop, an extra variable, that funny
(0..$#a) construct, etc. And if I have to "last" to bail out of the loop
I've lost my index and the array element that I bailed out at.

I'd rather write something like

for (@a) {
# Do stuff with $_ and some magical variable telling me the index
}

in the same way that I can

while (<>) {
# Do stuff with $_ and the line number $. here
}

Is there some magical variable like $. for array loops? Any better
idioms than what I'm currently doing?

Not really. The variable you want has been proposed before, right down
to the name (re-use the deprecated $#), but it isn't implemented.

A single variable would be somewhat incomplete. With nested for-loops
you'd also want a way to access the indices of enclosing loops.

If you want to keep the last index accessed, a while-loop is easier
to handle than a for-loop, because it doesn't take privacy of the
loop variable so serious:


my $i = 0;
while ( $i < @array ) {
# do something with $array[ $i];
last if bored();
$i ++; # hi Abigail
}
# use $i, it has the last value from the loop

But that's hardly an idiom, it's just a straight-forward way to do
something like that.

It may suffice to know what is left over from the array (the elements that
have *not* been processed). If so, this is a bit more perlish:

while ( @array ) {
my $element = shift @array;
# do something with $element
last if bored();
}
# now @array is what the loop left over

If you happen to know the original number of elements in @array,
"$original_length - @array" is the last index used.

I say this is "more perlish" because it uses the array as a unit. In
Fortran, an array is just a declaration, the only things you can actually
do something with are array elements, and the only way to access them is
through an index.

Perl can manipulate arrays as a whole, and doing that makes better
use of its high-level features. Once you get accustomed to this style,
you'll find that you rarely want to loop over an index. You loop over
array elements directly, and in fact map and grep do much of the looping.

If you actually need random access to elements, more often than not
the right data structure is a hash and not an array (even if the indices
happen to be numbers). With hashes, indexed access, and hence loops
over hash keys, are more frequent than with arrays, though "each" and
"values" offer alternatives.

Anno
 
T

Tim Shoppa

Perl can manipulate arrays as a whole, and doing that makes better
use of its high-level features. Once you get accustomed to this style,
you'll find that you rarely want to loop over an index. You loop over
array elements directly, and in fact map and grep do much of the looping.

If you actually need random access to elements, more often than not
the right data structure is a hash and not an array (even if the indices
happen to be numbers). With hashes, indexed access, and hence loops
over hash keys, are more frequent than with arrays, though "each" and
"values" offer alternatives.

I agree, hashes are great, hashes are wonderful, I love hashes, but
sometimes the data really is an ordered list.

I should add that the reason that I want to know the index of the
current element is to access the same element number in another
array. (Things like a list of x coordinates in a time series and
y coordinates in a time series and the time value of each x-y
coordinates). Given this one-to-one mapping between multiple arrays, it's
probably best to make one object (possibly hash-based object-oriented
stuff) which contains x,y, and time for each point. But it seems
silly to me to go through the whole object paradigm for a simple
ten-line program, and at the same time it seems a little awkward
that the hypothetical $# doesn't exist. There are lots of ways to
make workable programs, it's just that none of them have the
simplicity and elegance that I expect out of good modern Perl
that I'm writing :)

If the program was bigger, I'd go the full object-oriented route
with modules etc., but it seems silly to do this for such a tiny
quick little thing.

Tim.
 
B

Ben Morrow

my $i = 0;
while ( $i < @array ) {
# do something with $array[ $i];
last if bored();
$i ++; # hi Abigail

I haven't been here long enough to get the 'hi Abigail', but surely
that should be in a continue block so you can 'next'?

Also, I would be more inclined to code this as

my $i = 0;
for (@array) {
# do something with $i and $_;
} continue { $i++ }

or, as you say, completely restructure the code so I didn't need to
know what $i was at all :).

Ben
 
U

Uri Guttman

TS> I should add that the reason that I want to know the index of the
TS> current element is to access the same element number in another
TS> array. (Things like a list of x coordinates in a time series and
TS> y coordinates in a time series and the time value of each x-y
TS> coordinates). Given this one-to-one mapping between multiple
TS> arrays, it's probably best to make one object (possibly hash-based
TS> object-oriented stuff) which contains x,y, and time for each
TS> point. But it seems silly to me to go through the whole object
TS> paradigm for a simple ten-line program, and at the same time it
TS> seems a little awkward that the hypothetical $# doesn't exist.
TS> There are lots of ways to make workable programs, it's just that
TS> none of them have the simplicity and elegance that I expect out of
TS> good modern Perl that I'm writing :)

who says you need objects for that? you need proper perl data
structures. you really have to stop thinking in fortran!! :)

perl data structures use references which are not the same as
objects. to do a 1-1 mapping of two arrays is simple without
indexes. depending on where the data cam from you can just create a
array of arrays ( [ x, y ] coordinates ) or even add the time value as
the third array element. or you could do a simple hash like:

{ x => 1.2,
y => 3.4,
time => 12335,
}

and have an array or hash of those (not sure what the hash key would
be).

TS> If the program was bigger, I'd go the full object-oriented route
TS> with modules etc., but it seems silly to do this for such a tiny
TS> quick little thing.

perl works fine with or without objects. get your mind out of that
fortran gutter where all you have are individual arrays that can only be
linked by their indexes. that is such a poor design and paradigm.

read more about perl data structures in perllol and perldsc.

uri
 
T

Tim Shoppa

perl data structures use references which are not the same as
objects. to do a 1-1 mapping of two arrays is simple without
indexes. depending on where the data cam from you can just create a
array of arrays ( [ x, y ] coordinates ) or even add the time value as
the third array element. or you could do a simple hash like:

{ x => 1.2,
y => 3.4,
time => 12335,
}

and have an array or hash of those (not sure what the hash key would
be).

All those are workable but IMHO a bit ugly compared to creating a
"point" object that has x,y, and time methods. Your last suggestion
comes close but I don't like all those curly brackets (again, my
personal taste).

I did discover Class::MethodMaker which is somewhat elegant for
this sort of stuff.
TS> If the program was bigger, I'd go the full object-oriented route
TS> with modules etc., but it seems silly to do this for such a tiny
TS> quick little thing.

perl works fine with or without objects. get your mind out of that
fortran gutter where all you have are individual arrays that can only be
linked by their indexes. that is such a poor design and paradigm.

But it's where my mind goes for any quick-and-dirty program.

For big programs I'm quite used to the object-oriented approach...
but then I find that many CPAN modules still insist on, for example,
arrays ordered the "wrong" way (e.g. GD::Graph), so I'm forced
to take my elegant objects and break them down to a bunch of arrays
("the Fortran gutter").

I think more use of Class::MethodMaker will satisfy my desire for
elegance even when quick-and-dirty. And maybe I ought to just not
use CPAN modules from the Fortran Gutter.

Tim.
 
A

Anno Siegel

Ben Morrow said:
my $i = 0;
while ( $i < @array ) {
# do something with $array[ $i];
last if bored();
$i ++; # hi Abigail

I haven't been here long enough to get the 'hi Abigail', but surely

Oh, that's all about a blank, the one in front of "++". Abigail endorses
that style, but not everyone follows :)
that should be in a continue block so you can 'next'?

Also, I would be more inclined to code this as

my $i = 0;
for (@array) {
# do something with $i and $_;
} continue { $i++ }

Sure, that's more robust. I was being sketchy.
or, as you say, completely restructure the code so I didn't need to
know what $i was at all :).

As we have learned (news-propagation allowing), the purpose is access
to two (or more) parallel arrays. Perl isn't particularly good at that,
though no worse than comparable languages, it just isn't needed all
that much.

If you can't avoid parallel lists, I see something like

for ( map [ $_, shift @yy], @xx ) {
my ( $x, $y) = @$_;
print "x: $x, y: $y\n";
# here we go
}

which is slightly obscure and partially destructive. Maybe an indexed
approach is okay for a ten-liner.

Anno
 
A

Anno Siegel

Tim Shoppa said:
I agree, hashes are great, hashes are wonderful, I love hashes, but
sometimes the data really is an ordered list.

I should add that the reason that I want to know the index of the
current element is to access the same element number in another
array. (Things like a list of x coordinates in a time series and
y coordinates in a time series and the time value of each x-y
coordinates). Given this one-to-one mapping between multiple arrays, it's
probably best to make one object (possibly hash-based object-oriented
stuff) which contains x,y, and time for each point. But it seems
silly to me to go through the whole object paradigm for a simple
ten-line program, and at the same time it seems a little awkward
that the hypothetical $# doesn't exist. There are lots of ways to
make workable programs, it's just that none of them have the
simplicity and elegance that I expect out of good modern Perl
that I'm writing :)

You don't need objects just because you want an advanced data structure
in Perl. Usually it is possible to build a list of data points (triplets,
in your case) right from the input.

If, for some reason, the data is given in individual arrays, there are
many ways to pre-process them into a list of triplets. I have given an
example in another article not far from here in this thread.

The main loop would look like (untested):

for ( @data_points ) { # @data_points built elsewhere
my ( $x, $y, $t) = @{ $_}; # $_ is an array(ref) of three elements
# do things with $x, $y, $t
}

Hey, it doesn't even use a hash!
If the program was bigger, I'd go the full object-oriented route
with modules etc., but it seems silly to do this for such a tiny
quick little thing.

Well, that's one of the beauties of Perl that you can often use just
enough of it to fit your needs. In this case, an array-ref neatly
encapsulates your data. This is just an alternative to Uri's hashes-used-
as-records.

Anno
 
U

Uri Guttman

TS> All those are workable but IMHO a bit ugly compared to creating a
TS> "point" object that has x,y, and time methods. Your last suggestion
TS> comes close but I don't like all those curly brackets (again, my
TS> personal taste).

but you don't speak perl so you taste is not relevent. even with objects
you need to understand perl refs.

TS> I did discover Class::MethodMaker which is somewhat elegant for
TS> this sort of stuff.

overkill. you just said you didn't want to go OO for this. make up your
mind. and you still need to learn refs.

TS> If the program was bigger, I'd go the full object-oriented route
TS> with modules etc., but it seems silly to do this for such a tiny
TS> quick little thing.

that makes no sense at all.

TS> But it's where my mind goes for any quick-and-dirty program.

if you want to get better at perl, then you should stop thinking in
fortran.

TS> For big programs I'm quite used to the object-oriented approach...
TS> but then I find that many CPAN modules still insist on, for example,
TS> arrays ordered the "wrong" way (e.g. GD::Graph), so I'm forced
TS> to take my elegant objects and break them down to a bunch of arrays
TS> ("the Fortran gutter").

huh?

TS> I think more use of Class::MethodMaker will satisfy my desire for
TS> elegance even when quick-and-dirty. And maybe I ought to just not
TS> use CPAN modules from the Fortran Gutter.

huh?

uri
 
T

Tore Aursand

Is there some magical variable like $. for array loops?

No. You say that you've been programming Fortran, and you're saying - at
the same time - that you're too lazy to write the following code?

my $i = 0;
foreach ( @array ) {
$_ . ' at position ' . $i . "\n";
$i++;
}

Crazy. :)
 
B

Brad Baxter

If you can't avoid parallel lists, I see something like

for ( map [ $_, shift @yy], @xx ) {
my ( $x, $y) = @$_;
print "x: $x, y: $y\n";
# here we go
}

which is slightly obscure and partially destructive. Maybe an indexed
approach is okay for a ten-liner.

Here's one non-destructive version:

my %h;
@h{@xx}=@yy;
for ( map [ $_, $h{$_}], @xx ) {
my ( $x, $y) = @$_;
print "x: $x, y: $y\n";
}

Here's another. :)

{{
my $i=0;
sub rewind{$i=0}
sub eAch(\@\@){$i>$#{$_[0]}&&$i>$#{$_[1]}?():($_[0][$i],$_[1][$i ++])}
}}

while( my( $x, $y ) = eAch( @xx, @yy ) ) {
print "x: $x, y: $y\n";
}


Regards,

Brad
 
M

Michele Dondi

^

I haven't been here long enough to get the 'hi Abigail', but surely
that should be in a continue block so you can 'next'?

I *think* it is because of that space. And I can't believe it: it...
it really works!


Michele
 
T

Tassilo v. Parseval

Also sprach Michele Dondi:
I *think* it is because of that space. And I can't believe it: it...
it really works!

Well, you can do funny things in Perl, particularly with whitespaces:

ethan@ethan:~$ perl
$
# comment
bla


= 5
;

print $bla;
__END__
5

As I recall, Abigail occasionally uses such tricks in an imaginative way
in his signatures.

Tassilo
 
A

Anno Siegel

Brad Baxter said:
If you can't avoid parallel lists, I see something like

for ( map [ $_, shift @yy], @xx ) {
my ( $x, $y) = @$_;
print "x: $x, y: $y\n";
# here we go
}

which is slightly obscure and partially destructive. Maybe an indexed
approach is okay for a ten-liner.

Here's one non-destructive version:

my %h;
@h{@xx}=@yy;
for ( map [ $_, $h{$_}], @xx ) {
my ( $x, $y) = @$_;
print "x: $x, y: $y\n";
}

No. That will generally fail when the elements in @xx aren't unique.
Here's another. :)

{{
my $i=0;
sub rewind{$i=0}
sub eAch(\@\@){$i>$#{$_[0]}&&$i>$#{$_[1]}?():($_[0][$i],$_[1][$i ++])}
}}

Oh, the double-brace convention. I'm feeling flattered :)
while( my( $x, $y ) = eAch( @xx, @yy ) ) {
print "x: $x, y: $y\n";
}

Another variation of the each-for-lists theme, with a nice use of
prototypes. If we had a prototype for "arbitrarily many of \@", this
could be generalized to a step-by-step transposition routine for any
number of parallel arrays.

Anno
 
T

Tim Shoppa

Tore Aursand said:
No. You say that you've been programming Fortran, and you're saying - at
the same time - that you're too lazy to write the following code?

my $i = 0;
foreach ( @array ) {
$_ . ' at position ' . $i . "\n";
$i++;
}

Crazy. :)

Creating a brand new variable with scope outside the loop body and no
use outside the body, which I then have to increment each time
around the loop (and remember not to "next" before the code that increments
it), when obviously the computer internally knows the index of the
element it's working on, isn't crazy?

And while everyone is telling me that I'm some thick-headed Fortran
programmer who doesn't know how to use Perl data structures, I
generally have to generate arrays in these forms because
other Perl modules from CPAN insist on non-structured arrays of
X-Y data. (Specifically, GD::Graph and Perl/Tk, but they take their
arrays in different orders... GD::Graph wants a list of x's and a list of
y's, while Perl/Tk generally wants x0-y0-x1-y1-x2-y2-etc.). I did
learn some less-than-elegant idioms for converting to/from these forms, though.

Tim.
 
B

Brad Baxter

Creating a brand new variable with scope outside the loop body and no
use outside the body, which I then have to increment each time
around the loop (and remember not to "next" before the code that increments
it), when obviously the computer internally knows the index of the
element it's working on, isn't crazy?

Okay, for what it's worth, here's my attempt at cleverness. No, it's not
exactly what you asked for ...

{{
my $i=0;
sub rewind{$i=0}
sub it(\@){$i>$#{$_[0]}?():($i+0,$_[0][$i++])}
}}

while( my( $i, $it ) = it( @array ) ) {
print "i: $i, it: $it\n";
}


Regards,

Brad
 
M

Michele Dondi

Here's another. :)

{{ ^^
my $i=0;
sub rewind{$i=0}
sub eAch(\@\@){$i>$#{$_[0]}&&$i>$#{$_[1]}?():($_[0][$i],$_[1][$i ++])}
}}
^^

Why double braces?!? Ah! Nice JAPH btw...


Michele
 
A

Anno Siegel

Brad Baxter said:
Creating a brand new variable with scope outside the loop body and no
use outside the body, which I then have to increment each time
around the loop (and remember not to "next" before the code that increments
it), when obviously the computer internally knows the index of the
element it's working on, isn't crazy?

Okay, for what it's worth, here's my attempt at cleverness. No, it's not
exactly what you asked for ...

{{
my $i=0;
sub rewind{$i=0}
sub it(\@){$i>$#{$_[0]}?():($i+0,$_[0][$i++])}
}}

while( my( $i, $it ) = it( @array ) ) {
print "i: $i, it: $it\n";
}

Drawing in the OPs intentions, this would be required to work for more
than one array. We can't make the number of arrays entirely arbitrary
due to limitations in prototypes (or can we?), but this works for up
to five arrays:

BEGIN {{
my $i = -1;
sub rewind { $i = -1 }

sub them (;\@\@\@\@\@) {
$i++; # increment early, so we can return a valid index
return if $i >= @{ $_[ 0]};
( $i, map $_->[ $i], @_);
}
}}

while ( my ( $i, $x, $y, $t) = them( @xx, @yy, @tt) ) {
print "i: $i, x: $x, y: $y, t: $t\n";
}

Of course this is utterly fragile when applied to more than one set
of arrays, but for the given purpose it would do.

Anno
 
B

Ben Morrow

BEGIN {{
my $i = -1;
sub rewind { $i = -1 }

sub them (;\@\@\@\@\@) {
$i++; # increment early, so we can return a valid index
return if $i >= @{ $_[ 0]};
( $i, map $_->[ $i], @_);
}
}}

while ( my ( $i, $x, $y, $t) = them( @xx, @yy, @tt) ) {
print "i: $i, x: $x, y: $y, t: $t\n";
}

Of course this is utterly fragile when applied to more than one set
of arrays, but for the given purpose it would do.

To remove some of the fragility (untested):

BEGIN {{
my %i;

sub rewind (;\@\@\@\@\@) { $i{join "", @_} = -1 }

sub them (;\@\@\@\@\@) {
my $set = join "", @_;
$i{$set} = -1 unless defined $i{$set}; # $i{$set} //= -1;
$i{$set}++;
rewind(@_), return if grep { $i{$set} >= @$_ } @_;
# I'm not sure what we want here... 'if @_ == grep ...' may be better.
( $i{$set}, map $_->[$i{$set}], @_);
}
}}

Ben
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,743
Messages
2,569,478
Members
44,898
Latest member
BlairH7607

Latest Threads

Top