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

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

Learned Monks,

I build a data structure as I parse a file. The gist of it is in the code below. I am using Perl 5.8.5 if that matters.

I go round and round. Sometimes I read information on one line and that sets the "context" for the data on the lines that immediately follow. When I want to actually store something, I'm left with a number of state variables to index to where I need to put the data. Instead, I hope to use this "context" to just set a pointer into the data structure that I assemble and keep this as one state variable.

I then hope to collect the data into an anonymous array aliased by the hashref pointer.

Everything looks okay at first. My first call to "Dumper" prints what I expect. When I get to the end however, I see that the data was never placed into the main data structure. If I make multiple passes that should "push" to the same array, they don't show up either.

My pointer/alias is not working...

use strict; use warnings; use Data::Dumper; my $data; my $fsm = 1; my $m = ''; my $i = ''; while(<DATA>) { our $pointer; local *pointer; chomp; if (/^INSTANCE:\s+(\S+)/) { $m = ''; $i = $1; } elsif (/^MODULE:\s+(\S+)/) { $m = $1; $i = ''; } elsif (/^Fsm\s+(\S+)/) { next if !$fsm; if ($m) { *pointer = \$data->{'module'}->{$m}->{$1}; } else { *pointer = \$data->{'instance'}->{$i}->{$1}; } } elsif (/^State\s+(\S+)/) { next if !$fsm; push(@{ $pointer->{'state'} }, "$1"); print "A: " . Dumper( $pointer ); #DEBUG } elsif (/^Transition\s+(\S+)/) { next if !$fsm; push(@{ $pointer->{'transition'} }, "$1"); print "B: " . Dumper( $pointer ); #DEBUG } } print "C: " . Dumper($data); __DATA__ INSTANCE: i_name Fsm f_name State s_name1 # Transition t_name # State s_name2
Output looks like this...

A: $VAR1 = { 'state' => [ 's_name1' ] }; C: $VAR1 = { 'instance' => { 'i_name' => { 'f_name' => undef } } };
Given the data above, the final expected data should look like this...

C: $VAR1 = { 'instance' => { 'i_name' => { 'f_name' => { 'state' => [ 's_name1 +' ] } } } };
If you uncomment the commented lines in the __DATA__ section, then I'd expect data like this...

C: $VAR1 = { 'instance' => { 'i_name' => { 'f_name' => { 'state' => [ 's_name1 +' 's_name2 +' ] } 'transition' => [ 't_n +ame' ] } } } };
Re: Grouping an array of hashrefs by similar key values was helpful. Searching for "*" is a tricky thing to do!

Your help is always appreciated!

Update: added the "expected results"

Replies are listed 'Best First'.
Re: pointer/alias question
by GrandFather (Saint) on Dec 02, 2010 at 00:38 UTC

    Pointer think is complicating your life. Instead you need to wear a Perl hat and think about references. Consider:

    use strict; use warnings; use Data::Dumper; my $data; my $fsm = 1; my $module = ''; my $instance = ''; my $entryRef; while (<DATA>) { chomp; if (/^INSTANCE:\s+(\S+)/) { $module = ''; $instance = $1; } elsif (/^MODULE:\s+(\S+)/) { $module = $1; $instance = ''; } elsif (/^Fsm\s+(\S+)/) { next if !$fsm; if ($module) { $entryRef = $data->{'module'}{$module}{$1} ||= {}; } else { $entryRef = $data->{'instance'}{$instance}{$1} ||= {}; } } elsif (/^State\s+(\S+)/) { next if !$fsm; push(@{$entryRef->{'state'}}, "$1"); } elsif (/^Transition\s+(\S+)/) { next if !$fsm; push(@{$entryRef->{'transition'}}, "$1"); } } print Dumper($data); __DATA__ INSTANCE: i_name Fsm f_name State s_name1 Transition t_name State s_name2

    Prints:

    $VAR1 = { 'instance' => { 'i_name' => { 'f_name' => { 'transition' => +[ + 't_name' +], 'state' => [ 's_ +name1', 's_ +name2' ] } } } };

    Note in particular that where $entryRef is assigned values ...{$1} ||= {}; is used to assign a hash to the data strcture if there isn't one already.

    True laziness is hard work
      Thanks grandfather!

      Got it. Simple. That'll save me quite a few lines. That's the solution for me.

      I'm sure I tried something similar, but without the ||{} it was getting an undef reference, which obviously doesn't work.

      Anonymous Monk suggests that my use of local was killing my pointer idea it seems.

      Out of curiosity, since I looked all over the Perl docs perlsyn, perlvar, etc. Where is the *pointer-thing documented?

      Cheers!

        toolic gave the link, but the confusion is that it isn't a "pointer thing", it's a "type glob thing". Perl doesn't have pointers. It has references, but that's not the same thing - really, it's not the same thing! Perl uses reference counted memory management and that just doesn't work well with pointers. It does work well with generally not having to worry about how memory is handled though.

        True laziness is hard work
Re: pointer/alias question
by toolic (Bishop) on Dec 01, 2010 at 19:56 UTC
    You didn't show us what your expected data looks like, but maybe you should try a different data structure (perldsc):
    use strict; use warnings; use Data::Dumper; my %data; my $fsm = 1; my $m = ''; my $i = ''; while(<DATA>) { chomp; if (/^INSTANCE:\s+(\S+)/) { $m = ''; $i = $1; } elsif (/^MODULE:\s+(\S+)/) { $m = $1; $i = ''; } elsif (/^Fsm\s+(\S+)/) { next if !$fsm; if ($m) { $data{'module'}->{$m} = $1; } else { $data{'instance'}->{$i} = $1; } } elsif (/^State\s+(\S+)/) { next if !$fsm; $data{'state'} = $1; } elsif (/^Transition\s+(\S+)/) { next if !$fsm; $data{'transition'} = $1; } } print Dumper(\%data); __DATA__ INSTANCE: i_name Fsm f_name State s_name1 # Transition t_name # State s_name2

    Prints:

    $VAR1 = { 'instance' => { 'i_name' => 'f_name' }, 'state' => 's_name1' };
      Sorry to çôñfüsè you. I've updated the original node with my expected data...

      In the real dataset, I've got arrays of "states" and "instances" that make the sort of change you propose infeasible. Maybe you meant something else, but I can't flatten it like that. The data would step on itself.

Re: pointer/alias question
by Anonymous Monk on Dec 02, 2010 at 00:27 UTC
    Consider the following:

    # perl -lwE'for (reverse 0..2) { our $foo; local *foo; $foo=$_ if $_==1; print $foo // "undef"; }' undef 1 undef

    # perl -lwE'for (reverse 0..2) { our $foo; # local *foo; $foo=$_ if $_==1; print $foo // "undef"; }' undef 1 1