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

I have a hash of arrays and I want to be able to push data directly to the array contained within a given hash. I'm tyring to travers a file and based in a known tag in a line contine to add info line by line just changing the hash tags as I find key indicators File example.
[%TAG] data data data data [%TAG] data data [%TAG] data data [%TAG] data data data data Code fragement: Code does not work. if(/^\%/) { $tag=$line[0]; shift @line; $full = join ' ', @line; push $outer{$tag}@desclines, $full; }

Replies are listed 'Best First'.
Re: push to a hash of arrays
by Aristotle (Chancellor) on Dec 20, 2002 at 15:31 UTC

    And to complete derby's point: you don't need to test whether the hash entry exists - if it doesn't, Perl will automagically create an anonymous array and store it in there for you.

    It looks like you're not using strict and warnings - get in the habit of doing so. Yes, they're annoying at first, but they'll catch a lot of very simple but very hard to find mistakes (such as typos in variable names) for you. Also, note that shift returns the element it just removed from the front of the array, so you can directly assign it to $tag rather than doing the whole thing in two steps.

    That snippet would then look like so:

    if(/^\%/) { my $tag = shift @line; my $full = join ' ', @line; push @{$outer{$tag}}, $full; }
    Also, you might want to have another look at how Perl treats lists - you needn't touch @line at all.
    if(/^\%/) { my $full = join ' ', @line[1 .. $#line]; push @{$outer{$line[0]}}, $full; }

    $#array_var is the index of the last element in the @array_var variable.

    And now it's pretty obvious we need no extra variable to store the joined string in:

    if(/^\%/) { push @{$outer{$line[0]}}, join ' ', @line[1 .. $#line]; }
    I like to avoid as many nesting blocks wherever possible, so I'd take another step: push @{$outer{$line[0]}}, join ' ', @line[1 .. $#line] if /^\%/;

    Makeshifts last the longest.

Re: push to a hash of arrays
by gjb (Vicar) on Dec 20, 2002 at 15:19 UTC

    The code below should do the trick.

    use strict; use warnings; my $key; my %data; while (<DATA>) { chomp($_); my @data = split(/\s+/, $_); if ($data[0] =~ /^\%(.+)/) { $key = $1; shift(@data); } push(@{$data{$key}}, @data); } foreach my $key (keys %data) { print "$key: ", join(", ", @{$data{$key}}), "\n"; } __DATA__ %key1 data1 data2 %key2 data3 data4 data5 %key3 data6 data7 data8 data9

    Hope this helps, -gjb-

      Thanks Monks here is what I ended up with
      #!/usr/local/bin/perl open(RULES, $ARGV[0]) or die "Failed Open : $!\n"; #%GROUP ALSMS_consolidation #%CHPTR ALSMS_consolidation #%SBCH1 WORK_switch_id print "group ^ chapter ^ name ^ description\n"; while(<RULES>) { chomp; @line=split /\s+/; if(/^\%SBCH1/) { foreach my $key (keys %outer) { print "$key: ", join(", ", @{$outer{$key}}), "\n\n"; } %outer=(); } if(/^\%GROUP/) { $group=$line[1]; } if(/^\%CHPTR/) { $chapter=$line[1]; } else { if(/^\%/) { $tag=$line[0]; shift @line; $full = join ' ', @line; push(@{$outer{$tag}}, $full) } } }

        If you are given just filenames on the command line, you can simply use <> and Perl will read them for you. That will also work if you get your input from STDIN, so it's the most flexible way of handling things - and it's less code for you to write too.

        Here's one stab at cleaning this up:

        #!/usr/bin/perl -w use strict; #%GROUP ALSMS_consolidation #%CHPTR ALSMS_consolidation #%SBCH1 WORK_switch_id print "group ^ chapter ^ name ^ description\n"; my (%outer, $group, $chapter); while(<>) { chomp; @line = split /\s+/; if(/^\%SBCH1/) { my ($key, $value); print "$key: ", join(", ", @$value), "\n\n" while ($key, $value) = each %outer %outer=(); } elsif(/^\%GROUP/) { $group = $line[1]; } elsif(/^\%CHPTR/) { $chapter = $line[1]; } elsif(/^\%/) { push @{$outer{$line[0]}}, join ' ', @line[1 .. $#line]; } }
        At this point I'd move all the cases into an hash and let the regex engine pick out the right one for me. The array formerly stored in @line is now passed through the parameter list. I put these all into an array first because the order of keys is important for the regex built from it.
        #!/usr/bin/perl -w use strict; #%GROUP ALSMS_consolidation #%CHPTR ALSMS_consolidation #%SBCH1 WORK_switch_id my (%outer, $group, $chapter); my @handler = ( SBCH1 => sub { my ($key, $value); print "$key: ", join(", ", @$value), "\n\n" while ($key, $value) = each %outer %outer=(); }, GROUP => sub { $group = $_[1] }, CHPTR => sub { $chapter = $_[1] }, "" => sub { push @{$outer{$_[0]}}, join ' ', @_[1 .. $#line] }, ); my %handler = @handler; my $rx = do { my @key; while(@handler) { # take two element from front, throw away the second my ($key) = splice @handler, 0, 2; push @key, $key; } join '|', @key; }; $rx = qr/^\%($rx)/; print "group ^ chapter ^ name ^ description\n"; while(<>) { if(/$rx/) { chomp; $handler{$1}->(split /\s+/); } }

        Note that now, the loop doesn't chomp and split the string unless it actually found a handled tag at the front of the string - it's always good not to do any more work than necessary.

        It does look more complicated now - and indeed, if you really, really won't ever need any more than you originally had, it may not even be worth it. But if you ever have to extend your code, it is quite helpful to work with hashes of subroutines rather than use huge if/elsif blocks.

        Makeshifts last the longest.

Re: push to a hash of arrays
by derby (Abbot) on Dec 20, 2002 at 15:17 UTC
    I'm not sure what the desclines is, but if you have a hash and each entry in the hash is an array ref, then to push onto it you would do this:

    push @{$hash{$tag}}, $value;

    -derby

Re: push to a hash of arrays
by BrowserUk (Patriarch) on Dec 20, 2002 at 15:20 UTC

    First your regex doesn't match your tags. You need

    /^\[%/

    at least.

    To push an item onto an array referenced by a hash element you need something like

    push @{$outer{$tag}}, $full;,

    but your snippet is so isolated that I am not sure where "desclines" comes from or is doing, so that may not be quite right.

    There's no way to comment sensibly on the rest of the snippet as in isolation at least, it doesn't make much sense.

    Maybe that will help.


    Examine what is said, not who speaks.

Re: push to a hash of arrays
by mfriedman (Monk) on Dec 20, 2002 at 15:18 UTC
    What is @desclines and where does it come from? To push to an array reference, you must dereference it. I'm assuming the array reference in question is $outer{$tag}. So do this:

    push @{ $outer{$tag} }, $full;