Advanced sorting of arrays - arrays

I'm having difficulty alphabetically sorting the output of my Perl script.
Here's the script:
#!/usr/bin/perl
use strict;
use warnings;
use English; ## use names rather than symbols for special variables
my $dir = '/Users/jdm/Desktop/xampp/htdocs/cnc/images/plants';
opendir my $dfh, $dir or die "Can't open $dir: $OS_ERROR";
my %genus_species; ## store matching entries in a hash
for my $file (readdir $dfh)
{
next unless $file =~ /.png$/; ## entry must have .png extension
my $genus = $file =~ s/\d*\.png$//r;
push(#{$genus_species{$genus}}, $file); ## push to array,the #{} is to cast the single entry to a reference to an list
}
for my $genus (keys %genus_species)
{
print "$genus = ";
print "$_ " for sort #{$genus_species{$genus}}; # sort and loop though entries in list reference
print "\n";
}
Here's the un-sorted output:
Veronica_chamaedrys = Veronica_chamaedrys.png
Cardamine_douglassii = Cardamine_douglassii1.png Cardamine_douglassii2.png
Filipendula_rubra = Filipendula_rubra1.png Filipendula_rubra2.png
Taxodium_distichum = Taxodium_distichum.png
Asplenium_platyneuron = Asplenium_platyneuron1.png Asplenium_platyneuron2.png Asplenium_platyneuron3.png
Here's the desired sorted output:
Asplenium_platyneuron = Asplenium_platyneuron1.png Asplenium_platyneuron2.png Asplenium_platyneuron3.png
Cardamine_douglassii = Cardamine_douglassii1.png Cardamine_douglassii2.png
Filipendula_rubra = Filipendula_rubra1.png Filipendula_rubra2.png
Taxodium_distichum = Taxodium_distichum.png
Veronica_chamaedrys = Veronica_chamaedrys.png
Please advise. Thanks.

Replace
for my $genus (keys %genus_species)
with
for my $genus (sort keys %genus_species)
If your numbers are going to reach 10, you'll want to use natsort from Sort::Keys::Natural (1, 2, ..., 9, 10, ...) instead of the builtin sort (1, 10, 11, ..., 2, ...), at least for the one sorting the file names.

Related

Creating a JSON array containing multiple variables

I want to modify my Perl script to output a list of variables using the json_encode function, but I'm not sure how.
Here's the output of my unmodified Perl script:
Vicia_sativa = Vicia_sativa.png
Geranium_maculatum = Geranium_maculatum.png
Narcissus_pseudonarcissus = Narcissus_pseudonarcissus1.png Narcissus_pseudonarcissus2.png
Polygonum_persicaria = Polygonum_persicaria1.png Polygonum_persicaria2.png
Corylus_americana = Corylus_americana1.png Corylus_americana2.png
The variables to the left of the equal signs are plant names, and the one or more file names to the right of the equal signs are plant photos. Notice that these file names are not separated by commas.
Here's my Perl script that generated the above output:
#!/usr/bin/perl
use strict;
use warnings;
use English; ## use names rather than symbols for special variables
my $dir = '/Users/jdm/Desktop/xampp/htdocs/cnc/images/plants';
opendir my $dfh, $dir or die "Can't open $dir: $OS_ERROR";
my %genus_species; ## store matching entries in a hash
for my $file (readdir $dfh)
{
next unless $file =~ /.png$/; ## entry must have .png extension
my $genus = $file =~ s/\d*\.png$//r;
push(#{$genus_species{$genus}}, $file); ## push to array,the #{} is to cast the single entry to a reference to an list
}
for my $genus (keys %genus_species)
{
print "$genus = ";
print "$_ " for sort #{$genus_species{$genus}}; # sort and loop though entries in list reference
print "\n";
}
Please advise how to output these variables in a JSON array. Thanks.
Update... Here's the revised script with the recommended changes per a forum member:
#!/usr/bin/perl
use strict;
use warnings;
use JSON::PP;
use English; ## use names rather than symbols for special variables
my $dir = '/Users/jdm/Desktop/xampp/htdocs/cnc/images/plants';
opendir my $dfh, $dir or die "Can't open $dir: $OS_ERROR";
my %genus_species; ## store matching entries in a hash
for my $file (readdir $dfh)
{
next unless $file =~ /.png$/; ## entry must have .png extension
my $genus = $file =~ s/\d*\.png$//r;
push(#{$genus_species{$genus}}, $file); ## push to array,the #{} is to cast the single entry to a reference to an list
}
print(encode_json(\%genus_species));
This revised code works! However, the file names are no longer sorted. Any ideas how to incorporate sort into the encode_json function?
Pretty straight forward actually... You can use the JSON::PP module and pass a reference of your hash to encode_json().
#!/usr/bin/perl
use strict;
use warnings;
use JSON::PP;
# your other code goes here...
# instead of `for my $genus (keys %genus_species) { ... }` do:
print(encode_json(\%genus_species));

Need json_encode to return sorted array

I need json_encode to return a sorted array, but can't figure out how.
Here's my Perl script:
#!/usr/bin/perl
use strict;
use warnings;
use JSON::PP;
use English; ## use names rather than symbols for special variables
my $dir = '/Users/jdm/Desktop/xampp/htdocs/cnc/images/plants';
opendir my $dfh, $dir or die "Can't open $dir: $OS_ERROR";
my %genus_species; ## store matching entries in a hash
for my $file (readdir $dfh)
{
next unless $file =~ /.png$/; ## entry must have .png extension
my $genus = $file =~ s/\d*\.png$//r;
push(#{$genus_species{$genus}}, $file); ## push to array,the #{} is to cast the single entry to a reference to an list
}
print "var galleryarray = "; ## HTML Variable element ( <var> )
print (encode_json(\%genus_species)); ## define array in Javascript outputting elements containing image file names
Here's part of the output, showing the unsorted elements:
var galleryarray = {"Polygonum_pensylvanicum":["Polygonum_pensylvanicum2.png","Polygonum_pensylvanicum3.png","Polygonum_pensylvanicum1.png"]
Notice that the indexed file names are unsorted numerically.
Before posting, I tried adding the following sort function below the push function:
sort(#{$genus_species{$genus}}, $file);
Unfortunately, that caused an error; specifically "Useless use of sort in void context."
Please advise how I can output a sorted array using json_encode. Thanks.
The following is the proper syntax:
#{$genus_species{$genus}} = sort #{$genus_species{$genus}};
It's inefficient to repeatedly sort the arrays. Instead, you should create a second loop after the first.
for my $genus (keys(%genus_species)) {
#{$genus_species{$genus}} = sort #{$genus_species{$genus}};
}
or
#{$genus_species{$_}} = sort #{$genus_species{$_}}
for keys(%genus_species);
But your strings have a mix of text and numbers, so a natural sort would be better.
use Sort::Key::Natural qw( natsort );
#{$genus_species{$_}} = natsort #{$genus_species{$_}}
for keys(%genus_species);
If you want to the keys to be sorted too, replace
encode_json(...)
with
JSON::PP->new->utf8->canonical->encode(...)
or the faster
Cpanel::JSON::XS->new->utf8->canonical->encode(...)
---UPDATE--- One of our forum members provided a sort expression that allows the json_encode function to return a sorted array. I want to share his solution to help others.
Here's my revised script that returns a sorted array:
#!/usr/bin/perl
use strict;
use warnings;
use JSON::PP;
use English; ## use names rather than symbols for special variables
my $dir = '/Users/jdm/Desktop/xampp/htdocs/cnc/images/plants';
opendir my $dfh, $dir or die "Can't open $dir: $OS_ERROR";
my %genus_species; ## store matching entries in a hash
for my $file (readdir $dfh)
{
next unless $file =~ /.png$/; ## entry must have .png extension
my $genus = $file =~ s/\d*\.png$//r;
push(#{$genus_species{$genus}}, $file); ## push to array,the #{} is to cast the single entry to a reference to an list
}
#{$genus_species{$_}} = sort #{$genus_species{$_}}
for keys(%genus_species);
print (JSON::PP->new->utf8->canonical->encode(\%genus_species)); ## define array in Javascript outputting elements containing image file names

Is it possible to put elements of array to hash in perl?

example of file content:
>random sequence 1 consisting of 500 residues.
VILVWRISEMNPTHEIYPEVSYEDRQPFRCFDEGINMQMGQKSCRNCLIFTRNAFAYGIV
HFLEWGILLTHIIHCCHQIQGGCDCTRHPVRFYPQHRNDDVDKPCQTKSPMQVRYGDDSD;
>random sequence 2 consisting of 500 residues.
KAAATKKPWADTIPYLLCTFMQTSGLEWLHTDYNNFSSVVCVRYFEQFWVQCQDHVFVKN
KNWHQVLWEEYAVIDSMNFAWPPLYQSVSSNLDSTERMMWWWVYYQFEDNIQIRMEWCNI
YSGFLSREKLELTHNKCEVCVDKFVRLVFKQTKWVRTMNNRRRVRFRGIYQQTAIQEYHV
HQKIIRYPCHVMQFHDPSAPCDMTRQGKRMNFCFIIFLYTLYEVKYWMHFLTYLNCLEHR;
>random sequence 3 consisting of 500 residues.
AYCSCWRIHNVVFQKDVVLGYWGHCWMSWGSMNQPFHRQPYNKYFCMAPDWCNIGTYAWK
I need an algorithm to build a hash $hash{$key} = $value; where lines starting with > are the values and following lines are the keys.
What I have tried:
open (DATA, "seq-at.txt") or die "blabla";
#data = <DATA>;
%result = ();
$k = 0;
$i = 0;
while($k != #data) {
$info = #data[$k]; #istrina pirma elementa
if(#data[$i] !=~ ">") {
$key .= #data[$i]; $i++;
} else {
$k = $i;
}
$result{$key} = $value;
}
but it doesn't work.
You don't have to previously use an array, you can directly build your hash:
use strict;
use warnings;
# ^- start always your code like this to see errors and what is ambiguous
# declare your variables using "my" to specify the scope
my $filename = 'seq-at.txt';
# use the 3 parameters open syntax to avoid to overwrite the file:
open my $fh, '<', $filename or die "unable to open '$filename' $!";
my %hash;
my $hkey = '';
my $hval = '';
while (<$fh>) {
chomp; # remove the newline \n (or \r\n)
if (/^>/) { # when the line start with ">"
# store the key/value in the hash if the key isn't empty
# (the key is empty when the first ">" is encountered)
$hash{$hkey} = $hval if ($hkey);
# store the line in $hval and clear $hkey
($hval, $hkey) = $_;
} elsif (/\S/) { # when the line isn't empty (or blank)
# append the line to the key
$hkey .= $_;
}
}
# store the last key/val in the hash if any
$hash{$hkey} = $hval if ($hkey);
# display the hash
foreach (keys %hash) {
print "key: $_\nvalue: $hash{$_}\n\n";
}
It is unclear what you want, the array seems to be the lines subsequent to the random sequence number... If the contenst of a file test.txt are:
Line 1:">"random sequence 1 consisting of 500 residues.
Line 2:VILVWRISEMNPTHEIYPEVSYEDRQPFRCFDEGINMQMGQKSCRNCLIFTRNAFAYGIV
Line 3:HFLEWGILLTHIIHCCHQIQGGCDCTRHPVRFYPQHRNDDVDKPCQTKSPMQVRYGDDSD;
You could try something like:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my $contentFile = $ARGV[0];
my %testHash = ();
my $currentKey = "";
open(my $contentFH,"<",$contentFile);
while(my $contentLine = <$contentFH>){
chomp($contentLine);
next if($contentLine eq ''); # Empty lines.
if($contentLine =~ /^"\>"(.*)/){
$currentKey= $1;
}else{
push(#{$testHash{$currentKey}},$contentLine);
}
}
print Dumper(\%testHash);
Which results in a structure like this:
seb#amon:[~]$ perl test.pl test.txt
$VAR1 = {
'random sequence 3 consisting of 500 residues.' => [
'AYCSCWRIHNVVFQKDVVLGYWGHCWMSWGSMNQPFHRQPYNKYFCMAPDWCNIGTYAWK'
],
'random sequence 1 consisting of 500 residues.' => [
'VILVWRISEMNPTHEIYPEVSYEDRQPFRCFDEGINMQMGQKSCRNCLIFTRNAFAYGIV',
'HFLEWGILLTHIIHCCHQIQGGCDCTRHPVRFYPQHRNDDVDKPCQTKSPMQVRYGDDSD;'
],
'random sequence 2 consisting of 500 residues.' => [
'KAAATKKPWADTIPYLLCTFMQTSGLEWLHTDYNNFSSVVCVRYFEQFWVQCQDHVFVKN',
'KNWHQVLWEEYAVIDSMNFAWPPLYQSVSSNLDSTERMMWWWVYYQFEDNIQIRMEWCNI',
'YSGFLSREKLELTHNKCEVCVDKFVRLVFKQTKWVRTMNNRRRVRFRGIYQQTAIQEYHV',
'HQKIIRYPCHVMQFHDPSAPCDMTRQGKRMNFCFIIFLYTLYEVKYWMHFLTYLNCLEHR;'
]
};
You would be basically using each hash "value" as an array structure, the #{$variable} does the magic.

Separating CSV file into key and array

I am new to perl, and I am trying to separate a csv file (has 10 comma-separated items per line) into a key (first item) and an array (9 items) to put in a hash. Eventually, I want to use an if function to match another variable to the key in the hash and print out the elements in the array.
Here's the code I have, which doesn't work right.
use strict;
use warnings;
my %hash;
my $in2 = "metadata1.csv";
open IN2, "<$in2" or die "Cannot open the file: $!";
while (my $line = <IN2>) {
my ($key, #value) = split (/,/, $line, 2);
%hash = (
$key => #value
);
}
foreach my $key (keys %hash)
{
print "The key is $key and the array is $hash{$key}\n";
}
Thank you for any help!
Don't use 2 as the third argument to split: it will split the line to only two elements, so there'll be just one #value.
Also, by doing %hash =, you're overwriting the hash in each iteration of the loop. Just add a new key/value pair:
$hash{$key} = \#value;
Note the \ sign: you can't store an array directly as a hash value, you have to store a reference to it. When printing the value, you have to dereference it back:
#! /usr/bin/perl
use warnings;
use strict;
my %hash;
while (<DATA>) {
my ($key, #values) = split /,/;
$hash{$key} = \#values;
}
for my $key (keys %hash) {
print "$key => #{ $hash{$key} }";
}
__DATA__
id0,1,2,a
id1,3,4,b
id2,5,6,c
If your CSV file contains quoted or escaped commas, you should use Text::CSV.
First of all hash can have only one unique key, so when you have lines like these in your CSV file:
key1,val11,val12,val13,val14,val15,val16,val17,val18,val19
key1,val21,val22,val23,val24,val25,val26,val27,val28,val29
after adding both key/value pairs with 'key1' key to the hash, you'll get just one pair saved in the hash, the one that were added to the hash later.
So to keep all records, the result you probably need array of hashes structure, where value of each hash is an array reference, like this:
#result = (
{ 'key1' => ['val11','val12','val13','val14','val15','val16','val17','val18','val19'] },
{ 'key1' => ['val21','val22','val23','val24','val25','val26','val27','val28','val29'] },
{ 'and' => ['so on'] },
);
In order to achieve that your code should become like this:
use strict;
use warnings;
my #AoH; # array of hashes containing data from CSV
my $in2 = "metadata1.csv";
open IN2, "<$in2" or die "Cannot open the file: $!";
while (my $line = <IN2>) {
my #string_bits = split (/,/, $line);
my $key = $string_bits[0]; # first element - key
my $value = [ #string_bits[1 .. $#string_bits] ]; # rest get into arr ref
push #AoH, {$key => $value}; # array of hashes structure
}
foreach my $hash_ref (#AoH)
{
my $key = (keys %$hash_ref)[0]; # get hash key
my $value = join ', ', #{ $hash_ref->{$key} }; # join array into str
print "The key is '$key' and the array is '$value'\n";
}

How to create a table using two arrays in perl

Need a help to create a table using two arrays or files in perl cgi.
I need to create a table that print the list of directories from different path then put it into table say the title on column one says path1 and column 2 path2 and so on, and each column list the directory from that path with href link..here what I do have.
opendir(D, "../abc/status") or die"$!";
my #path1_dir = sort readdir D; closedir D;
opendir(D, "../def/status") or die "$!";
my #path2_dir = sort readdir D; closedir D; .... ...
print "\n"; print "$path1_dir\n"; print "$path2_dir\n";
#print list of directories to column-1 with title Path1
foreach my $path (#path1_dir) {
print "\t\n";
next if ($path =~ /^./);
next if ($path =~ /^\s*$/);
print "$path\n";
}
#this should go to the column two with Path2 title but it does not
foreach my $path (#path2_dir) {
print "\t\n"; `enter code here`
next if ($path =~ /^./);
next if ($path =~ /^\s*$/);
print "$path\n";
}
Can someone help me on this if you can?
I think you want something like this based on your description -- ie printing in two columns
$dir1 = "../abc/status";
$dir2 = "../def/status";
opendir(D, $dir1) or die"$!";
my #path1_dir = sort grep { !/(^\.|^\s*$)/ } readdir D; closedir D;
opendir(D, $dir2) or die "$!";
my #path2_dir = sort grep { !/(^\.|^\s*$)/ } readdir D; closedir D;
print "$dir1\t$dir2\n";
# figure out which one has more files
$limit = $#path1_dir < $#path2_dir ? $#path2_dir : $#path1_dir;
# print in 2 columns
for ($i = 0; $i<=$limit; $i++) {
printf "%s\t%s\n",
($i<=$#path1_dir ? $path1_dir[$i] : ""),
($i <= $#path2_dir ? $path2_dir[$i] : ""),"\n";
}
Although it is not clear what exactly you want, I'm guessing you want to have an ls style output (only directories) for an FTP or something. The variable naming might be ambiguous, but basically what it's doing is printing out one directory from each directory at a time.
#!/usr/bin/perl
use strict;
use warnings;
my %dirs;
my #dirs_to_look = qw(../abc ../def);
my $max_num_of_dirs;
foreach my $dir (#dirs_to_look) {
# Directories that were found in $dir
my #dirs_in_dir = grep { -d } glob "$dir/*";
$max_num_of_dirs = scalar #dirs_in_dir
if not defined $max_num_of_dirs or $max_num_of_dirs < scalar #dirs_in_dir;
$dirs{$dir} = \#dirs_in_dir;
}
my #keys = sort {$a cmp $b} keys %dirs;
# Print the column titles
foreach my $key (#keys) {
printf "%15s", $key;
}
print "\n";
for (my $i = 0; $i < $max_num_of_dirs; $i++) {
foreach my $key (#keys) {
my $dir = shift #{ $dirs{$key} };
printf "%15s", $dir // "";
}
print "\n";
}

Resources