0

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>
6
  • 1
    This sounds like an XY problem. Could you elaborate on it in a bit more detail? Normally recursive structures are recursive for a reason. Commented Jul 26, 2016 at 14:21
  • @Sobrique the input hashref is generated from an classification hierarchy of data in xml. I have to join data to generate strings which represents parent child link. Commented Jul 26, 2016 at 16:16
  • 1
    Ok. Can I suggest taking a step back then. Use an XML parser - post your XML, and we can give what you want more easily i think, given XML parsers implicitly handle that recursion. Commented Jul 26, 2016 at 20:59
  • @Sobrique XML is: <?xml version="1.0" encoding="UTF-8"?> <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> Commented Jul 27, 2016 at 6:39
  • 1
    Added an example of how to work directly with the XML. Backtrack a bit, and figure out what you're actually trying to accomplish and you might find that working direct with XML is a better solution to your problem. Commented Jul 27, 2016 at 8:44

2 Answers 2

3

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;
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for answer Dave. But this code will fail if there are only 2 levels of nesting. The hash I'm trying is having dynamic nesting & it's getting too complicated when I try to further code it.
Yeah, having a dynamic number of hash levels means you'll need a recursive solution. I might have time to look at that a little later.
Added a recursive solution.
2

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.