Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery

Is there a "better" way to cycle an array?

by rje (Deacon)
on Sep 07, 2013 at 19:14 UTC ( #1052838=perlquestion: print w/replies, xml ) Need Help??

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

Brothers and Sisters:

My code cycles thru the elements of an array, sort of like rotating bits in a register. The important bit is the 'cycle' function -- it rotates or cycles thru the array without emptying it. Array order doesn't need to be preserved. A functional sample would be:

my $stuff = [ 'a', 'b', 'c', 'd' ]; sub cycle { push @{$_[0]}, shift @{$_[0]}; return $_[0]->[-1]; } print cycle $stuff for 1..30; # result: # $ perl # abcdabcdabcdabcdabcdabcdabcdab

I'm wondering if there's a better way to do it. Anyone have any suggestions?

Replies are listed 'Best First'.
Re: Is there a "better" way to cycle an array?
by choroba (Cardinal) on Sep 07, 2013 at 19:21 UTC
    You can use the modulo operator:
    for my $i (0 .. 29) { print $stuff->[ $i % @$stuff ]; }
    لսႽ ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ
Re: Is there a "better" way to cycle an array?
by hdb (Monsignor) on Sep 07, 2013 at 20:26 UTC

    Using closures and modulo is one way. Define a function that returns a function reference which has a copy of the array reference and knows the last position. Every time you call this unnamed function you will get the next element of the cycle. Using array references will mean that any change of the array will be reflected in your cycling. If this is undesired use arrays instead.

    use strict; use warnings; my $stuff = [ 'a', 'b', 'c', 'd' ]; sub cyclist { my $array = shift; my $pos = -1; return sub { return $$array[++$pos % @$array]; } } my $cyclestuff = cyclist $stuff; print $cyclestuff->() for 1..30; print "\n"; $$stuff[0] = 'A'; print $cyclestuff->() for 1..30; print "\n";
Re: Is there a "better" way to cycle an array?
by Eily (Monsignor) on Sep 07, 2013 at 21:57 UTC

    Here is another way to do it, inspired by hdb's answer, but with a function that takes the array you want to cycle through as a parameter (instead of having a sub that remembers both the array and the associated position for each array). The positions are stored in a hash which uses the stringified arrayref as a key.

    I've added some extra food for thoughts: using *cycle to be able to call the sub without the -> notation. blessing one arrayref into the current package to call the function as a method. Or, using the + prototype on a function that calls $sub->() to be able to call cycle2 on either an array ref or a literal array.

    use strict; use warnings; sub makeClosure { my %pos; # hash that will store the positions of all the array +s return sub { my $array = shift; $pos{$array} //= -1; # To avoid "Use of uninit +ialized value" $pos{$array} = (($pos{$array})+1) % @$array; return $array->[$pos{$array}]; } } my $sub = makeClosure; my $array1 =[1..4]; my $array2 = ['a'..'c']; $\ = $/; print $sub->($array1) for 1..3; print $sub->($array2) for 1..2; print $sub->($array1) for 1..3; print $sub->($array2) for 1..2; print "__________"; print "Unblessed reference : ", $array2; *cycle = $sub; # &main::cycle is now our closure use subs 'cycle'; # Tell Perl that there is a function called cycle, b +ecause there was no 'sub cycle'. # This is only useful if you want to call cycle with +out parenthesis bless $array2; # $array2 is now an object of class 'main' print "Blessed reference : ", $array2; print cycle $array2; # this works because of use subs print $array2->cycle() for 1..3; # calls main::cycle on object $array2 sub cycle2(+) { my $array = shift; $sub->($array); } print "__________"; print cycle2 $array1; my @array = qw/Tic Tac/; print cycle2 @array for 1..4;
    1 2 3 a b 4 1 2 c a __________ Unblessed reference : ARRAY(0x10eb6a0) Blessed reference : main=ARRAY(0x10eb6a0) a b c a __________ 3 Tic Tac Tic Tac
    As you may notice, when blessing a reference, its stringified version changes to include the class. So in this case, the cycling starts back at position 0.

Re: Is there a "better" way to cycle an array?
by kcott (Archbishop) on Sep 08, 2013 at 06:23 UTC

    G'day rje,

    Here's a solution using state, which therefore requires 5.10.0 or later:

    $ perl -Mstrict -Mwarnings -E ' sub cycle { state %i; no warnings "uninitialized"; $_[0]->[($i{$_[0]} % @{$_[0]}, ++$i{$_[0]})[0]]; } my $a2d = ["a" .. "d"]; my $e2h = ["e" .. "h"]; for (1..3) { print cycle($a2d) for 1..3; print cycle($e2h) for 1..3; } ' abcefgdabhefcdaghe

    For earlier Perls, this uses a closure and retains the same level of privacy for %i:

    $ perl -Mstrict -Mwarnings -E ' { my %i; sub cycle { no warnings "uninitialized"; $_[0]->[($i{$_[0]} % @{$_[0]}, ++$i{$_[0]})[0]]; } } my $a2d = ["a" .. "d"]; my $e2h = ["e" .. "h"]; for (1..3) { print cycle($a2d) for 1..3; print cycle($e2h) for 1..3; } ' abcefgdabhefcdaghe

    If you only ever want to cycle through a single array (I wasn't sure if that's what your OP was suggesting), then this would be faster (but has distinct limitations):

    $ perl -Mstrict -Mwarnings -E ' sub cycle { state $i = -1; $_[0]->[++$i, $i %= @{$_[0]}]; } my $stuff = ["a" .. "d"]; print cycle($stuff) for 1..30; ' abcdabcdabcdabcdabcdabcdabcdab

    Obviously, that can also be modified for earlier Perls in the same manner as the previous example.

    -- Ken

Re: Is there a "better" way to cycle an array?
by Preceptor (Deacon) on Sep 09, 2013 at 11:08 UTC

    Not that this is any more correct than any other suggestion, but in the past I've used something like:

    my @stuff = ( "a", "b", "c", "d" ); { my @current_list; sub cycle { @current_list = @stuff unless @current_list; return pop ( @current_list ); } }
Re: Is there a "better" way to cycle an array?
by Anonymous Monk on Sep 08, 2013 at 16:02 UTC
    There's also many modules on CPAN. Just search for "cycle".

Log In?

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

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (7)
As of 2023-12-06 17:12 GMT
Find Nodes?
    Voting Booth?
    What's your preferred 'use VERSION' for new CPAN modules in 2023?

    Results (31 votes). Check out past polls.