kdmurphy001 has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks,
I'm hoping you can yet again help me..wait I know you "can" help me let me rephrase that. I hope that you will seek to enlighten me with your infinte wisdom.

I have a hash that is made of up the following elements:

1958796238 => ch02.03.MultipleChoice 029
1631977192 => ch02.03.MultipleChoice 012
649020496 => ch01.03.MultipleChoice 061
1431783758 => ch02.03.MultipleChoice 082
1515026861 => ch01.03.MultipleChoice 071
1767673050 => ch01.03.MultipleChoice 049
1988528379 => ch01.03.MultipleChoice 091
220401953 => ch01.03.MultipleChoice 071


This list is not complete as it is a very large pull form a db. This is just a sample. The Key value is unique meaing that only one Key value can be associated with one Hash value (But not vice versa). I need to create an array that will have the key value for each unique key. That is the array will need each key that has ch01.03.MultipleCHoice 071 as it's value. Then I will need to repeat this process (with some stuff done in between) for each unique key value (and it's associated keys). Below is the entire script.
#!/usr/bin/perl use strict; use warnings; use DBD::mysql; ###################### #### CONFIG VARS ##### ###################### #Specify start and end dates to filter data my $start_date = '2008-12-25'; my $end_date = '2009-04-01'; my $output_file = 'Hordis_Item_Overview.txt'; ###################### #### PRIVATE VARS #### ###################### my $db_user = 'xxxxx'; my $db_pass = 'xxxxx'; ###################### #### MAIN BODY ####### ###################### open OUTPUT, ">$output_file"; my $dbh = DBI->connect("DBI:mysql:database=xxxx:host=xxxxx",$db_user,$ +db_pass,{RaiseError=>1})|| die "$DBI::errstr\n"; #Hash to hold ItemUID and Item Name my %ItemUID= (); #Get ItemUID and Item Name (ItemUID is unique to each Test.IData) { my $sql = "SELECT t.IData FROM Tests AS t JOIN Assignments AS a ON a.A +ssignmentTestID=t.ID JOIN Users AS u ON u.ID=t.UserID WHERE u.Institu +tionID='004452' AND t.IData REGEXP 'ch01.03.MultipleChoice 049';"; my $sth = $dbh->prepare ( $sql ); $sth->execute(); while (my @row = $sth->fetchrow_array()) { my $idata = $row[0]; while ($idata =~ /<item([^>]+)/gi) { my $elements = $1; my $src; my $uid; if ($elements =~ /src=\"\w+\/([^"]+)/i) { $src = $1 } if ($elements =~ /uid=\"([^"]+)/i) { $uid = $1 } if (not exists $ItemUID{$uid}) { $ItemUID{$uid} = $src } } } $sth->finish(); } #Hold Unique Item Names to use against ItemUID hash @hvalues=(); while (my ($key, $value) = each $ItemUID) { push #Get Item Specific Info for Each Student/Assignment { while (my ($key, $value) = each %ItemUID) { print "Processing $key/$value\n"; my $sql = "SELECT s.Name, CONCAT(u.LastName,', ',u.FirstName), a.N +ame, TRUNCATE((MAX(air.TimeSpent)/60),0), TRUNCATE((MIN(air.TimeSpent +)/60),0), SUM(air.TimesTaken), AVG(air.Score), air.FeedbackAttempts, +air.HintsGiven FROM Sections AS s JOIN Assignments AS a ON s.ID=a.SectionID JO +IN Users AS u ON u.ID=s.OwnerID JOIN AssignmentResults AS ar ON a.ID= +ar.AssignmentID JOIN AssignmentItemResults AS air ON ar.ID=air.Assign +mentResultsID WHERE air.ItemUID IN '$key' GROUP BY s.Name ORDER BY s.Name, a. +Name, u.LastName"; my $sth = $dbh->prepare ( $sql ); $sth->execute(); while (my @row = $sth->fetchrow_array()) { print OUTPUT "$row[0]\t$row[1]\t$row[2]\t$row[3]\t$row[4]\t$ro +w[5]\t$row[6]\t$row[7]\n"; } $sth->finish(); } } $dbh->disconnect(); close OUTPUT; ###################### #### PRIVATE SUBS #### ######################
  • Comment on Creating Array from Hash that pulls only Unique values while iterating over each Unique Value
  • Download Code

