http://www.perlmonks.org?node_id=11135194

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

Version 1 - works fine
package SomeMod; sub new { my $obj = bless{ Order=>[], Items=>{}, Config=>{}, }, shift; while ( @_ ) { my ( $key, $val ) = ( shift, shift ) ; push @{$obj->{Order}}, $key; $obj->{Items}{$key} = $val; } return $obj } 1; ### main use SomeMod; my %Items_Key_Value_pairs = (A => "Apple", B => "Banana", C => "Cat"); my $obj = SomeMod -> new ( %Items_Key_Value_pairs ) ;

Version 2 - I want to change the constructor interface
package SomeMod; sub new { my $obj = bless{ Order=>[], Items=>{}, Config=>{}, }, shift; my %arg = @_ ; while ( @{$arg{Items}} ) { # BLOW! 'Not an ARRAY reference' my ( $key, $val ) = ( shift, shift ) ; push @{$obj->{Order}}, $key; $obj->{Items}{$key} = $val; } # ... deal with config ... return $obj } 1; ### main use SomeMod; my %Items_Key_Value_pairs = (A => "Apple", B => "Banana", C => "Cat"); my %some_config_param = (); my $obj = SomeMod -> new ( Items => { %Items_Key_Value_pairs } , Config => { %some_config_param } ) ;

I know that's not an ARRAY ref, but since I want to learn the appear order, can I do anything to access this HASH ref like other ordinary array ( they still a list right) ?

Or, when I do Items => {... }, this list already "digested" in a hash form thus not feasible to access like an array anymore?

Though I already have a work around by using Items => [ A => "Apple", B => "Banana" ... ], I still interest to know if I can force to access a Hash ref like Array, same as I did in Version 1

Thank you very much

Replies are listed 'Best First'.
Re: Force access a Hash ref as Array ref
by tobyink (Canon) on Jul 20, 2021 at 06:45 UTC

    "I know that's not an ARRAY ref, but since I want to learn the appear order"

    Hashes in Perl aren't ordered. As soon as you put things in a hash, whatever order the keys and values were listed in has already gone.

    The workaround you describe is the best option. Passing a tied hash which maintains order is also a possibility, but results in more work for your caller, and slower performance.

Re: Force access a Hash ref as Array ref
by haj (Vicar) on Jul 20, 2021 at 06:52 UTC

    Let me start with the second question:

    Or, when I do Items => {... }, this list already "digested" in a hash form thus not feasible to access like an array anymore?

    That's correct. As soon as Perl has processed it into a hash reference, the original order of key/value pairs is lost. This gives the answer to the first question:

    I know that's not an ARRAY ref, but since I want to learn the appear order, can I do anything to access this HASH ref like other ordinary array ( they still a list right) ?

    A hash is neither an array nor a list. You can convert a hash to an array like my @array = %hash, but then you get random ordering of the key/value pairs.

    So, if you want to retain information about the ordering, then you can:

    • either process the key/value pairs while they are still in the caller's order in @_, before converting them to a hash,
    • or use some CPAN module like Hash::Ordered which creates "hashes with ordered keys".
Re: Force access a Hash ref as Array ref
by GrandFather (Saint) on Jul 20, 2021 at 07:00 UTC
    sub new { my $obj = bless{ Order => [], Items => {}, Config => {}, }, shift @_; my (undef, $itemValues) = @_ ; my @asList = %$itemValues; while (@asList) { my ($key, $val) = splice @asList, 0, 2;
    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond

      This appears to change the signature of new() to SomeMod->new( undef, { name => value ... } ). Not to mention that it loses the order of the arguments.

      OTOH splice @asList, 0, 2 is a reasonable alternative to ( shift @asList, shift @asList ).

Re: Force access a Hash ref as Array ref
by exilepanda (Friar) on Jul 20, 2021 at 07:24 UTC
    My question has been answered, and lesson learned. Thank you very much everyone.
Re: Force access a Hash ref as Array ref
by perlfan (Vicar) on Jul 20, 2021 at 22:17 UTC
    Maybe you just want something like this;
    package Foo; sub new { my $pkg = shift; my %self = @_; my $self = bless \%self, $pkg; # do stuff to $self (now a __PACKAGE__) return $self; } 1;
    Then,
    use Data::Dumper (); my $foo = Foo->new(a => 1, b => 2, c => [qw/apple banana grape/]); print Data::Dumper::Dumper(\$foo)
    Output:
    $VAR1 = \bless( { 'b' => 2, 'c' => [ 'apple', 'banana', 'grape' ], 'a' => 1 }, 'Foo' );
    If you want the basis for a Foo to be an array ref, then,
    package Foo; sub new { my $pkg = shift; my @self = @_; my $self = bless \@self, $pkg; # do stuff to $self (now a __PACKAGE__) return $self; } 1;
    Output:
    $VAR1 = \bless( [ 'a', 1, 'b', 2, 'c', [ 'apple', 'banana', 'grape' ] ], 'Foo' );
    Note, => is taken as a comma when an array is on the LHS of the assignment (=). Hashes can be treated as even-sized arrays - or arrays of tuples (i.e., key => val).
A reply falls below the community's threshold of quality. You may see it by logging in.