waghso
waghso

Reputation: 595

Generate array from reference hash

I'm trying to generate array from hash reference, created by joining all keys of hashes with sorting. Consider I have dynamic hash reference like

my $hash_ref = {
          'A1' => {
                  'B2' => {
                          'C1' => {
                                  'D1' => {},
                                  'D2' => {},
                                  'D3' => {}
                                }
                        },
                  'B3' => {
                          'C1' => {
                                  'D2' => {},
                                  'D1' => {},
                                  'D3' => {}
                                }
                        },
                  'B1' => {
                          'C1' => {
                                  'D1' => {},
                                  'D2' => {}
                                }
                        }
                }
        };

how to create array from above hash like

@arr = qw/A1B1C1D1 A1B1C1D2 A1B2C1D1 ..../;

below is the code I tried(which is not working)

my $out = hash_walk($hash_ref);

say Dumper $out;

sub hash_walk {
    my $hash = shift;
    my $array_ref;
    my $temp_arr;
    my @temp_arr2;
    foreach my $k ( sort keys %$hash ) {
        $v = $$hash{$k};

        if ( ref($v) eq 'HASH' ) {

            # Recurse.
            $temp_arr = hash_walk( $v);

        }
        push @$array_ref, $k if $k;

        my (@lvlfirst, @lvlnext );

        if ($array_ref && $temp_arr){
            @lvlfirst = @$array_ref;
            @lvlnext = @$temp_arr; 
        }

        for ( my $i = 0 ; $i <= $#lvlfirst ; $i++ ) {
            for ( my $j = 0 ; $j <= $#lvlnext ; $j++ ) {
                push @temp_arr2, "$lvlfirst[$i]$lvlnext[$j]"; ##Trying to join here

            }
        }
    }

    return \@temp_arr2;
}

XML is:

<root>
  <class1 name="A1">
    <class2 name="B1">
      <class3 name="C1">
        <class4 name="D1"></class4>
        <class4 name="D2"></class4>
      </class3>
    </class2>
    <class2 name="B2">
      <class3 name="C1">
        <class4 name="D1"></class4>
      </class3>
    </class2>
    <class2 name="B3">
      <class3 name="C1">
        <class4 name="D1"></class4>
        <class4 name="D2"></class4>
        <class4 name="D3"></class4>
      </class3>
    </class2>
  </class1>
</root>

Upvotes: 0

Views: 69

Answers (2)

Sobrique
Sobrique

Reputation: 53478

I would tackle this differently - as this is XML, I would skip the intermediate 'mangle the XML into a hash' step, and just work with it directly.

Something like this does what you want:

#!/usr/bin/env perl
use strict;
use warnings 'all'; 

use XML::Twig;
use Data::Dumper;

my $twig = XML::Twig -> new -> parsefile ('your.xml');

my @node_keys; 

#find all the nodes with a name attribute.
#then grep out the ones that have child nodes. 
foreach my $elt ( grep { not $_ -> descendants } $twig -> get_xpath('//*[@name]') ){
    my $path = $elt -> att('name'); 
    my $cursor = $elt; 
    #recurse upwards through 'parent' nodes with a 'name' attribute. 
    while ( $cursor -> parent -> att('name') ) {
       $path = $cursor -> parent -> att('name') . $path;
       $cursor = $cursor -> parent;
    }
    push @node_keys, $path; 
}

print Dumper \@node_keys;

Gives output:

$VAR1 = [
          'A1B1C1D1',
          'A1B1C1D2',
          'A1B2C1D1',
          'A1B3C1D1',
          'A1B3C1D2',
          'A1B3C1D3'
        ];

Note - because it's walking in 'XML order' it's preserving the same ordering as source. That might be called a feature, or you can sort it afterwards.

But I would question perhaps, what you're trying to accomplish by making these compounds of 'name' attributes - it may be that you can solve the task more effectively through XML parsing and xpath queries.

Upvotes: 2

Dave Cross
Dave Cross

Reputation: 69244

You should really make some effort yourself before coming to SO for help. We're far more likely to help you fix broken code than just give you an answer.

But I'm feeling generous and I have a couple of minutes to spare.

The brute force approach would be to walk through every key at every level in the hash.

#!/usr/bin/perl

use strict;
use warnings;
use 5.010;

use Data::Dumper;

my $hash_ref = {
    'A1' => {
        'B2' => {
            'C1' => {
                'D1' => {},
                'D2' => {},
                'D3' => {}
            }
        },
        'B3' => {
            'C1' => {
                'D2' => {},
                'D1' => {},
                'D3' => {}
            }
        },
        'B1' => {
            'C1' => {
                'D1' => {},
                'D2' => {}
            }
        }
    }
};

my @arr;

for my $l1 (sort keys %$hash_ref) {
  for my $l2 (sort keys %{$hash_ref->{$l1}}) {
    for my $l3 (sort keys %{$hash_ref->{$l1}{$l2}}) {
      for my $l4 (sort keys %{$hash_ref->{$l1}{$l2}{$l3}}) {
        push @arr, "$l1$l2$l3$l4";
      }
    }
  }
}

say Dumper \@arr;

This produces the output:

$VAR1 = [
          'A1B1C1D1',
          'A1B1C1D2',
          'A1B2C1D1',
          'A1B2C1D2',
          'A1B2C1D3',
          'A1B3C1D1',
          'A1B3C1D2',
          'A1B3C1D3'
        ];

Update: Here's a recursive solution:

#!/usr/bin/perl

use strict;
use warnings;
use 5.010;

use Data::Dumper;

my $hash_ref = {
    'A1' => {
        'B2' => {
            'C1' => {
                'D1' => {},
                'D2' => {},
                'D3' => {}
            }
        },
        'B3' => {
            'C1' => {
                'D2' => {},
                'D1' => {},
                'D3' => {}
            }
        },
        'B1' => {
            'C1' => {
                'D1' => {},
                'D2' => {}
            }
        }
    }
};

my @arr = walk_hash($hash_ref, '');

say Dumper \@arr;

sub walk_hash {
  my ($hash_ref, $prefix) = @_;

  return $prefix unless keys %$hash_ref;
  return map { walk_hash($hash_ref->{$_}, "$prefix$_") } sort keys %$hash_ref;
}

Upvotes: 3

Related Questions