Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Populate Hash of Arrays (Relationship) 3

Status
Not open for further replies.

Extension

Programmer
Nov 3, 2004
311
CA
Hi,

I'm trying to build a hash of arrays that could handle a (recursive relationship) between two data sets (USERS & LOCATION).
I'm pretty much lost right now.

I have two flat files: USERS & DATA
I want to create a hash of arrays and then output the data.

Based on the code and data I have below, my hash has the following structure:

Feedback on my code is also welcomed.

Code:
Hash
{12}{100} => [Bobby]
{14}{110} => [Ted]
{16}{120} => [Carey]

instead of

Code:
Hash
{12}{}{100} => [Bobby]
{12}{14}{110} => [Ted]
{12}{16}{120} => [Carey]


Code:
# Location of elements in Array (Users)
$USER_ID = 0;
$USER_PARENT_ID = 2; 
$USER_NAME = 1;

# Location of elements in Array (Location)
$LOC_ID = 0;
$LOC_NAME = 1; 

# Open File (LOCATIONS)
open(LOCATION,"Loc.txt") || die("Error");				
my $fieldNames = <LOCATION>;
@Locs = map {chomp; [split /\|/, $_]} <LOCATION>;
close(LOCATION);

# Create Hash for Locations
my %Locations = map {$_->[$LOC_ID] => $_} @Locs; 


# Open File 
open(USERS,"Users.txt") || die("Error");				
my $fieldNames = <USERS>;
@Users = <USERS>;
close(USERS);

# Populate Hash
foreach my $User (@Users) {	
	chomp $User;
	my @fieldUser = split(/\|/,$User);
	$userList{$fieldUser[$USER_PARENT_ID]}{$fieldUser[$USER_ID]} = [ @fieldUser ];
}

Code:
__USERS__ (Users.txt)
ID	|	USERNAME	|	LOC_ID
100	|	Bobby		|	12
110	|	Ted			|	14
120	|	Carey		|	16

Code:
__LOCATION__ (Loc.txt)
ID	|	NAME	|	LEVEL	|	PARENT
10	|	US		|	1		|	0
12	|	Oregon	|	2		|	10	
14	|	Seattle	|	3		|	12
16	|	Ostig	|	3		|	12

 
Your goal is unclear to me: can you better explain what you want to obtain? The complete hash corresponding to your sample data and the printout sought would be useful.
Your code creates two separate hashes, the second one resembles to your first example (but it is not the same).
BTW what you mean by:- [tt][ignore][Bobby][/ignore][/tt] - in :- [tt]{12}{100} => [ignore][Bobby][/ignore][/tt] -is obscure to me.

Franco
: Online engineering calculations
: Magnetic brakes for fun rides
: Air bearing pads
 
You can't have this structure in a hash:

Code:
{12}{}{100} => [Bobby]
{12}{14}{110} => [Ted]
{12}{16}{120} => [Carey]

Well, you can but it would have to be a hash of anonymous hashes otherwise the last instance of 12 in the file would be the only key in the hash with that value since hash keys must be unique. Then again, maybe I don't understand the example.

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
Kevin:

You answered part of my question because I wasn't certain if it was "legal" to leave a hash key empty.

Is there an acceptable way to populate a hash (of Arrays) that could reflect the structure I'm looking for ?

I want a structure like this:

Code:
{STATE_ID}{CITY_ID}{USED_ID} => [@Array of USER data]

But if you look at the data above, some USERS don't have a CITY defined and are linked directly to the STATE.

Thanks
 
my point was that a hash can't have two top-level keys that are the same, hash keys must be unique. If 12 represent the top-level key of the hash, there will only be one key 12 in the end.

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
Ok. Learned something new because I thought it was ok to do that.

Like this is working fine even if they share the same top level key.

Code:
for ($count = 1; $count <= 12; $count++) {
		
	$userList{'12'}{$count} = [ "User no. : " . $count ];
		
}


foreach my $topLevel ( keys %userList ) {
		print qq(= $topLevel \n);
		
	foreach my $User ( keys %{ $userList{$topLevel} } ) {
		print qq( \t - $userList{$topLevel}{$User}->[0] \n);
	}

}
 
OK, I misunerstood your first example hash. It should have been like this:

Code:
'12' => {  
  '14' => {'110' => [Ted]},
  '16' => {'120' => [Carey]},
}

or something like that. There is only one top-level key '12'. That key (12) is a hash of hashes with 12 being the top-level key and 14/16/etc being the second-level keys, and so on. Inside the second-level hash there can once again only be one top-level key for its own level, so 14 and 16 could not be repeated in the 12 keys hash. Its easier to understand than it is to explain.

You will find Data::Dumper is very helpful to understand data structures. Your last example:

Code:
my %userList;
for (my $count = 1; $count <= 12; $count++) {
        
    $userList{'12'}{$count} = [ "User no. : " . $count ];
        
}
print Dumper \%userList;

The output. Note there is only one top-level key 12:

