perl passing array of hashes to a sub - perl

I'm passing an array by reference to a sub function(\#charts);
the array charts contains hash array at i=0 and a string at i=1 and so on (hash-string..)
I want to store the keys of hashes in #x and the values in #y as shown in the below function.But I'm getting an infinite loop;it keeps printing ...
sub function{
print Dumper #_;
for ($i=0;$i<scalar #{$_[0]} ;$i+2)
{
#data= #{$_[$i]};
$title=$_[$i+1];
%hash =%{$data[$i]};
# print Dumper \%hash;
foreach my $key (sort { $a <=> $b} keys %hash) {
push (#x,$key);
push (#y,$hash{$key});
}
print Dumper #x;
}}
Output:
$VAR1 = [
{
'84' => 2,
'11' => 2,
'53' => 2,
'3' => 2,
'-46' => 2,
'14' => 2,
'-7' => 2,
'47' => 2,
'-10' => 2,
'0' => 2,
'72' => 2,
'-2' => 2
},
'1_-2_-2'
];
here is the #x infinite loop
...$VAR21817 = '-46';
$VAR21818 = '-10';
$VAR21819 = '-7';
$VAR21820 = '-2';
$VAR21821 = '0';
$VAR21822 = '3';
$VAR21823 = '11';
$VAR21824 = '14';
$VAR21825 = '47';
$VAR21826 = '53';
$VAR21827 = '72';
$VAR21828 = '84';
$VAR21829 = '-46';
$VAR21830 = '-10';
$VAR21831 = '-7';
$VAR21832 = '-2';
$VAR21833 = '0';
$VAR21834 = '3';
$VAR21835 = '11';
$VAR21836 = '14';
$VAR21837 = '47';
$VAR21838 = '53';
$VAR21839 = '72';
$VAR21840 = '84';
$VAR21841 = '-46';
$VAR21842 = '-10';
$VAR21843 = '-7';
$VAR21844 = '-2';
$VAR21845 = '0';
$VAR21846 = '3';
$VAR21847 = '11';
$VAR21848 = '14';
$VAR21849 = '47';
$VAR21850 = '53';
$VAR21851 = '72';
$VAR21852 = '84';
Where is the problem and how to fix ?
Thanks in advance

The infinite loop is because of this line
for ($i=0;$i<scalar #{$_[0]} ;$i+2)
# ^^^^---- here
This is the loop incrementor, and you are just feeding it a number, not incrementing $i. This part should of course be $i += 2.
This is a rather poor setup, though. I assume \#charts looks something like this
$VAR1 = [
{
'a' => 1,
'b' => 2
},
'foo',
{
'y' => 13,
'x' => 12
},
'bar'
];
What you should do is keep the sets together
$VAR1 = [
{
'title' => 'foo',
'data' => {
'a' => 1,
'b' => 2
}
},
{
'title' => 'bar',
'data' => {
'y' => 13,
'x' => 12
}
}
];
That way you can simply assign directly, and use a simple loop
for my $href (#_) {
my $title = $href->{title};
my %hash = %{ $href->{data} };
....
}
You should also add
use strict;
use warnings;
And declare your variables in the smallest scope possible, such as inside a loop in a subroutine.

Related

Converting HoA to HoH with counting

Have this code:
use 5.020;
use warnings;
use Data::Dumper;
my %h = (
k1 => [qw(aa1 aa2 aa1)],
k2 => [qw(ab1 ab2 ab3)],
k3 => [qw(ac1 ac1 ac1)],
);
my %h2;
for my $k (keys %h) {
$h2{$k}{$_}++ for (#{$h{$k}});
}
say Dumper \%h2;
produces:
$VAR1 = {
'k1' => {
'aa2' => 1,
'aa1' => 2
},
'k3' => {
'ac1' => 3
},
'k2' => {
'ab1' => 1,
'ab3' => 1,
'ab2' => 1
}
};
Is possible to write the above code with "another way"? (e.g. simpler or more compact)?
Honestly, I don't like the number of times $h2{$k} is evaluated.
my %h2;
for my $k (keys %h) {
my $src = $h{$k};
my $dst = $h2{$k} = {};
++$dst->{$_} for #$src;
}
A subroutine can help make the intent more obvious. Maybe.
sub counts { my %c; ++$c{$_} for #_; \%c }
$h2{$_} = counts(#{ $h{$_} }) for keys %h;
That can be simplified if you do the change in-place.
sub counts { my %c; ++$c{$_} for #_; \%c }
$_ = counts(#$_) for values %h;

Perl: Sorting hash of hash by value descending order

data :
%HoH => (
abc => {
value => "12",
},
xyz => {
number => "100",
},
pqr => {
digit => "5",
}
)
How do I sort the hash of hash by value in descending order?
Output
100
12
5
You can't sort a hash, it won't hold the order. If you wanted to keep them sorted, you'll have to sort the keys based on the number and store the keys in an array.
#!/usr/bin/perl
use strict;
use warnings;
my %HoH = (
abc => { value => 12 },
xyz => { value => 100},
pqr => { value => 5},
def => { value => 15},
hij => { value => 30},
);
my #sorted_keys = map { $_->[0] }
sort { $b->[1] <=> $a->[1] } # use numeric comparison
map { my $temp;
if ( exists $HoH{$_}{'value'} ) {
$temp = $HoH{$_}{'value'};
} elsif ( exists $HoH{$_}{'number'} ) {
$temp = $HoH{$_}{'number'};
} elsif ( exists $HoH{$_}{'digit'} ) {
$temp = $HoH{$_}{'digit'};
} else {
$temp = 0;
}
{[$_, $temp]} }
(keys %HoH);
for my $key (#sorted_keys) {
my $temp;
if ( exists $HoH{$key}{'value'} ) {
$temp = $HoH{$key}{'value'};
} elsif ( exists $HoH{$key}{'number'} ) {
$temp = $HoH{$key}{'number'};
} elsif ( exists $HoH{$key}{'digit'} ) {
$temp = $HoH{$key}{'digit'};
} else {
$temp = 0;
}
print $key . ":" . $temp ."\n";
}
Output:
xyz:100
hij:30
def:15
abc:12
pqr:5
This technique to do the sorting is called Schwartzian Transform.
Given you're not actually using the keys for anything, you can flatten the data structure into a single array and then sort it:
use strict;
use warnings;
my %HoH = (
abc => {value => "12",},
xyz => {number => "100",},
pqr => {digit => "5",},
);
my #numbers = sort {$b <=> $a} map {values %$_} values %HoH;
print "$_\n" for #numbers;
Outputs:
100
12
5
However, if you want to use the additional key information, then you'll need fold your Hash of Hash into an array, and then you can sort however you like:
my #array;
while (my ($k, $ref) = each %HoH) {
while (my ($k2, $v) = each %$ref) {
push #array, [$k, $k2, $v];
}
}
#array = sort {$b->[2] <=> $a->[2]} #array;
use Data::Dump;
dd \#array;
Outputs:
[
["xyz", "number", 100],
["abc", "value", 12],
["pqr", "digit", 5],
]
I came up with this solution
#!/usr/bin/perl
use strict;
use warnings;
my %HoH = (
abc => {
value => "12",
},
xyz => {
number => "100",
},
pqr => {
digit => "5",
}
);
my %rever;
for my $TopKey(keys %HoH){
for my $value(values %{ $HoH{$TopKey} }){
push #{ $rever{$value} }, $TopKey;
}
}
my #nums = sort {$b <=> $a} (keys(%rever));
print $_, "\n" for #nums;
I reversed the values in case you still needed to use the key names.
This is how it looks after using Dumper.
$VAR1 = '100';
$VAR2 = [
'xyz'
];
$VAR3 = '12';
$VAR4 = [
'abc'
];
$VAR5 = '5';
$VAR6 = [
'pqr'
];

Get value from hash of hashes

I would like to get value from hash of hashes but i do not. My code is :
sub test {
my $filename = $_[0];
open INFILE, ${filename} or die $!;
my %hashCount;
my #firstline = split('\t',<INFILE>);
shift(#firstline);
while (my $line = <INFILE>)
{
my %temp;
chomp($line);
my #line = split('\t', $line);
foreach my $cpt (1..$#line) {
$temp{$firstline[$cpt-1]}=$line[$cpt];
}
$hashCount{$line[0]}={%temp};
}
return %hashCount;
}
sub get_hash_of_hash {
my $h = shift;
foreach my $key (keys %$h) {
if( ref $h->{$key}) {
get_hash_of_hash( $h->{$key} );
}
else {
say $h->{$key};
}
}
}
And when i display my hash :
$VAR10679 = 'M00967_43_1106_2493_14707';
$VAR10680 = {
'A' => '1',
'B' => '0',
'C' => '1',
'D' => '0',
'E' => '0'
};
My first function return my hash of hashes and i get my specific value with the second function.
So I want to get value like that :
my %hashTest = test("FILE.txt");
get_hash_of_hash(%hashTest,"M00967_43_1106_2493_14707","A")
//return value '1'
You can either access nested elements like
$hash{keyA}{keyB}
or we can write a function that walks the data structure, like
sub walk {
my ($hashref, #keys) = #_;
my $pointer = $hashref;
for my $key (#keys) {
if (exists $pointer->{$key}) {
$pointer = $pointer->{$key};
} else {
die "No value at ", join "->", #keys;
}
}
return $pointer;
}
which can be used like
my %hash = (
'M00967_43_1106_2493_14707' => {
'A' => '1',
'B' => '0',
'C' => '1',
'D' => '0',
'E' => '0'
},
);
say walk(\%hash, 'M00967_43_1106_2493_14707', 'A');
Note: When using Data::Dumper, pass references to the Dump function:
print Dump \%hash; # not print Dump %hash
This is neccessary to show the correct data structure.
Your hash holds references to hashes.
You can access them like this:
$hashTest{'M00967_43_1106_2493_14707'}{'A'};
See perlref for more info
Use this subroutine..
sub get_hash_of_hash {
my $h = shift;
foreach my $key (keys %$h) {
if( ref $h->{$key}) {
get_hash_of_hash( $h->{$key} );
}
else {
print $h->{$key};
}
}
}

odd number of elements in anonymous hash

I'm trying to understand this Perl code...
If there is one stream it works, if there are 2 or more streams it warns with odd number of elements in anonymous hash. It seems to return an array in that case. How do I add the array elements correctly to #streams? It appears to add correctly for the HASH case in the if clause. Is the else clause bunk?
my $x = $viewedProjectDataObj->{streams};
if (ref($x) eq 'HASH') {
push(#streams, $x->{id});
} elsif (ref($x) eq 'ARRAY') {
print "$x\n";
print "#$x\n";
my #array = #$x;
foreach my $obj (#array) {
print "in $obj\n";
print Dumper( $obj);
push(#streams, ($obj->{id}) );
}
}
print "streamcount " . #streams % 2;
print Dumper(#streams);
my $stream_defect_filter_spec = {
'streamIdList' => #streams,
'includeDefectInstances' => 'true',
'includeHistory' => 'true',
};
my #streamDefects = $WS->get_stream_defects($defectProxy, \#cids, $stream_defect_filter_spec);
print Dumper(#streamDefects);
I'm adding the next lines...
if ($defectSummary->{owner} eq "Various") {
foreach (#streamDefects) {
if (exists($_->{owner})) {
$defectSummary->{owner} = $_->{owner};
last;
}
}
}
my $diref = $streamDefects[0]->{defectInstances};
if ($diref) {
my $defectInstance;
if (ref($diref) eq 'HASH') {
$defectInstance = $diref;
} elsif (ref($diref) eq 'ARRAY') {
$defectInstance = #{$diref}[0];
} else {
die "Unable to handle $diref (".ref($diref).")";
}
It now errors with
Web API returned error code S:Server: calling getStreamDefects: No stream found
for name null.
$VAR1 = -1;
me
Can't use string ("-1") as a HASH ref while "strict refs" in use at xyz-handler.pl line 317.
some Dumper output
$VAR1 = {
'streamIdList' => [
{
'name' => 'asdfasdfadsfasdfa'
},
{
'name' => 'cpp-62bad47d63cfb25e76b29a4801c61d8d'
}
],
'includeDefectInstances' => 'true',
'includeHistory' => 'true'
};
The list assigned to a hash is a set of key/value pairs, which is why the number of elements must be even.
Because the => operator is little more than a comma, and the #streams array is flattened in the list, this
my $stream_defect_filter_spec = {
'streamIdList' => #streams,
'includeDefectInstances' => 'true',
'includeHistory' => 'true',
};
is equivalent to this
my $stream_defect_filter_spec = {
'streamIdList' => $streams[0],
$streams[1] => $streams[2],
$streams[3] => $streams[4],
...
'includeDefectInstances' => 'true',
'includeHistory' => 'true',
};
so I hope you can see that you will get the warning if you have an even number of elements in the array.
To fix things you need the value of the hash element to be an array reference, which is a scalar and won't upset the scheme of things
my $stream_defect_filter_spec = {
'streamIdList' => \#streams,
'includeDefectInstances' => 'true',
'includeHistory' => 'true',
};
that way you can access the array elements as
$stream_defect_filter_spec->{streamIdList}[0]
etc.
And by the way you can tidy up your code substantially by letting map do what it's good at:
if (ref $x eq 'HASH') {
push #streams, $x->{id};
}
elsif (ref $x eq 'ARRAY') {
push #streams, map $_->{id}, #$x;
}
The assignment in:
my $stream_defect_filter_spec = {
'streamIdList' => #streams, # <---- THIS ONE
'includeDefectInstances' => 'true',
'includeHistory' => 'true',
};
is not correct, you get hash keys from the 1 3 5th ... array element.
You probably want assign a reference to array, not the array itself:
'streamIdList' => \#streams,
example for the unwanted (as in your code):
use strict;
use warnings;
use Data::Dump;
my #z = qw(a b c x y z);
dd \#z;
my $q = {
'aa' => #z,
};
dd $q;
unwanted result:
["a", "b", "c", "x", "y", "z"]
Odd number of elements in anonymous hash at a line 12.
{ aa => "a", b => "c", x => "y", z => undef }
^-here
Example of assign a reference
use strict;
use warnings;
use Data::Dump;
my #z = qw(a b c x y z);
dd \#z;
my $q = {
'aa' => \#z,
};
dd $q;
produces:
["a", "b", "c", "x", "y", "z"]
{ aa => ["a", "b", "c", "x", "y", "z"] }
The difference is clearly visible.

Perl Working On Two Hash References

I would like to compare the values of two hash references.
The data dumper of my first hash is this:
$VAR1 = {
'42-MG-BA' => [
{
'chromosome' => '19',
'position' => '35770059',
'genotype' => 'TC'
},
{
'chromosome' => '2',
'position' => '68019584',
'genotype' => 'G'
},
{
'chromosome' => '16',
'position' => '9561557',
'genotype' => 'G'
},
And the second hash is similar to this but with more hashes in the array. I would like to compare the genotype of my first and second hash if the position and the choromosome matches.
map {print "$_= $cave_snp_list->{$_}->[0]->{chromosome}\n"}sort keys %$cave_snp_list;
map {print "$_= $geno_seq_list->{$_}->[0]->{chromosome}\n"}sort keys %$geno_seq_list;
I could do that for the first array of the hashes.
Could you help me in how to work for all the arrays?
This is my actual code in full
#!/software/bin/perl
use strict;
use warnings;
use Getopt::Long;
use Benchmark;
use Config::Config qw(Sequenom.ini);
useDatabase::Conn;
use Data::Dumper;
GetOptions("sam=s" => \my $sample);
my $geno_seq_list = getseqgenotypes($sample);
my $cave_snp_list = getcavemansnpfile($sample);
#print Dumper($geno_seq_list);
print scalar %$geno_seq_list, "\n";
foreach my $sam (keys %{$geno_seq_list}) {
my $seq_used = $geno_seq_list->{$sam};
my $cave_used = $cave_snp_list->{$sam};
print scalar(#$geno_seq_list->{$_}) if sort keys %$geno_seq_list, "\n";
print scalar(#$cave_used), "\n";
#foreach my $seq2com (# {$seq_used } ){
# foreach my $cave2com( # {$cave_used} ){
# print $seq2com->{chromosome},":" ,$cave2com->{chromosome},"\n";
# }
#}
map { print "$_= $cave_snp_list->{$_}->[0]->{chromosome}\n" } sort keys %$cave_snp_list;
map { print "$_= $geno_seq_list->{$_}->[0]->{chromosome}\n" } sort keys %$geno_seq_list;
}
sub getseqgenotypes {
my $snpconn;
my $gen_list = {};
$snpconn = Database::Conn->new('live');
$snpconn->addConnection(DBI->connect('dbi:Oracle:pssd.world', 'sn', 'ss', { RaiseError => 1, AutoCommit => 0 }),
'pssd');
#my $conn2 =Database::Conn->new('live');
#$conn2->addConnection(DBI->connect('dbi:Oracle:COSI.world','nst_owner','nst_owner', {RaiseError =>1 , AutoCommit=>0}),'nst');
my $id_ind = $snpconn->execute('snp::Sequenom::getIdIndforExomeSample', $sample);
my $genotype = $snpconn->executeArrRef('snp::Sequenom::getGenotypeCallsPosition', $id_ind);
foreach my $geno (#{$genotype}) {
push #{ $gen_list->{ $geno->[1] } }, {
chromosome => $geno->[2],
position => $geno->[3],
genotype => $geno->[4],
};
}
return ($gen_list);
} #end of sub getseqgenotypes
sub getcavemansnpfile {
my $nstconn;
my $caveman_list = {};
$nstconn = Database::Conn->new('live');
$nstconn->addConnection(
DBI->connect('dbi:Oracle:CANP.world', 'nst_owner', 'NST_OWNER', { RaiseError => 1, AutoCommit => 0 }), 'nst');
my $id_sample = $nstconn->execute('nst::Caveman::getSampleid', $sample);
#print "IDSample: $id_sample\n";
my $file_location = $nstconn->execute('nst::Caveman::getCaveManSNPSFile', $id_sample);
open(SNPFILE, "<$file_location") || die "Error: Cannot open the file $file_location:$!\n";
while (<SNPFILE>) {
chomp;
next if /^>/;
my #data = split;
my ($nor_geno, $tumor_geno) = split /\//, $data[5];
# array of hash
push #{ $caveman_list->{$sample} }, {
chromosome => $data[0],
position => $data[1],
genotype => $nor_geno,
};
} #end of while loop
close(SNPFILE);
return ($caveman_list);
}
The problem that I see is that you're constructing a tree for generic storage of data, when what you want is a graph, specific to the task. While you are constructing the record, you could also be constructing the part that groups data together. Below is just one example.
my %genotype_for;
my $record
= { chromosome => $data[0]
, position => $data[1]
, genotype => $nor_geno
};
push #{ $gen_list->{ $geno->[1] } }, $record;
# $genotype_for{ position }{ chromosome }{ name of array } = genotype code
$genotype_for{ $data[1] }{ $data[0] }{ $sample } = $nor_geno;
...
return ( $caveman_list, \%genotype_for );
In the main line, you receive them like so:
my ( $cave_snp_list, $geno_lookup ) = getcavemansnpfile( $sample );
This approach at least allows you to locate similar position and chromosome values. If you're going to do much with this, I might suggest an OO approach.
Update
Assuming that you wouldn't have to store the label, we could change the lookup to
$genotype_for{ $data[1] }{ $data[0] } = $nor_geno;
And then the comparison could be written:
foreach my $pos ( keys %$small_lookup ) {
next unless _HASH( my $sh = $small_lookup->{ $pos } )
and _HASH( my $lh = $large_lookup->{ $pos } )
;
foreach my $chrom ( keys %$sh ) {
next unless my $sc = $sh->{ $chrom }
and my $lc = $lh->{ $chrom }
;
print "$sc:$sc";
}
}
However, if you had limited use for the larger list, you could construct the specific case
and pass that in as a filter when creating the longer list.
Thus, in whichever loop creates the longer list, you could just go
...
next unless $sample{ $position }{ $chromosome };
my $record
= { chromosome => $chromosome
, position => $position
, genotype => $genotype
};
...