Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things

setting and retrieving sub-hash elements in an object

by Amoe (Friar)
on Dec 17, 2001 at 01:52 UTC ( #132400=perlquestion: print w/ replies, xml ) Need Help??
Amoe has asked for the wisdom of the Perl Monks concerning the following question:

Hey monks. I'm having a stab at writing my first OO module. All is going well, and I'm enjoying it, but I have stumbled upon a problem. The underlying hash of my object has a sub-hash, so to speak, called 'options'. (When I bless it, it's like this:)

bless {foo => 'bar', options => {anoption => 1, anotheroption => 'foo', option3 => 'barbar'}, bar => 'maz'}, $class;

The options hash has a lot of keys, so I thought I could write a subroutine to return an element. Here's my try:

sub option { shift->{options}->{shift()} }

All goes well, until I have to modify a value in the options hash. I'm sure there's a very simple way to do this. I tried this:

sub option { my $self = shift; if (scalar @_ == 1) { return $self->{options}->{shift()}; } else { my ($key, $value) = @_; $self->{options}->{$key} = $value; } }
This works, but looks cumbersome. There has to be an easier way to do it. I think I need to be clearer on hash assignment in general, as well. I always thought that you say this:

%hash = @array

and the pairs described by @array would be added to %hash, instead of wiping the hash and then adding them. This doesn't happen, and the only way I can think of accomplishing the same thing is to divide the array into two array based on modulo 2, to get the effective keys and values, then using a hash slice to assign them. Again, very cumbersome. I'm sure many OO modules have done this, but I searched for examples in vain. An example, preferably supporting changing of more than one element, would be great.

my one true love

Comment on setting and retrieving sub-hash elements in an object
Select or Download Code
(jeffa) Re: setting and retrieving sub-hash elements in an object
by jeffa (Chancellor) on Dec 17, 2001 at 02:03 UTC
    Cumbersome, yes - but it is good style. If you don't really care about others understanding your code (which you should), you could opt for something like:
    sub option { my ($self,$arg,$value) = @_; return $self->{options}->{$arg} unless $value; $self->{options}->{$arg} = $value; }
    A bit more consise, but at what gain?

    For your second question - sounds like you want a hash slice:

    my %hash; @hash{('a'..'d')} = (1..4);
    UPDATE: nope, that is not what you want - see Masem's answer.

    UPDATE (about an hour of reflection):

    To elaborate more - i would avoid using shift like you do. I have been known to do that from time to time, but it is sheer laziness on my behalf when i do.

    For serious code, i really tend to use:

    sub foo { my ($self) = @_; }
    in case i need to (and usually do) supply more arguments. There are exceptions, such as default values or attaching comments to each argument, but for the most part....

    The other item is lining up data structures correctly:

    sub new { my ($class) = @_; my $self = { foo => 'bar', bar => 'maz', options => { anoption => 1, anotheroption => 'foo', option3 => 'barbar', }, }; return bless $self, $class; }
    A lot easier for the eyes to parse! You can indent pretty much however you want, mine is just one way, but always remain consistent in how you do it.


    (the triplet paradiddle)
Re: setting and retrieving sub-hash elements in an object
by Masem (Monsignor) on Dec 17, 2001 at 02:04 UTC
    It's probably easier to do the following to decide between setting and getting:
    sub option { my ( $self, $key, $value ) = @_; if ( defined( $value ) ) { $self->{ options }->{ $key } = $value; } $self->{ options }->{ $key }; }
    Also, to add new items to a hash but otherwise key the has intact (assuming the array is even-sized) you can do:
    %hash = ( %hash, @array );

    Dr. Michael K. Neylon - || "You've left the lens cap of your mind on again, Pinky" - The Brain
    "I can see my house from here!"
    It's not what you know, but knowing how to find it if you don't know that's important

Re: setting and retrieving sub-hash elements in an object
by clintp (Curate) on Dec 17, 2001 at 02:05 UTC
    sub option { my $self=shift; die unless @_; if (@_ == 1) { return $self->{options}->{+shift}; } my %h=@_; @{$self->{options}}{keys %h}=values %h; }
    If you want to play with @_ as though it were a hash, I don't think you can get away without a temp hash (%h) without using an explicit loop (or two) like this:
    while(@_) { $self->{options}->{+shift}=shift; }
      Actually there is a way to avoid the explicit loop, but it is a little inefficient:
      sub option { my $self = shift; if (1 == @_) { return $self->{option}->{ shift(@_) }; } else { %{$self->{option}} = (%{$self->{option}}, @_); } }
Re: setting and retrieving sub-hash elements in an object
by Ven'Tatsu (Deacon) on Dec 17, 2001 at 02:55 UTC
    If you don't mind sacraficing compatability to do this than you can make your method 'lvalue'able. This only works with perl 5.6 and higher.
    sub option : lvalue { shift->{options}->{shift()} }

    Then you can get values as you normaly would and set values with
    $object->option('new') = 'bar';

    But if you do this please include use 5.6.0; at the top of your code.
Re: setting and retrieving sub-hash elements in an object
by Aristotle (Chancellor) on Dec 17, 2001 at 04:29 UTC
    This is really just the same thing as you do, but much briefer and IMHO a lot easier on the eyes:
    sub option { my ($self, $key, $value) = @_; return defined $value ? $self->{options}->{$key} = $value : $self->{options}->{$key}; }
    Merging hashes (or an array into a hash, which really is the same thing) unfortunately really is cumbersome in Perl since there's no built in way to do it. The %hash = (%hash, @array); method is agreeable, readability wise, but pretty inefficient - only use it in seldom used code and/or small sets of data. My solution:
    @hash{ map $array[$_], grep !($_ & 1), 0 .. $#array } = map $array[$_] +, grep $_ & 1, 0 .. $#array;
    The greps get a list of the array's indices and filter out the odd and the even ones, respectively, then the maps turn the index lists into values out of the array. The odd-index values set up a hash slice, the even-index values feed it. Of course this painful on both eyes and fingers. I would make a sub that I can safely stash it away:
    sub merge_hash { my ($hash, $array) = @_; @$hash{ map $array->[$_], grep !($_ & 1), 0 .. $#{$array} } = map +$array->[$_], grep $_ & 1, 0 .. $#{$array}; }
    This way I can just write down merge_hash(\%hash, \@array); and get an efficient and descriptive piece of code. You may even want to make it
    sub merge_hash (\%\@) { ## rest unchanged }
    so that you can simply write merge_hash(%hash, @array);. But beware, Perl will then insist on seeing a % and a @ even if you're passing a hash and an array reference - just like with the first parameter of push for example.

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://132400]
Approved by root
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others about the Monastery: (13)
As of 2014-11-28 17:22 GMT
Find Nodes?
    Voting Booth?

    My preferred Perl binaries come from:

    Results (199 votes), past polls