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

i'm changing some code which used to loop through the key/value pairs in a hash. Now the source table has three pieces of data per line, and I don't know what the while/for syntax is. It's important to keep the data triplets clear in the source, not make three lists. Here's what I'm trying to do - many thanks for any advice.
# originally had paired data my %dynval = qw { obVersion 0x0204 obStat 0x04b4 obMode 0x0020 obRegulation 0x0014 obScheduler 0x0001 <lots more data ...> } # simple, can do this while((my $name, my $val) = each(%dynval)) { set_value( $name, $val ); } # new world with triplet data # must keep the data line-by-line for easy viewing my @dynval3 = qw ( Version obVersion 0x0204 Stat obStat 0x04b4 Mode obMode 0x0020 Regulation obRegulation 0x0014 Scheduler obScheduler 0x0001 ) while ?? for ?? (@dynval3) change_value($newname, $name, $val); }
should dynval3 be a list or is some other structure simpler?
thanks
alan

Replies are listed 'Best First'.
Re: loop thru 3-element array?
by tachyon (Chancellor) on Sep 27, 2004 at 23:37 UTC

    Just use an ordinary for loop to step through 3 elements at a time:

    for (my $i=0; $i<$#dynval3; $i+=3) { my ( $a, $b, $c ) = @dynval3[$i..$i+2]; # shorthand slice idiom i +nstead of $ary[$i], $ary[$i+1],... print "$a|$b|$c\n"; }

    But if you are creating this data structure an easier structure would be an array of arrays which is perls version of a list of lists.

    my @dynval3 = ( [ 'Version', 'obVersion', '0x0204' ], [ 'Stat', 'obStat', '0x04b4' ], [ 'Mode', 'obMode', '0x0020' ], [ 'Regulation', 'obRegulation', '0x0014' ], [ 'Scheduler', 'obScheduler', '0x0001' ], ); for my $ref(@dynval3) { # access examples print "@$ref\n"; my ( $a, $b, $c ) = @$ref; print "$a $b $c\n"; print "Field 1 is: ", $ref->[1], "\n\n"; }

    See perlman:perlref

    cheers

    tachyon

Re: loop thru 3-element array?
by borisz (Canon) on Sep 27, 2004 at 23:30 UTC
    you can use a Array with arrayrefs.
    my @dynval3 = ( [qw/Version obVersion 0x0204/], [qw/Stat obStat 0x04b4/], [qw/Mode obMode 0x0020/], [qw/Regulation obRegulation 0x0014/], [qw/Scheduler obScheduler 0x0001/], ); for my $aref (@dynval3) { my ( $newname, $name, $val ) = @$aref; change_value( $newname, $name, $val ); }
    If you really want to change the values inside the array @dynval3 use $aref->[0] = "New Val for the first element";
    Boris
Re: loop thru 3-element array?
by Limbic~Region (Chancellor) on Sep 27, 2004 at 23:43 UTC
    anadem,
    Perl6 is going to make this a lot easier :-)
    I have been fascinated with iterators lately, so this mildly tested code can easily be changed to iterate by any quantity specified.
    #!/usr/bin/perl use strict; use warnings; my @array = 0 .. 30; my $next = by_groups_of( 3, @array ); while ( my @group = $next->() ) { print "@group\n"; } sub by_groups_of { my $by = shift; return sub { () } if ! $by || $by =~ /\D/ || ! @_; my @list = @_; # Make a copy in case it changes my $end = $#list; my $pos = 0; my $done; return sub { return () if $done; my $stop = $pos + $by - 1 > $end ? $end : $pos + $by - 1; my @sub = @list[ $pos .. $stop ]; $pos += $by - 1; $done = 1 if $stop == $end; return @sub; } }

    Cheers - L~R

    Update: Also see this node if you need to have modifications to the looping variables modify the array as well

      Since you're making a separate copy of the list that gets passed in anyway, you could also use the splice trick that merlyn showed above, and get rid of all the arithmetic.

      sub by_groups_of { my $by = shift; return sub { () } if ! $by || $by =~ /\D/ || ! @_; my @list = @_; # here's the copy. we're safe to splice return sub { splice @list, 0, $by }; }

•Re: loop thru 3-element array?
by merlyn (Sage) on Sep 27, 2004 at 23:29 UTC

      The OP should be made aware that this example will destroy the contents of the @dynval3 array which may not be what is wanted. That is once you fix the typo in the splice as it is currently an infinite loop {chuckle}.

      cheers

      tachyon

      I do this like:
      my @tmp = @dynval3; while (my ($newname, $name, $val) = splice @tmp, 0, 3) { ... }
Re: loop thru 3-element array?
by ihb (Deacon) on Sep 28, 2004 at 00:05 UTC

    In my own version of List::Util I have this subroutine:

    sub group { my $n = shift; map [ @_[$_*$n .. $_*$n + $n - 1] ] => 0 .. $#_ / $n; }
    For your example, it would be used as
    for (group(3, @dynval3)) { my ($newname, $name, $val) = @$_; ... }

    ihb

    Read argumentation in its context!

Re: loop thru 3-element array?
by Anonymous Monk on Sep 28, 2004 at 10:10 UTC
    Just use the C-style for loop. Many people will automatically balk at it, but it's more flexible than the Perlish loop. This is a case where you want to use it:
    for (my $i=0; $i<@dynval3; $i+=3) { change_value(@dynval3[$i..$i+2]) }
    This assumes @dynval3 has a size that's a multitude of 3.
Re: loop thru 3-element array?
by TedPride (Priest) on Sep 28, 2004 at 06:26 UTC
    Since you probably want one key and two data elements, here's one possible solution:
    my %hash = ( 'Version' => ['obVersion', '0x0204'], 'Stat' => ['obStat', '0x04b4'], 'Mode' => ['obMode', '0x0020'], 'Regulation' => ['obRegulation', '0x0014'], 'Schedule' => ['obScheduler', '0x0001'] ); $hash{'Regulation'} = ['newReg', '0x0015']; my $p = $hash{'Schedule'}; @$p[0] = 'obUpdater'; $hash{'NewKey'} = ['This', 'Is', 'Four', 'Items']; delete($hash{'Mode'}); foreach (keys %hash) { $p = $hash{$_}; print "$_ => [" . join(' ', @$p) . "]\n"; }
    This is easily upgradable to as many data elements as you want, and accessing / updating / deleting items is simple.