http://qs321.pair.com?node_id=83418

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

I currently have a data structure where I have a bunch of (roughly) key/value pairs. However, they are NOT hashable, both because keys are not unique, and because order is important.

Currently, I have lots of accessor functions where I do stuff like this to extract the values, but not the keys.

my @subs; my @subdata = @{$self->{_subfields}}; while ( @subdata ) { my $code = shift @subdata; my $text = shift @subdata; push( @subs, $text ); } # for # And then I can do stuff with @subs
Any suggestions on a better idiom? I really don't dig having to make a copy of the list to jump thru it. I've thought about hitting the 2nd, 4th, 6th, etc elements, but that seems to non-Perl-like.

Suggestions welcomed...

Thanks,
xoxo,
Andy

%_=split/;/,".;;n;u;e;ot;t;her;c; ".   #   Andy Lester
'Perl ;@; a;a;j;m;er;y;t;p;n;d;s;o;'.  #   http://petdance.com
"hack";print map delete$_{$_},split//,q<   andy@petdance.com   >

Replies are listed 'Best First'.
Re: Idiom for looping thru key/value pairs
by MeowChow (Vicar) on May 26, 2001 at 00:36 UTC
    <shameless_plug> Use iter. </shameless_plug>

    You can also idiomize your loop a bit:

    my ($code, $text) = splice @subdata, 0, 2;
    Also, check out this discussion.
       MeowChow                                   
                   s aamecha.s a..a\u$&owag.print
(tye)Re: Idiom for looping thru key/value pairs
by tye (Sage) on May 26, 2001 at 00:33 UTC
    my @subs= @$_[ map { 2*$_ } 0..$#$_/2 ] for $self->{_subfields}; my @sups= @$_[ map { 1+2*$_ } 0..$#$_/2 ] for $self->{_subfields};
            - tye (but my friends call me "Tye")
Re: Idiom for looping thru key/value pairs
by Masem (Monsignor) on May 26, 2001 at 00:40 UTC
    It might be easier to work with the data stored as an array of arrays. eg:
    my @data = ( [ 'a', 2 ], [ 'b', 5 ], [ 'b', 2 ], [ 'c', 8 ] ); # Keep data sorted on keys. @data = sort { $a->[0] cmp $b->[0] } @data; # Get list of values in order @values = map { $_->[1] } @data; # Operate on values, in order { $_->[1] =* 2 } foreach ( @data );

    Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
      I did originally have them as lists of lists, but flattening out into the "array of key/value pairs" idea sped up my constructor by about 10%, which was pretty significant.

      xoxo,
      Andy

      %_=split/;/,".;;n;u;e;ot;t;her;c; ".   #   Andy Lester
      'Perl ;@; a;a;j;m;er;y;t;p;n;d;s;o;'.  #   http://petdance.com
      "hack";print map delete$_{$_},split//,q<   andy@petdance.com   >
      

        Since you already have an object, I'd really encourage you to consider separating them into two separate parallel lists!

                - tye (but my friends call me "Tye")
Re: Idiom for looping thru key/value pairs
by tinman (Curate) on May 26, 2001 at 00:36 UTC

    Please correct me if I'm wrong, but perhaps you could use splice ?

    #!/usr/bin/perl -w use strict; my @list = ('1','val','2','val2','1','val3'); while((my $first, my $last) = splice(@list,0,2)) { print "$first -> $last \n"; }

    HTH

      The problem with splice is that it would chop the values out of the original array, and he doesn't want to use a dupe.

      This might be the time to use old-timey C-style for:

      my @list = ('1','val','2','val2','1','val3'); my $self = { _subfields => [@list] }; my @subs; for (my $index = 1; $index < @{$self->{_subfields}}; $index+=2) { push @subs, ${$self->{_subfields}}[$index]; } # waa-la: print $_,"\n" foreach (@subs);
      It's ugly, but it avoids the duplication. For the cost of a scalar you could always use
      my $aryref = $self->{_subfields};
      instead.

      -- Frag.

(dkubb) Re: (2) Idiom for looping thru key/value pairs
by dkubb (Deacon) on May 27, 2001 at 07:19 UTC

    The CPAN module Tie::DxHash will allow you to make a hash that retains the order the keys were entered, and allows duplicate keys as well.

    Here's a short example to show you how simple it's usage is:

    use Tie::DxHash; tie my %hash, 'Tie::DxHash'; %hash = ( a => 1, a => 2, b => 2, b => 3, );

    An interesting thing to note is that in the assignment to %hash the duplicate keys are not clobbered.

    You can then iterate over this hash using while/each or a foreach/keys, and the elements will be returned in the order they were inserted into the hash.

Re: Idiom for looping thru key/value pairs
by runrig (Abbot) on May 26, 2001 at 00:33 UTC
    If you have duplicate keys, why not use a hash of arrays?

    Doh!- forgot you wanted them ordered. Still, maybe a tied hash?