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

Monks,

How can I step through an array and keep the current element but also access the next or previous element. For example:

my @array = qw( wilma fred barney betty );
I am trying to take the element "wilma" using a for loop so it would be $array[$i] when $i=0 and while still being able to access wilma as $array[$i] I would also be able to grab "fred" without knowing it was $array[1] but just that it came next. $array[$i++] doesn't work.

Replies are listed 'Best First'.
Re: Getting the next array element while still keeping the current one
by NetWallah (Canon) on May 01, 2004 at 23:38 UTC
    The standard idiom TomDLux mentions is probably best.

    You can track the Previous element, and do comparisons thus:

    my $prev; #Previous element for my $elem ( @array ) { # Verify $prev is NON-empty if ($prev){ #Now you can compare $prev and $elem # and do whatever } $prev=$elem; # Prepare for next round of loop }

    Offense, like beauty, is in the eye of the beholder, and a fantasy.
    By guaranteeing freedom of expression, the First Amendment also guarntees offense.

      I was about to post a very similar algorithm, when I saw you had beat me to it. Nicely done! :)

      One minor issue I'd like to highlight, though. You wrote:

      # Verify $prev is NON-empty if ($prev) { ... }

      What if the first value is 0? Without knowing the range of values in @array, we can't assume there are no valid-but-false values. We can usually use undef, but there are situations where even that is considered valid.

      The most straightforward approach is to use a third variable to track whether this is the first element:

      # straightforward approach: track whether this # is the first iteration or not explicitly. my $is_first = 1; my $prev; foreach my $elem ( @array ) { if ( $is_first ) { $is_first = 0; } else { push @result, compute( $elem, $prev ); } $prev = $elem; }

      If we can alter the contents of @array (which might be the case pretty often; consider the common situation of wanting to look through @_), we could initialize $prev with the first element and only iterate over the second and later elements:

      # note that this alters @array my $prev = shift @array; foreach my $elem ( @array ) { push @result, compute( $elem, $prev ); $prev = $elem; }

      If we can't alter it, but the array is short enough to copy:

      my ( $prev, @rest ) = @array; foreach my $elem ( @rest ) { push @result, compute( $elem, $prev ); $prev = $elem; }

      Finally, if we can't alter it, and it is a long list, we can use slices to iterate through second and later elements. (I don't know if this is particularly efficient, as I can't recall whether array slices are lazily evaluated or not):

      my $prev = $array[0]; foreach my $elem ( @array[ 1 .. $#array ] ) { push @result, compute( $elem, $prev ); $prev = $elem; }

      Although at this point I'd likely just switch back to using indexes directly:

      # note that we want $i to stop one short of $#array, # since we access $array[$i+1]. for ( my $i = 0; $i < $#array; ++$i ) { push @result, compute( $array[$i+1], $array[$i] ); }

      Update (at 2004-05-01 18:55 -0700): Rewrote some comments, split the alternate versions out into distinct <code> chunks.

      But how do I tell it what $prev is?
Re: Getting the next array element while still keeping the current one
by TomDLux (Vicar) on May 01, 2004 at 20:16 UTC

    The natural way of processing arrays in Perl is very direct, compared to the Pascal/Java focus on de-referencing arrays.

    for my $elem ( @array ) { # do something with $elem }

    It's worth taking a few moments to see if what you are trying to achieve can be implemented in this style. Some code doesn't fit nicely into the pattern, so using indices becomes necessary. But when it works, the direct style is both more efficient, and clearer to read: do XYZ with each element of the array, instead of getting hung up on implementation details.

    --
    TTTATCGGTCGTTATATAGATGTTTGCA

Re: Getting the next array element while still keeping the current one
by Anonymous Monk on May 01, 2004 at 19:07 UTC

    You're very close. The post-increment operator (as in $i++) returns the current value and then increments the value, while the pre-increment operator (as in ++$i), increments the value immediately and returns the incremented value.

    In this case, however, I don't think you even want that, as it will mess up your counter. All you really want is $i + 1 (untested code follows, note that this will mess up on the last output, since nothing follows 'betty'):

    my @array = qw( wilma fred barney betty ); for my $i (0 .. $#array) { printf( "We have %s, while %s is next.", $array[$i], $array[$i + 1] ); }
      What if my array contains numbers. When I try to use $array[$i+1] -  $array[$i] I get use of uninitialized value in subraction.
        When I try to use $array[$i+1] -  $array[$i] I get use of uninitialized value in subraction.

        This is probably a sign that you run $i across all valid indexes in your array -- which means that $i + 1 will be one past the end of the array, and thus uninitialized. If you are looking for adjacent differences, try this:

        my @array = ( ... ); my @adj_diff; for ( my $i = 0; $i < $#array; ++$i ) { push @adj_diff, $array[$i+1] - $array[$i]; }

        This should result in @adj_diff having one fewer elements than @array. If you like the functional mode of programming, you could do it with a map too:

        my @adj_diff = map { $array[$_+1] - $array[$_] } 0 .. $#array-1;
Re: Getting the next array element while still keeping the current one
by parv (Parson) on May 02, 2004 at 01:04 UTC

    Interesting problem. Here is something...

    #!/usr/local/bin/perl use warnings; use strict; init( [ qw/2 3 5 polka dot bikini/ ] ); my $index = 3; # 'polka' my ($current , $next , $prev ) = get($index); printf "%s\n%s\n%s\n" , $current , $next , $prev; { my ($old , $cursor , $size , @array); sub init { my $a_ref = shift; @array = @{$a_ref}; $size = scalar @array or die "empty array\n"; $cursor = 0; } sub get { $cursor = shift; $old = $cursor++; return ( current() , next_() , prev() ); } sub current { return $array[$old]; } sub prev { return ($old -1) >= 0 ? $array[ $old -1 ] : undef; } sub next_ { return ($old +1) < $size ? $array[ $old +1 ] : undef; } }

    From the looks of it, it seems something like it would already have been in existence on CPAN. Anyway ...

    Mind that one can easily ignore some/all of the return values; above is just one way of the using the-code-that-wants-to-be-a-module. Needless to say that current(), next_(), and prev() can be called directly as desired.

    UPDATE: Return of references to prev() and next_() is changed to the return of values they would have actually returned.

      Well here is another update. I did not like expanding the array on initializtion; code below has some other clarifications and enough changes to warrant another reply.

      #!/usr/local/bin/perl use warnings; use strict; my @in = qw/2 3 5 polka dot bikini/; # Help the helper code help us init( \@in ); foreach (0 .. scalar @in -1) { printf " Current: %s\n Next: %s\nPrevious: %s\n" , map { defined $_ ? $_ : 'undefined' } # # Let something else fetch the values (in forward direction o +nly) get( $_ ) # # Do it yourself #sub { move_fore(); ( current() , next_() , prev() ); }->() ; print "\n"; } # Helper code { my ($old , $cursor , $size , $array); sub init { $array = shift; $size = scalar @{$array} or die "empty array\n"; $old = $cursor = 0; } sub get { $cursor = shift; die "no index given\n" unless defined $cursor; move_fore(); return ( current() , next_() , prev() ); } sub current { return $array->[$old]; } sub prev { return ($old -1) >= 0 ? $array->[ $old -1 ] : undef; } sub next_ { return ($old +1) < $size ? $array->[ $old +1 ] : undef; } sub move_fore { $old = $cursor++; } sub move_back { $old = --$cursor; } }
Re: Getting the next array element while still keeping the current one
by ff (Hermit) on May 02, 2004 at 01:21 UTC
    Sometimes what I do for this is:
    my @array = qw( wilma fred barney betty ); foreach my $i ( 0 .. $#array ) { print "$array[ $i ] is the current element.\t"; print "The next element is "; # Before using the next element, test for its existence. print exists $array[ $i + 1 ] ? $array[ $i + 1 ] : "unprintable" ; print ".\n"; # An approach more to your liking might be: # if ( exists $array[ $i + 1 ] ) { # or: # last if $i == $#array; # or: # print $array[ $i + 1 ] unless $i == $#array; # }

    Other times, like after I've already written my loop but I need to expand its powers to be able to access the next element, I might do:

    use strict; my @array = qw( wilma fred barney betty ); my $ctr = 0; my $next_name; foreach my $name ( @array ) { print "$name is the current element.\t"; # Before using the next element, test for its existence. $next_name = exists $array[ $ctr + 1 ] ? $array[ $ctr + 1 ] : "unavailable" ; # Or you could break out immediately or perhaps have some # "end of array" code: # last if $next_name eq 'unavailable'; # or: # if ( $next_name eq 'unavailable' ) { # # do whatever you do when no more elements # } print "The next element is $next_name.\n"; } continue { $ctr ++; } # Use of the '} continue {' line is more fancy than necessary; # It's useful if you plan to break out of your loop with # a 'next' command.

    Note that while and for have their own ways of letting you set up these loops. I'm just in something of a rut of using foreach (which could be shortened to for) and twist things as above in order to use its approach.

      These code samples, while correct, are tending to get long, and not easily remembered as idioms.

      Here is another attempt at making this readable and small ..(And , hopefully, correct)

      my @a=qw( wilma fred barney betty pebbels dino ); for (my ($i,$p,$c,$n)=(0,undef,@a); $i < scalar @a; $i++,($p,$c,$n) = ($c,$n,$a[$i+1])){ print qq($i Prev=$p \tCurr=$c \tNext=$n\n) } -- Output -- 0 Prev= Curr=wilma Next=fred 1 Prev=wilma Curr=fred Next=barney 2 Prev=fred Curr=barney Next=betty 3 Prev=barney Curr=betty Next=pebbels 4 Prev=betty Curr=pebbels Next=dino 5 Prev=pebbels Curr=dino Next=
      Update:Noticed and Fixed Bug: Changed <= to <, @a[[$i]] to @a[[$i+1]].

      Offense, like beauty, is in the eye of the beholder, and a fantasy.
      By guaranteeing freedom of expression, the First Amendment also guarntees offense.

        That's one clear piece of code (w.r.t. doing everything in/around the main loop).

        BTW, did you intentionally removed the warnings pragma or just forgot?

A reply falls below the community's threshold of quality. You may see it by logging in.