Plexus Plexus - 1 month ago 9
Perl Question

object oriented perl, set hash values

I have a class:

sub new {

my ($class, $name) = @_;

my $self = {
_ids => [],
_devices => {}
};

bless ($self, $class);

return $self;

}


I pass the subroutine an array, and say:

sub Importids {

my ($self, $id_ref) = @_;

foreach $id (@{$id_ref})
{
push(@{$self->{_ids}}, $id);
}

}


I further would like to add into this function that I initialize the hash, but am having a hell of a time doing this.
In the end I want that my hash looks like this for a number of ids:

_device --> id1|
--> status --> 0/1
id2|
--> status --> 0/1


where the id is the key.

I tried to do this in the function as follows:

sub Importids {

my ($self, $id_ref) = @_;

foreach $id (@{$id_ref})
{
push(@{$self->{_ids}}, $id);
}

foreach my $id_value(@{$self->{_ids}})
{
$self->{_devices}{$id_value}{'status'} = 0;
}

}


but when I go to check the contents as follows, it returns only the hex dump of the hashes

for my $hash_key (keys %{$self->{_devices}})
{
print $self->{_devices}{$hash_key};
#print keys % {$self->_devices}};
}


gives:

HASH(0x....)
HASH(0x....)
...
...
HASH(0x....)


however, when I try:

for my $hash_key (keys %{$self->{_devices}})
{
print $self->{_devices}->{$hash_key}->{'status'};

}


I get what I want:

0
0
...
0


How should I access the keys and also add an additional field such as status2''?

Answer

When you are setting up the hash, you iterate over the array with id's, using $id_value as the variable. Then you assign it to a non-existent key

$self->{_devices}{ids} = $id_value;  # what is 'ids' string?

Thus a key 'ids' will be added and each assignment will overwrite the previous. Also, note that this way the $id_value (would) become values, not keys, contrary to what the question states.

What is supposed to be in $self->{_devices} ? If keys are id's, to be "initialized", say to 0

foreach my $id (@$id_ref)
{
    push @{$self->{_ids}}, $id;
    $self->{_devices}{$id} = 0;
}

There are more compact and clearer ways but let's first clarify if this is intended.


Update to clarification in comments

The objective is to construct a structure

_device --> id1 --> status --> 0/1
            id2 --> status --> 0/1
            ...

We also want to be able to read/change values, and add another kind of 'status'. Let us first forget about classes and build this data structure. See tutorial perlreftut and the data-structures cookbook perldsc.

In Perl we can use a hash reference for this, with further nested hash references

my $hashref = { 
    'id1' => { 
        'status'  => 0,
        'status2' => 0
    }, 
    'id2' => { 
        'status'  => 0,
        'status2' => 0,
    },
    # ...
};

The anonymous hash { status => 0, status2 => 0 } is assigned as a value to keys 'id1', 'id2'. It is often loosely called hashref, meaning a reference to a hash (what is strictly speaking \%hash). A hashref is a scalar, a single value, so it can be assigned to a key. This is how we can build nested (complex) data structures, using references. They are much like pointers.

We can populate it in code like so

use warnings 'all';
use strict;
use Data::Dumper;  # to see our structures

my @ids = qw(id1 id2 id3);

my $hashref;

foreach my $id (@ids) {
    foreach my $stat ('status', 'status2') {
        $hashref->{$id}{$stat} = 0;
    }
}

print Dumper($hashref);

The keys $id are added to the hash (autovivified) once encountered, and so are the keys 'status' in the nested hash(ref), and these are assigned the value 0. We change the value simply by

$hashref->{$id}{'status'} = 1;

We can add yet another 'status' the same way, $hashref->{$id}{'status3'} = 0.

Back to the class. We use only one 'status' field. Note that no error checking is done.

sub Importids 
{
    my ($self, $id_ref) = @_;

    foreach my $id (@{$id_ref}) 
    {    
        push @{$self->{_ids}}, $id;
        $self->{_devices}{$id}{'status'} = 0;
    }
    return 1;
}

How do we change values for given ids? Let's first come up with interface. With ids in $id variables and their new values-to-be in $val's, we can imagine a call

$obj->status( { $id1 => $val1, $id2 => $val2 } );         
# Or, with values-to-ids built during processing,
$obj->status( $new_vals_for_ids_hashref );

Then here is a method for it

sub status 
{
    my ($self, $rstat) = @_; 

    foreach my $id (keys %$rstat) {
        $self->{_devices}{$id} = $rstat->{$id};
    }
    return 1;
}

This adds new ids, if they are not already there, or overwrites existing ones. We can make this same sub be the getter as well -- if nothing is passed in

sub status 
{
    my ($self, $rstat) = @_; 

    if (not defined $rstat) { return $self->{_devices} }
    else {
        foreach my $id (keys %$rstat) {
            $self->{_devices}{$id} = $rstat->{$id};
        }
    }
    return 1;
}

with intended use my $devices = $obj->status();. You can add 'status' fields similarly,

$obj->add_status( [ qw(status2 status3) ] );

where [ ... ] is an anonymous array, with a method

sub add_status 
{
    my ($self, $rstats) = @_;
    foreach my $stat (@$rstats) 
    {
        foreach my $id (@{$self->{_ids}}) 
        {
            $self->{_devices}{$id}{$stat} = 0;
        }
    }
    return 1;
}

Note that there are more compact ways to work with lists of keys/values.