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

I'm somewhat of a noob at hashes of hashes so I need some help on this one. I have an array of hashes that look like this:
File Number => [ FirstNum => 1234, NextNum => 5678, ID => ID1, File => text.txt ]
I want to transform this into something that would look like:
1234 => [ NextNum => 5678, ID => ID1, File => text.txt ]
So basically here I wanted to shift the hash so that the first value of the original is now the main value. Hope this makes sense.

Replies are listed 'Best First'.
Re: Modifying order of a hash
by pjotrik (Friar) on Sep 25, 2008 at 19:22 UTC
    Beware, order of items in a hash may change. If you want to access them in some order, you have to sort them or access them by name.

    But what you have here in your examples are not hashes, these are array references. Don't get confused by the =>, that's just a fancy way of writing comma.

Re: Modifying order of a hash
by jethro (Monsignor) on Sep 25, 2008 at 19:38 UTC

    Hash of Hashes or Array of Hashes? Or Hash of Arrays which is what your syntax suggests?

    Lets say it is an array of hashes:

    my @new; foreach $fn (@old) { $new[$fn->{FirstNum}]= $fn; delete $fn->{FirstNum}; }

    If it is a hash of hashes:

    my %new; foreach $fn (keys %old) { $new{$fn->{FirstNum}}= $fn; delete $fn->{FirstNum}; }

    UPDATE: As an afterthought: Use Data::Dumper. It will show you the right syntax you should have used in your question. And also it will tell you which of the wildly different answers posted in this thread does what you need, if you use it to print the data structure before and after.

      Here is what code I have written with a small change to the input hash:
      my %file_attachments = ( '103496-1' => { 'CLVD' => '5678', 'COMP' => '1234', 'FD' + => '0010', 'File' => 'text.txt'}, ); print Dumper(%file_attachments); print "\n"; my %newfile_attachments; my $fn; my %newfile_attachments; foreach $fn (keys %file_attachments) { $newfile_attachments{$fn->{FirstNum}}= $fn; delete $fn->{FirstNum}; } print Dumper(%newfile_attachments); print "\n";
      and my output looks like:
      > perl hashofhash.pl "my" variable %newfile_attachments masks earlier declaration in same s +cope at hashofhash.pl line 17. $VAR1 = '103496-1'; $VAR2 = { 'FD' => '0010', 'COMP' => '1234', 'CLVD' => '5678', 'File' => 'text.txt' }; Can't use string ("103496-1") as a HASH ref while "strict refs" in use + at hashofhash.pl line 19.

        Ah yes, should have tested my code. The first error is because you defined %newfile_attachments twice. The second error goes away if you use this:

        foreach $fn (values %file_attachments) {

        In your example you don't have a key 'FirstNum' in the hash. Be prepared to see an error message "Use of uninitialized value..." because of this. If you have subhashes with no key 'FirstNum', then you have to test for that and do something sensible with those subhashes

        Btw, the output of Dumper looks much better if you use Dumper(\%x) instead of Dumper(%x)

Re: Modifying order of a hash
by karavelov (Monk) on Sep 25, 2008 at 19:41 UTC

    This will do the job:

    my %HoH = map { delete $_->{FirstNum} => $_ } @AoH;

    Be aware that you will loose data if there is duplication of "FirstNum" values.

Re: Modifying order of a hash
by apl (Monsignor) on Sep 25, 2008 at 19:26 UTC
    Untested...
    foreach my $first ( keys %( $hash1{$key} ) ) { $hash2{ $first }{NextNum} = $hash1{ $key }{ $first }{NextNum}; # etc. }
    (I should have used $FileNumber rather than $first; sorry.)

    Beware of possible loss of data if you have duplicate NextNum values in %hash1.

Re: Modifying order of a hash
by monaghan (Novice) on Sep 25, 2008 at 23:29 UTC
    Really appreciate all of this help. This site is always great to hit up if you run into trouble. Rather than starting a new thread, I figured I would just ask my similar question here: How would I transform this:
    my %hash = ( '103496-1' => [{ 'CLVD' => '5678', 'COMP' => '1234', 'FD +' => '0010', 'Files' => [{'File' => 'text.txt', 'hash' => 'a538346ad3 +485'},{'File' => 'text2.txt', 'hash' => '237d97892376a'}] }] );
    into this:
    my %newhash = ( '103496-1' => [{ 1234 => {'CLVD' => '5678', 'FD' => '001 +0', 'Files' => [{'File' => 'text.txt', 'hash' => 'a538346ad3485'},{'F +ile' => 'text2.txt', 'hash' => '237d97892376a'}] }] );
    Basically the only difference is that I don't want to remove the front value and simply shift 1234 outside of the original array. Any thoughts or suggestions?
      A question: What are you trying to do? Learn about HoHs ? Or solve a real problem? Solve several problems? Convert old data to some new format?
        The main reason is that its a real problem and I need to change the format of the input to make it easier to read into another program. Basically I have no control of the original structure of the data so thats why I need to transform it into this.
      This will do the job:
      my %newhash = map { $_ => [ map { { delete $_->{COMP} => $_ } } @{$hash{$_}} ] } keys %hash;

      EDIT:

      Or may be :

      my %newhash = map { $_ => [{ map { delete $_->{COMP} => $_ } @{$hash{$_}} }] } keys %hash;

      depending on what you want as final result: your example is vague because you have only one element in the inner array reference

      If it was my program I will be looking for result like:

      { '103496-1' => { '1234' => { 'FD' => '0010', 'CLVD' => '5678', 'Files' => [ { 'hash' => 'a538346ad3485', 'File' => 'text.txt' }, { 'hash' => '237d97892376a', 'File' => 'text2.txt' } ] } } };

      because you do not need an arrayref with just one element - hash reference. In which case use the following:

      my %newhash = map { $_ => { map { delete $_->{COMP} => $_ } @{$hash{$_}} } } keys %hash;
      Best regards
        This works great and I appreciate all of your help. I'm not that familiar with the map function but it has worked for me well so far. I'm now trying to incorporate multiple hashes rather than above where I have just one and it doesn't seem to work. I've wrapped a foreach statement around the code but would that mess up the map function?