I have a list that I read into an array. Each element is a text string
like this (it's an object identifier):
"region.level.location.system"
Okay, you can store this in a 4-dimensional hash like this:
my %hash;
foreach my $string (@array) {
my ($region, $level, $location, $system) = split /\./, $string;
$hash{$region}{$level}{$location}{$system}++;
}
The values in the hash will simply be numbers indicating how many
times each identifier has been seen. You don't need to use them for
anything -- it's just the keys that matter.
need to generate a report that shows me the relationships in an orderly
(almost graphic) manner ... I want to be able to print out something
like this (actual formatting is not important, just the organization):
region1
region1.level1
region1.level2
region1.level1.location1
region1.level1.location2 [snip]
... so on and so forth ... iterating through every region, every
level, every location, every system
For the output you want, the simplest way would be to write four
separate loops, one for each depth. They will all look fairly
similar:
foreach my $region (sort keys %hash) {
print "$region\n";
}
foreach my $region (sort keys %hash) {
my %region = %{ $hash{$region} };
foreach my $level (sort keys %region) {
print "$region.$level\n";
}
}
foreach my $region (sort keys %hash) {
my %region = %{ $hash{$region} };
foreach my $level (sort keys %region) {
my %level = %{ $region{$level} };
foreach my $location (sort keys %level) {
print "$region.$level.$location\n";
}
}
}
foreach my $region (sort keys %hash) {
my %region = %{ $hash{$region} };
foreach my $level (sort keys %region) {
my %level = %{ $region{$level} };
foreach my $location (sort keys %level) {
my %location = %{ $level{$location} };
foreach my $system (sort keys %location) {
print "$region.$level.$location.$system\n";
}
}
}
}
Of course, if you wanted to do the same for a nested hash of arbitrary
depth, you could use a recursive solution:
sub keys_at_depth {
my ($hashref, $depth) = @_;
return sort keys %$hashref if $depth < 2;
my @keys;
foreach my $key (sort keys %$hashref) {
my $subhash = $hashref->{$key};
next unless ref $subhash eq 'HASH';
push @keys, map "$key.$_", keys_at_depth($subhash, $depth-1);
}
return @keys;
}
my $depth = 1;
while (my @keys = keys_at_depth(\%hash, $depth)) {
print "$_\n" for @keys;
$depth++;
}
There, that's much nicer, isn't it? Of course, there are several
other ways to approach your problem as well. For example, you could
instead use four separate hashes, one for each depth:
my (%regions, %levels, %locations, %systems);
foreach my $string (@array) {
my ($region, $level, $location, $system) = split /\./, $string;
$regions {"$region"}++;
$levels {"$region.$level"}++;
$locations{"$region.$level.$location"}++;
$systems {"$region.$level.$location.$system"}++;
}
print "$_\n" for sort keys %regions;
print "$_\n" for sort keys %levels;
print "$_\n" for sort keys %locations;
print "$_\n" for sort keys %systems;
Or you could use an array of hashes:
my @depths;
foreach my $string (@array) {
my @parts = split /\./, $string;
$depths[$_]{join ".", @parts[0 .. $_]}++ for 0 .. $#parts;
}
foreach my $depth (@depths) {
print "$_\n" for sort keys %$depth;
}
There, I don't think it gets any more compact than that. Of course,
to populate @array, you'd use:
my @array = <>;
chomp @array;
and don't forget to start your script with:
#!/usr/bin/perl
use warnings;
use strict;