Code:
$VAR1 = {
          '12' => {
                    '6' => [
                             'User no. : 6'
                           ],
                    '11' => [
                              'User no. : 11'
                            ],
                    '3' => [
                             'User no. : 3'
                           ],
                    '7' => [
                             'User no. : 7'
                           ],
                    '9' => [
                             'User no. : 9'
                           ],
                    '12' => [
                              'User no. : 12'
                            ],
                    '2' => [
                             'User no. : 2'
                           ],
                    '8' => [
                             'User no. : 8'
                           ],
                    '1' => [
                             'User no. : 1'
                           ],
                    '4' => [
                             'User no. : 4'
                           ],
                    '10' => [
                              'User no. : 10'
                            ],
                    '5' => [
                             'User no. : 5'
                           ]
                  }
        };

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
I now better understand your goal. This is possibly closer to what you are looking for:
Code:
# Populate Hash
my$state_id;
for my$User(@Users){    
  chomp$User;
  my@fieldUser=split(/\|/,$User);
  if($Locations{$fieldUser[2]}[2]==3){
    $state_id=$Locations{$fieldUser[2]}[3];
  }else{
    $state_id=$fieldUser[2];
  }
  $userList{$state_id}{$fieldUser[2]}{$fieldUser[0]}=\@fieldUser;
}
As you cannot have a void hash key, this code will use user's LOC_ID as the second level key. This doesn't add any more information to the system, you could simply use instead a default key like 'unnamed' or 'unknown'.

Franco
: Online engineering calculations
: Magnetic brakes for fun rides
: Air bearing pads
 
Thanks to KevinADC and Prex1. Your help was really appreciated.



 

I'm stuck again !!! I'm trying to build a hash (of arrays) that would capture the relationship of the DATA below.
I guess I lack of knowledge in regards to hashes because I don't know how to populate a hash when you don't know how many keys (levels) you'll have.

I can post my code if necessary.

Code:
Data:
ID|NAME|PARENT
10|US|0
12|Oregon|10
18|Cali|10
14|Seattle|12
16|Ostig|12
22|LosAngeles|18


Code:
Outcome I'm trying to achieve
- US [ID: 10]
	- Oregon [ID: 12]
		- Seattle [ID: 14]
		- Ostig [ID: 16]
	- Cali [ID: 18]
		- Los Angeles [ID: 22]
 
post your current code

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
Hi Guys,

I was able to come up with a solution to populate the hash.

But now I have another question. I want to sort the hash either by numeric or alpha order. I created a sub-routine that would take care of applying the right sorting.
I guess I'm doing something wrong cause it's not working.

Here's my code.

Code:
# Third Level
for my $records (sort sortHash keys %{ $userList{$Level2}{$Level3} }) {
	####
}


sub sortHash {

	if ($param eq "numeric") {
		{ $userList{$Level2}{$Level3}{$a}[0] cmp $userList{$Level2}{$Level3}{$b}[0] } 
	}
	else {
		{ $userList{$Level2}{$Level3}{$a}[2] cmp $userList{$Level2}{$Level3}{$b}[2] } 
	}
}
 
to sort number use '<=>' inplace of 'cmp'

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
Kevin: Yes.. That was changed. But it didn't fix the problem.

When I change this
Code:
# Third Level
for my $records (sort sortHash keys %{ $userList{$Level2}{$Level3} }) {
    ####
}

to this

Code:
# Third Level
for my $records (sort { $userList{$Level2}{$Level3}{$a}[2] cmp $userList{$Level2}{$Level3}{$b}[2] } keys %{ $userList{$Level2}{$Level3} }) {
    ####
}

The sorting does work fine.




 
OK, but if you are sorting numerically, "cmp" will not sort numbers properly. It sorts in ASCII order.

Code:
my @array = (1, 10, 100, 2, 3, 4);
@array = sort @array;
print "$_\n" for @array;

which outputs:

1
10
100
2
3
4

Just something to be aware of.

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
Yeah I know (right now I'm testing using Alpha data with "cmp"). But my issue is happening because I'm storing the sorting "statement" in a sub-routine.
 
It looks like you have this mostly worked out, but since I'm stuck here due to bad weather I thought I'd give it a go. This may be a bit more complex than you were looking for though.
Code:
$_ = <DATA>;	# Grab headers
my @loc;

while (<DATA>) {
	chomp(my ($id, $name, $pid) = split /\s*\|\s*/, $_);
	push @loc, {id=>$id, name=>$name, parent=>$pid, children=>[]};
}

{
	#	Move child records to associated parent record
	my %ids = map {$_->{id} => \$_} @loc;
	for my $i (0.. $#loc) {
		if (defined $loc[$i]) {
			my $parent = $loc[$i]->{parent};
			next if $parent == 0 or ! defined $ids{$parent};
			push @{${$ids{$parent}}->{children}}, splice(@loc, $i, 1);
			redo;
		} else {
			delete $loc[$i];
		}
	}
}

print_list (\@loc);
# Recursively iterate through records and print them
sub print_list {
	my ($aref, $level) = @_;
	$level ||= 0;
	foreach my $record (sort by_id @{$aref} ) {
		print '   ' x $level, "- ";
		print $record->{name}, " [ID: $record->{id}]\n";
		if (defined @{$record->{children}}) {
			print_list(\@{$record->{children}}, $level+1);
		}
	}
}

sub by_id { $a->{id} <=> $b->{id}; }
sub by_name {$a->{name} cmp $b->{name}; }
You should be able to incorporate the users if you key a hash off of their parent ID and print any user records with the appropriate location record.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top