Replies are listed 'Best First'.
Re: Creating Array from Hash that pulls only Unique values while iterating over each Unique Value
by CountZero (Bishop) on Apr 02, 2009 at 06:38 UTC
    Or more generally: a way to invert a hash (keys become values and vice versa) without loosing any data:
    use strict; use warnings; use Data::Dumper; my %hash; while (<DATA>) { my ( $key, $value ) = split; $hash{$key} = $value; } ## end while (<DATA>) print "Original hash\n", Dumper( \%hash ); my %data_structure; foreach my $key ( keys %hash ) { push @{ $data_structure{ $hash{$key} } }, $key; } ## end foreach my $key ( keys %hash) print "Inverted hash\n", Dumper( \%data_structure ); __DATA__ key1, value1 key2, value2 key3, value1 key4, value3 key5, value2 key6, value4 key7, value4
    output
    Original hash $VAR1 = { 'key3,' => 'value1', 'key2,' => 'value2', 'key4,' => 'value3', 'key6,' => 'value4', 'key7,' => 'value4', 'key5,' => 'value2', 'key1,' => 'value1' }; Inverted hash $VAR1 = { 'value4' => [ 'key6,', 'key7,' ], 'value1' => [ 'key3,', 'key1,' ], 'value3' => [ 'key4,' ], 'value2' => [ 'key2,', 'key5,' ] };
    The resulting data-structure is a Hash-of-Arrays.

    CountZero

    A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

Re: Creating Array from Hash that pulls only Unique values while iterating over each Unique Value
by CSJewell (Beadle) on Apr 02, 2009 at 05:27 UTC

    OK. Let's create a second hash whose keys are the array you need.

    # Put this after defining %ItemUID. my %hvalues; # instead of my @hvalues. ... if (not exists $ItemUID{$uid}) { $ItemUID{$uid} = $src } # At end of first while loop, insert this line after the one above. if (not exists (%hvalues{src})) { %hvalues{src} = 1; }

    Then keys %hvalues will get you the array you need. You could always say my @hvalues = keys %hvalues; if clarity of that type is needed.

Re: Creating Array from Hash that pulls only Unique values while iterating over each Unique Value
by ig (Vicar) on Apr 02, 2009 at 05:55 UTC
    That is the array will need each key that has ch01.03...

    If I understand correctly, you have pairs of UID and NAME. There will be several UIDs associated with each name. You want a list of all the unique names and for each name a list of all the UIDs associated with that name. The structure you need for this is a hash of arrays, and you can produce it at the same time you produce your ItemUID hash, as follows:

    my %ItemUID = (); my %ItemName = (); #Get ItemUID and Item Name (ItemUID is unique to each Test.IData) { my $sql = "SELECT t.IData FROM Tests AS t JOIN Assignments AS a ON + a.A +ssignmentTestID=t.ID JOIN Users AS u ON u.ID=t.UserID WHERE u.Institu +tionID='004452' AND t.IData REGEXP 'ch01.03.MultipleChoice 049';"; my $sth = $dbh->prepare($sql); $sth->execute(); while ( my @row = $sth->fetchrow_array() ) { my $idata = $row[0]; while ( $idata =~ /<item([^>]+)/gi ) { my $elements = $1; my $src; my $uid; if ( $elements =~ /src=\"\w+\/([^"]+)/i ) { $src = $1 } if ( $elements =~ /uid=\"([^"]+)/i ) { $uid = $1 } if ( not exists $ItemUID{$uid} ) { $ItemUID{$uid} = $src; push(@{$ItemName{$src}}, $uid); } } } $sth->finish(); } # you can generate an array of the unique names if you like my @names = sort keys %ItemNames;

    The hash $ItemName will have keys like 'ch02.03.MultipleChoice 029' and the values will be references to arrays of UIDs.

    Hope this helps...

Re: Creating Array from Hash that pulls only Unique values while iterating over each Unique Value
by Ish (Acolyte) on Apr 02, 2009 at 05:29 UTC
    Hi. Could you elaborate on the problem you are having or what is not working? (or whatever) Thanks.
      Thanks much the first reply helped and from there I was able to solve the problem. In short the issue was that rather then grouping the results per Section/Instructor it was grouping them by the ItemUID which created over 300 rows returned rather then 18. The issue was partially in my query and the perl code. Thanks again!