How can I loop through a Perl array of arrays of hashes? - arrays

I would like to print an Array of Arrays of Hashes, so I looked at perldsc, and ended up with
for my $j (0 .. $#aoaoh) {
for my $aref (#aoaoh) {
print '"' . join('","', #$aref[$j]), "\"\n";
}
}
but it doesn't work.
Does anyone know how to do this?

It works as far as you've gone. Adding some test data to your program gives us:
#!/usr/bin/perl
use strict;
use warnings;
my #aoaoh = (
[
{ a => 1, b => 2 },
{ c => 3, d => 4 },
],
[
{ a => 101, b => 102 },
{ c => 103, d => 104 },
],
);
for my $j (0 .. $#aoaoh) {
for my $aref (#aoaoh) {
print '"' . join('","', #$aref[$j]), "\"\n";
}
}
And running that gives:
$ ./aoaoh
"HASH(0x9c45818)"
"HASH(0x9c70c48)"
"HASH(0x9c60418)"
"HASH(0x9c70c08)"
So you've successfully navigated the two levels of arrays and you're just left with the hash references to dereference. Something like this perhaps:
#!/usr/bin/perl
use strict;
use warnings;
my #aoaoh = (
[
{ a => 1, b => 2 },
{ c => 3, d => 4 },
],
[
{ a => 101, b => 102 },
{ c => 103, d => 104 },
],
);
for my $j (0 .. $#aoaoh) {
for my $aref (#aoaoh) {
# print '"' . join('","', #$aref[$j]), "\"\n";
for (keys %{$aref->[$j]}) {
print "$_ -> $aref->[$j]{$_}\n";
}
}
}
Which gives:
$ ./aoaoh
a -> 1
b -> 2
a -> 101
b -> 102
c -> 3
d -> 4
c -> 103
d -> 104
Personally, I'd write it like this as I think it's easier to deal with elements than indexes.
#!/usr/bin/perl
use strict;
use warnings;
my #aoaoh = (
[
{ a => 1, b => 2 },
{ c => 3, d => 4 },
],
[
{ a => 101, b => 102 },
{ c => 103, d => 104 },
],
);
for my $aref (#aoaoh) {
for my $href (#$aref) {
for (keys %{$href}) {
print "$_ -> $href->{$_}\n";
}
}
}

foreach my $aoh (#aoaoh) {
foreach my $hashref ( #{$aoh} ) {
foreach my $key ( keys %{$hashref} ) {
print $key . " => " . $hashref->{$key}, "\n";
}
print "#" x 40, "\n";
}
}
UPDATE: Sorry, it must be array (not array ref)

Have a look at perlreftut, it will help you,
and see the answer below.
#!/usr/bin/perl
use strict;
use warnings;
my #aoaoh = (
[
{ a => 1, b => 2 },
{ c => 3, d => 4 },
],
[
{ a => 101, b => 102 },
{ c => 103, d => 104 },
],
);
for my $j (0 .. $#aoaoh) {
for my $aref (#{$aoaoh[$j]}) {
for my $test (keys %{$aref})
{
print"$test => ${$aref}{$test}\n";
}
}
}
output:
a => 1
b => 2
c => 3
d => 4
a => 101
b => 102
c => 103
d => 104

To loop through the whole kit & caboodle:
use 5.012;
use warnings;
my #array = (
[
{ a => 1, b => 2 },
{ c => 3, d => 4 },
],
[
{ a => 101, b => 102 },
{ c => 103, d => 104 },
],
);
for my $root (#array) {
for my $each_array_of_hashes (#$root) {
for my $k (keys %{ $each_array_of_hashes } ) {
say $k, ' => ', $each_array_of_hashes->{$k};
}
}
}
Is this what you're after?

Related

How to use variable name or array value as name to initialize another array?

