Search code examples
perlperl-data-structures

Perl - Convert hash of arrays to array of arrays using recursive function


I have created following hash of arrays (called $hoa) in my script:

$hoa = {
      'Continents' => [
                        'Continent1',
                        'Continent2',
                        'Continent3'
                      ],
      'Earth' => [
                   'Continents'
                 ],
      'Continent1' => [
                        'Country1'
                      ],
      'Continent3' => [
                        'Country3'
                      ],
      'Country1' => [
                      'City1',
                      'City2'
                    ]
    };

I want to convert this into an array of arrays for being consumed in the later part of the script. The array of array should look as below:

$aoa=[
    ['Earth','Continents','Continent1','Country1','City1'],
    ['Earth','Continents','Continent1','Country1','City2'],
    ['Earth','Continents','Continent2'],
    ['Earth','Continents','Continent3','Country3']
];

I have created the following code to achieve the job but it is not working as expected.

sub CreateArrofArr
{
    my $arg={@_};
    my $member=$arg->{member};
    my $hoa=$arg->{hoa};

    my $aoa=[];

    if(exists($hoa->{$member}))
    {
        for(my $i=0; $i<scalar(@{$hoa->{$member}}); $i++)
        {
            my $elem=@{$hoa->{$member}}->[$i];

            my $temp_arr=[];
            if(!exists($hoa->{$elem}))
            {
                push(@{$temp_arr},$member);
                push(@{$temp_arr},$elem);
                push(@{$aoa},$temp_arr);

            }
           else
           {
                push(@{$aoa},@{CreateArrofArr(member=>$elem,hoa=>$hoa)})
           }
        }
    }
    return ($aoa);
}


my $aoa=CreateArrofArr(member=>"Earth",hoa=>$hoa);

print Dumper($aoa);

$aoa is returned as follows (which is not what I am expecting):

$VAR1 = [
      [
        'Country1',
        'City1'
      ],
      [
        'Country1',
        'City2'
      ],
      [
        'Continents',
        'Continent2'
      ],
      [
        'Continent3',
        'Country3'
      ]
    ];

Please help.


Solution

  • Minimal changes:

    sub CreateArrofArr {
        my $arg = { @_ };
    
        my $member = $arg->{member};
        my $hoa    = $arg->{hoa};
    
        my $aoa = [];
        if (exists($hoa->{$member})) {
            for (my $i=0; $i<scalar(@{$hoa->{$member}}); $i++) {
                my $elem = $hoa->{$member}->[$i];
                push @{$aoa},
                    map { [ $member, @$_ ] }
                        @{ CreateArrofArr( member => $elem, hoa => $hoa ) };
            }
        } else {
            my $temp_arr = [];
            push @{$temp_arr}, $member;
            push @$aoa, $temp_arr;
        }
    
        return $aoa;
    }
    
    my $aoa = CreateArrofArr( member => "Earth", hoa => $hoa );
    

    Cleaned up version of the above:

    sub flatten {
        my ($tree, $current) = @_;
        my $node = $tree->{$current};
        return [ $current ] if !$node;
        return
           map { [ $current, @$_ ] }
              map { flatten($tree, $_) }
                @$node;
    }
    
    my @flattened = flatten($tree, 'Earth');
    

    An alternative approach is to pass down the path to the root of the tree than to keep prepending to your paths. This simplifies things a little.

    sub flatten {
        my $tree = shift;
        my $node = $tree->{ $_[-1] };
        return [ @_ ] if !$node;
        return map { flatten($tree, @_, $_) } @$node;
    }
    
    my @flattened = flatten($tree, 'Earth');