My array will have strings as below...
my #array1 = ( "CE_2N_Comp_ChmProcess",
"CE_2N_Comp_FmMasterProcess"....
"CE_2N_Comp_EmaProcess" );
And I want to use array1 values as name of new arrays... e.g.,
my #CE_2N_Comp_ChmProcess = (1,2,3);
my #CE_2N_Comp_FmMasterProcess = (4,5,6);
.
.
my #CE_2N_Comp_EmaProcess = (7,8,9);
Please help.
Expected array names
my #CE_2N_Comp_ChmProcess = (1,2,3);
my #CE_2N_Comp_FmMasterProcess = (4,5,6);
.
.
my #CE_2N_Comp_EmaProcess = (7,8,9);
In reply (Perl console), using a HASH with ARRAYs data structure:
> my $h = {} # create a HASH ref
$res[1] = {}
> push #{ $h->{CE_2N_Comp_ChmProcess} }, (1,2,3);
$res[2] = 3
> push #{ $h->{CE_2N_Comp_FmMasterProcess} }, (4,5,6);
$res[3] = 3
> push #{ $h->{CE_2N_Comp_EmaProcess} }, (7,8,9);
$res[4] = 3
> use Data::Dumper;
> print Dumper $h
{
'CE_2N_Comp_ChmProcess' => [
1,
2,
3
],
'CE_2N_Comp_EmaProcess' => [
7,
8,
9
],
'CE_2N_Comp_FmMasterProcess' => [
4,
5,
6
]
}
Or used dynamically :
0> my $h = {}
$res[0] = {}
1> my $c = 0;
$res[1] = 0
2> for ("CE_2N_Comp_ChmProcess", "CE_2N_Comp_FmMasterProcess", "CE_2N_Comp_EmaProcess") { \
push #{ $h->{$_} }, (++$c .. ($c+2)); $c+=2 }
$res[2] = ''
3> use Data::Dumper;
4> print Dumper $h;
{
'CE_2N_Comp_ChmProcess' => [
1,
2,
3
],
'CE_2N_Comp_EmaProcess' => [
7,
8,
9
],
'CE_2N_Comp_FmMasterProcess' => [
4,
5,
6
]
}

Parametric sorting of a list of hashes

My aim is to write a subroutine which takes in
An array of hashes
A list containing the sort order
Just to be clear - the keys may be anything. My example is just for reference.
Given an array containing a list of keys in their required sort order
my #aSortOrder = ( 'DELTA1_2', 'SET1', 'SET2' );
My idea is to form a string
$a->{DELTA1_2} <=> $b->{DELTA1_2} or $a->{SET1} <=> $b->{SET1} or $a->{SET2} <=> $b->{SET2}
and then execute it with eval.
Here's my code
my $paRecords = [
{ 'SET1' => 48265, 'DELTA1_2' => -1, 'SET2' => 48264 },
{ 'SET1' => 8328, 'DELTA1_2' => -29, 'SET2' => 8299 },
{ 'SET1' => 20, 'DELTA1_2' => 0, 'SET2' => 0 },
{ 'SET1' => 10, 'DELTA1_2' => 0, 'SET2' => 0 }
];
my #aSortOrder = ( 'DELTA1_2', 'SET1', 'SET2' );
my $pStr = '';
foreach ( #aSortOrder ) {
$pStr = $pStr . ' or $a->{' . $_ . '} <=> $b->{' . $_ . '}';
}
$pStr =~ s/^\s*or\s*//;
my #aSorted = sort { eval "$pStr"; } #$paRecords;
print Dumper \#aSorted;
output
$VAR1 = [
{
'SET1' => 8328,
'SET2' => 8299,
'DELTA1_2' => -29
},
{
'SET1' => 48265,
'SET2' => 48264,
'DELTA1_2' => -1
},
{
'SET2' => 0,
'DELTA1_2' => 0,
'SET1' => 10
},
{
'SET2' => 0,
'DELTA1_2' => 0,
'SET1' => 20
}
];
I guess that this is far from the ideal approach to solving the problem, so any pointer on how this problem could be better solved would be a great help.
Just create a sub that does the comparison.
sub custom_cmp {
my $keys = shift;
for my $key (#$keys) {
my $cmp = $_[0]{$key} <=> $_[1]{$key};
return $cmp if $cmp;
}
return 0;
}
my #aSorted = sort { custom_cmp(\#aSortOrder, $a, $b) } #$paRecords;
The above makes two sub calls for each comparison. If we generate the compare function, we can reduce that to one.
sub make_custom_cmp {
my #keys = #_;
return sub($$) {
for my $key (#keys) {
my $cmp = $_[0]{$key} <=> $_[1]{$key};
return $cmp if $cmp;
}
return 0;
};
}
my $cmp = make_custom_cmp(#aSortOrder);
my #aSorted = sort $cmp #$paRecords;
We could go one further and flatten the loop through code generation. This is what a "proper" eval-based solution would look like. However, this level of optimization is hardly needed.
sub make_custom_cmp {
my #keys = #_;
my #cmps;
for $i (0..$#keys) {
push #cmps, "\$_[0]{\$keys[$i]} <=> \$_[1]{\$keys[$i]}"
}
return eval("sub($$) { ".( join(" || ", #cmps) )."}");
}
my $cmp = make_custom_cmp(#aSortOrder);
my #aSorted = sort $cmp #$paRecords;
In fact, the following is probably the most performant solution:
my #aSorted =
map $paRecords->[ unpack('N', substr($_, -4))-0x7FFFFFFF ],
sort
map pack('N*', map $_+0x7FFFFFFF, #{ $paRecords->[$_] }{#aSortOrder}, $_),
0..$#$paRecords;
The block passed to sort may contain any amount of code. It is required only to evaluate to a negative number, zero, or a positive number according to whether $a should be considered to be less than, equal to, or great than $b
I agree with your decision to bundle this into a subroutine, so I have written sort_hashes_by_keys, which expects a reference to an array of hashes to be sorted, and a reference to an array of key strings. It returns a list of hashes sorted according to the list of keys
use strict;
use warnings 'all';
use Data::Dump 'dd';
my $records = [
{ SET1 => 48265, DELTA1_2 => -1, SET2 => 48264 },
{ SET1 => 8328, DELTA1_2 => -29, SET2 => 8299 },
{ SET1 => 20, DELTA1_2 => 0, SET2 => 0 },
{ SET1 => 10, DELTA1_2 => 0, SET2 => 0 }
];
my #sort_order = qw/ DELTA1_2 SET1 SET2 /;
my #sorted = sort_hashes_by_keys( $records, \#sort_order );
dd \#sorted;
sub sort_hashes_by_keys {
my ( $hashes, $order ) = #_;
sort {
my $cmp = 0;
for my $key ( #$order ) {
last if $cmp = $a->{$key} <=> $b->{$key};
}
$cmp;
} #$hashes;
}
output
[
{ DELTA1_2 => -29, SET1 => 8328, SET2 => 8299 },
{ DELTA1_2 => -1, SET1 => 48265, SET2 => 48264 },
{ DELTA1_2 => 0, SET1 => 10, SET2 => 0 },
{ DELTA1_2 => 0, SET1 => 20, SET2 => 0 },
]
Note that I strongly advise against both hungarian notation and camel case when naming your variables. Perl is not strictly typed, and it has sigils like $, # and % which indicate the type of every variable, so hungarian notation is superfluous at best, and also adds distracting and irrelevant noise. Also, by convention, capital letters are reserved for module names and global variables, so local identifiers should be in "snake case", i.e. lower-case letters and underscores. Many non-English speakers also find camel case difficult to parse
Well, you're quite right - using eval like that is a road to future pain.
The joy of 'sort' is that you can define a sort subroutine, that implicitly defines $a and $b and you can use whatever logic you desire to decide if it's a positive, negative or 'zero' comparison (equal). (e.g. like <=> or cmp do).
The trick here is - 'true' is anything non zero, so <=> you can test for 'true' to see if there's a comparison to be made ( 4 <=> 4 is 'false')
So if you're just working numerically (you'd need to test for 'alphanumeric' and use cmp in some cases there, but doesn't seem to apply to your data):
#!/usr/bin/env perl
use strict;
use warnings;
my $paRecords = [
{ 'SET1' => 48265, 'DELTA1_2' => -1, 'SET2' => 48264 },
{ 'SET1' => 8328, 'DELTA1_2' => -29, 'SET2' => 8299 },
{ 'SET1' => 20, 'DELTA1_2' => 0, 'SET2' => 0 },
{ 'SET1' => 10, 'DELTA1_2' => 0, 'SET2' => 0 }
];
#qw is 'quote-words' and just lets you space delimit terms.
#it's semantically the same as ( 'DELTA1_2', 'SET1', 'SET2' );
my #order = qw ( DELTA1_2 SET1 SET2 );
#note - needs to come after definition of `#order` but it can be re-written later as long as it's in scope.
#you can pass an order explicitly into the subroutine if you want though.
sub order_by {
for my $key (#order) {
#compare key
my $result = $a->{$key} <=> $b->{$key};
#return it and exit the loop if they aren't equal, otherwise
#continue iterating sort terms.
return $result if $result;
}
return 0; #all keys were similar, therefore return zero.
}
print join (",", #order), "\n";
foreach my $record ( sort {order_by} #$paRecords ) {
#use hash slice to order output in 'sort order'.
#optional, but hopefully clarifies what's going on.
print join (",", #{$record}{#order}), "\n";
}
This, given your data outputs:
DELTA1_2,SET1,SET2
-29,8328,8299
-1,48265,48264
0,10,0
0,20,0
Note, I've opted to use hash slice for your output, because otherwise hashes are unordered, and so your Dumper output will be inconsistent (randomly ordered fields).
If you need to be a little more dynamic about your ordering, you can pass it into the sort-sub:
#!/usr/bin/env perl
use strict;
use warnings;
sub order_by {
for my $key (#_) {
#compare key
my $result = $a->{$key} <=> $b->{$key};
#return it and exit the loop if they aren't equal, otherwise
#continue iterating sort terms.
return $result if $result;
}
return 0; #all keys were similar, therefore return zero.
}
my $paRecords = [
{ 'SET1' => 48265, 'DELTA1_2' => -1, 'SET2' => 48264 },
{ 'SET1' => 8328, 'DELTA1_2' => -29, 'SET2' => 8299 },
{ 'SET1' => 20, 'DELTA1_2' => 0, 'SET2' => 0 },
{ 'SET1' => 10, 'DELTA1_2' => 0, 'SET2' => 0 }
];
#qw is 'quote-words' and just lets you space delimit terms.
#it's semantically the same as ( 'DELTA1_2', 'SET1', 'SET2' );
my #order = qw ( DELTA1_2 SET1 SET2 );
print join( ",", #order ), "\n";
foreach my $record ( sort {order_by ( #order ) } #$paRecords ) {
#use hash slice to order output in 'sort order'.
#optional, but hopefully clarifies what's going on.
print join( ",", #{$record}{#order} ), "\n";
}

perl: deep merge with per-element arrays merge

I'm trying to merge two hashes and Hash::Merge does almost exactly what I need, except for arrays. Instead of concatenating arrays I need it to do per-element merge.
For example:
use Hash::Merge qw (merge);
my %a = ( 'arr' => [ { 'a' => 'b' } ] );
my %b = ( 'arr' => [ { 'c' => 'd' } ] );
my %c = %{ merge( \%a, \%b) };
Desired result is ('arr'=>[{'a'=>'b','c'=>'d'}]), actual result is ('arr'=>[{'a'=>'b'},{'c'=>'d'}])
Can this be done by using specify_behavior or is there some other way?
I think that specify_behaviour is used to specify how to handle conflicts, or uneven structures to merge. The documentation doesn't actually say much. But try it, go through defined shortcuts, or try to set them yourself. For your data structure you could try
SCALAR => ARRAY => sub { [ %{$_0}, %{$_[0]} ] }
SCALAR => ARRAY => HASH => sub { [ $_[0], $_[0] ] }
If you tried and it didn't work you may have found a bug in the module? By what you show it just didn't go "deep" enough. Here it is without the module. I've enlarged your sample structures.
use warnings;
use strict;
my %a = (
'arr1' => [ { a => 'A', a1 => 'A1' } ],
'arr2' => [ { aa => 'AA', aa1 => 'AA1' } ]
);
my %b = (
'arr1' => [ { b => 'B', b1 => 'B1' } ],
'arr2' => [ { bb => 'BB', bb1 => 'BB1' } ]
);
# Copy top level, %a to our target %c
my %c;
#c{keys %a} = values %a;
# Iterate over hash keys, then through array
foreach my $key (sort keys %c) {
my $arr_len = #{$c{$key}};
foreach my $i (0..$arr_len-1) {
my %hb = %{ ${$b{$key}}[$i] };
# merge: add %b to %c
#{ ${$c{$key}}[$i] }{keys %hb} = values %hb;
}
}
# Print it out
foreach my $key (sort keys %c) {
print "$key: ";
my $arr_len = #{$c{$key}};
foreach my $i (0..$arr_len-1) {
my %hc = %{ ${$c{$key}}[$i] };
print "$_ => $hc{$_}, " for sort keys %hc;
}
print "\n";
}
This prints the contents of %c (aligned manually here)
arr1: a => A, a1 => A1, b => B, b1 => B1,
arr2: aa => AA, aa1 => AA1, bb => BB, bb1 => BB1,
Code does not handle arrays/hashes of unequal sizes but checks can be added readily.
Another solution (that handles uneven hash elements in %a and %b).
my %c;
foreach my $key (keys %a, keys %b) {
my $a_ref = $a{$key};
my $b_ref = $b{$key};
$c{$key} = { map %$_, #$a_ref, #$b_ref };
}
use Data::Dumper;
print Dumper \%c;

How do I breakdown common elements in hash of arrays in perl?

I am trying to find any intersections of elements within a hash of arrays in Perl
For example
my %test = (
Lot1 => [ "A","B","C"],
Lot2 => [ "A","B","C"],
Lot3 => ["C"],
Lot4 => ["E","F"],
);
The result I would be after is
Lot1 and Lot2 have AB
Lot1,Lot2 and Lot3 have C
Lot4 has E and F.
I think this could be done with a recursive function that effectively moves its way through the arrays and if an intersection between two arrays is found it calls itself recursively with the intersection found and the next array. The stopping condition would be running out of arrays.
Once the function is exited I would have to iterate through the hash to get the arrays that contain these values.
Does this sound like a good approach? I have been struggling with the code, but was going to use List::Compare to determine the intersection.
Thank you.
Array::Utils has an intersection operation where you can test the intersect of two arrays. But that's only the start point of what you're trying to do.
So I would be thinking that you need to first invert your lookup:
my %member_of;
foreach my $key ( keys %test ) {
foreach my $element ( #{$test{$key}} ) {
push ( #{$member_of{$element}}, $key );
}
}
print Dumper \%member_of;
Giving:
$VAR1 = {
'A' => [
'Lot1',
'Lot2'
],
'F' => [
'Lot4'
],
'B' => [
'Lot1',
'Lot2'
],
'E' => [
'Lot4'
],
'C' => [
'Lot1',
'Lot2',
'Lot3'
]
};
Then collapse that, into a key set:
my %new_set;
foreach my $element ( keys %member_of ) {
my $set = join( ",", #{ $member_of{$element} } );
push( #{ $new_set{$set} }, $element );
}
print Dumper \%new_set;
Giving:
$VAR1 = {
'Lot1,Lot2,Lot3' => [
'C'
],
'Lot1,Lot2' => [
'A',
'B'
],
'Lot4' => [
'E',
'F'
]
};
So overall:
#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
my %test = (
Lot1 => [ "A", "B", "C" ],
Lot2 => [ "A", "B", "C" ],
Lot3 => ["C"],
Lot4 => [ "E", "F" ],
);
my %member_of;
foreach my $key ( sort keys %test ) {
foreach my $element ( #{ $test{$key} } ) {
push( #{ $member_of{$element} }, $key );
}
}
my %new_set;
foreach my $element ( sort keys %member_of ) {
my $set = join( ",", #{ $member_of{$element} } );
push( #{ $new_set{$set} }, $element );
}
foreach my $set ( sort keys %new_set ) {
print "$set contains: ", join( ",", #{ $new_set{$set} } ), "\n";
}
I don't think there's a more efficient way to tackle it, because you're comparing each array to each other array, and forming a new compound key out of it.
This gives you:
Lot1,Lot2 contains: A,B
Lot1,Lot2,Lot3 contains: C
Lot4 contains: E,F
This can be done as two simple hash conversions:
Build a hash that lists all of the lots each item is in
Convert that to a hash that lists all items for each lot combination
Then just dump the last hash in a convenient form
This is the code.
use strict;
use warnings 'all';
use feature 'say';
my %test = (
Lot1 => [ "A", "B", "C" ],
Lot2 => [ "A", "B", "C" ],
Lot3 => ["C"],
Lot4 => [ "E", "F" ],
);
my %items;
for my $lot ( keys %test ) {
for my $item ( #{ $test{$lot} } ) {
push #{ $items{$item} }, $lot;
}
}
my %lots;
for my $item ( keys %items ) {
my $lots = join '!', sort #{ $items{$item} };
push #{ $lots{$lots} }, $item;
}
for my $lots ( sort keys %lots ) {
my #lots = split /!/, $lots;
my $items = join '', #{ $lots{$lots} };
$lots = join ', ', #lots;
$lots =~ s/.*\K,/ and/;
printf "%s %s %s\n", $lots, #lots > 1 ? 'have' : 'has', $items;
}
output
Lot1 and Lot2 have AB
Lot1, Lot2 and Lot3 have C
Lot4 has EF
It generates an %items hash that looks like this
{
A => ["Lot2", "Lot1"],
B => ["Lot2", "Lot1"],
C => ["Lot2", "Lot3", "Lot1"],
E => ["Lot4"],
F => ["Lot4"],
}
and from that a %lots hash that looks like this
{
"Lot1!Lot2" => ["A", "B"],
"Lot1!Lot2!Lot3" => ["C"],
"Lot4" => ["E", "F"],
}

Push array to a certain hash within an array in Perl

I want to dynamically push values of hashes into an array of hashes in Perl.
I have this code block to create and push classHash to an array classList.
$courseName = <STDIN>;
$section = <STDIN>;
my $classHash = {};
$classHash->{courseName} = $courseName;
$classHash->{section} = $section;
push #classList, $classHash;
Now, I want to add a studentHash to the classHash.
for my $i ( 0 .. $#classList ) {
#I want to add the studentHash to a specific classHash in the classList
if($courseName1 eq $classList[$i]{courseName} && $section1 eq $classList[$i]{section}){
$studName = <STDIN>;
$studNum = <STDIN>;
my $studHash = {};
$studHash->{studName} = $studName;
$studHash->{studNum} = $studNum;
push #studList, $studHash;
push #{$classList[$i]}, \#studList; #but this creates an array reference error
}
}
Ignoring the interactive bits... here is how you can add the student to the class:
#!/usr/bin/env perl
use warnings;
use strict;
use Data::Dumper;
my #classList = (
{
courseName => 'Algebra',
section => 101,
students => [],
},
{
courseName => 'Geometry',
section => 102,
students => [],
},
);
my $studName = 'Alice';
my $studNum = 13579;
my $desiredClass = 'Geometry';
my $desiredSection = 102;
for my $class (#classList) {
if ($class->{courseName} eq $desiredClass and
$class->{section} eq $desiredSection) {
# Add student to the class
my $student = {
studName => $studName,
studNum => $studNum,
};
push #{ $class->{students} }, $student;
}
}
print Dumper \#classList;
# Printing out the students for each class
for my $class (#classList) {
my $course = $class->{courseName};
my $section = $class->{courseSection};
my $students = $class->{students};
my $total_students = scalar #$students;
my $names = join ', ', map { $_->{studName} } #$students;
print "There are $total_students taking $course section $section.\n";
print "There names are [ $names ]\n";
}
Output
VAR1 = [
{
'students' => [],
'section' => 101,
'courseName' => 'Algebra'
},
{
'students' => [
{
'studNum' => 13579,
'studName' => 'Alice'
}
],
'section' => 102,
'courseName' => 'Geometry'
}
];
There are 0 students taking Algebra section 101.
There names are [ ]
There are 1 students taking Geometry section 102.
There names are [ Alice ]

Resources