convert hash to array of hashes in Perl - arrays

How simply convert a hash to an array of key/value ?
my %h;
%h{1} = 11;
%h{2} = 22;
and I want an array #result which I could represent as : [ { 1 => 11 }, { 2 => 22} ] (yes like in json to be clear)

That's an easy one.
my #h = map { { $_ => $h{$_} } } keys %h;

You could also use the built in List::Util library's excellent pairmap function.
use List::Util qw< pairmap >;
my #array_of_pairs = pairmap { { $a => $b } } %some_hash;
Even if you don't use this function today, take some time to check out List::Util, there is a lot of good stuff in there.

Related

Programmatically change sorting order in Perl

I'd like to give the user the possibility to change the sorting order (asc / desc) in a data structure. As far as I know, this is done changing the order of $a and $bin the code, but I'd like to programmatically change this to avoid redundant code.
I made a working example:
use 5.018;
use warnings;
# Supply any argument to change sorting order
my $sorting_direction = $ARGV[0];
my $data = {
'item1' => {
'min' => 4,
'size' => 825,
'max' => 256,
},
'item2' => {
'min' => 4,
'size' => 130,
'max' => 65,
},
};
if (defined $sorting_direction) {
foreach my $item (sort { $$data{$a}{'size'} <=> $$data{$b}{'size'} } keys %{$data} ) {
say "$item\t", $$data{$item}{'size'};
}
} else {
foreach my $item (sort { $$data{$b}{'size'} <=> $$data{$a}{'size'} } keys %{$data} ) {
say "$item\t", $$data{$item}{'size'};
}
}
Giving any parameter will change the sorting_direction. Can I do this without the if conditional?
As <=> has a value of -1, 0 or 1, you can multiply with -1 to get the opposite sorting order.
So if your $sorting_direction is 1 or -1 use
$sorting_direction * ( $$data{$a}{'size'} <=> $$data{$b}{'size'} )
A generic solution is to use different compare functions.
my %sorters = (
by_size_asc => sub { $data->{$a}{size} <=> $data->{$b}{size} },
by_size_desc => sub { $data->{$b}{size} <=> $data->{$a}{size} },
# ...
);
#ARGV
or die("usage\n");
my $sorter = $sorters{$ARGV[0]}
or die("Invalid sort function \"$ARGV[0]\".\n");
my #sorted_keys = sort $sorter keys(%$data);
You could also use different sort functions, such as when using the great Sort::Key module.
use Sort::Key qw( ikeysort rikeysort );
my %sorters = (
by_size_asc => sub { ikeysort { $data->{$_}{size} } #_ },
by_size_desc => sub { rikeysort { $data->{$_}{size} } #_ },
# ...
);
#ARGV
or die("usage\n");
my $sorter = $sorters{$ARGV[0]}
or die("Invalid sort function \"$ARGV[0]\".\n");
my #sorted_keys = $sorter->( keys(%$data) );
While it's always going to be slower because it's a full extra operation, if performance is not as much a concern as code cleanliness you could just reverse the list when the opposite sorting direction is chosen. Note that this would be slightly different in the case of sorting equal elements, as sort in Perl is normally stable (equal elements stay in the same order they originally were).
my #sorted = sort { $$data{$a}{'size'} <=> $$data{$b}{'size'} } keys %{$data};
#sorted = reverse #sorted if $reverse;

Perl: find the max and min values in the array of hashes

I have the structure below in Perl:
#!/usr/bin/perl
use strict;
use warnings;
my %hash = (
'firstitem' => {
'1' => ["A","99"],
'2' => ["B","88"],
'3' => ["C","77"],
},
'seconditem' => {
'3' => ["C","100"],
'4' => ["D","200"],
'5' => ["E","300"],
},
);
I am looking for a way to find the max number and min number in the array of each hash.
So the output will be
firstitem: max:99, min:77
seconditem: max:300, min:100
My idea is sorting the secondary key first and then do the bubble sort or other sorts in the for loop. It looks like not very elegant and smart.
foreach my $k1 (keys %hash) {
my $second_hash_ref = $hash{$k1};
my #sorted_k2 = sort { $a <=> $b } keys %{$second_hash_ref};
foreach my $i (0..$#sorted_k3){
#bubble sort or other sort
}
}
List::Util is a core module that provides the min and max functions:
use strict;
use warnings;
use List::Util qw(min max);
my %hash = (
'firstitem' => {
'1' => ["A","99"],
'2' => ["B","88"],
'3' => ["C","77"],
},
'seconditem' => {
'3' => ["C","100"],
'4' => ["D","200"],
'5' => ["E","300"],
},
);
for my $key (keys(%hash)) {
my #numbers = map { $_->[1] } values(%{$hash{$key}});
printf("%s: max: %d, min: %d\n", $key, max(#numbers), min(#numbers));
}
Output:
firstitem: max: 99, min: 77
seconditem: max: 300, min: 100
You are almost there.
Once you get to the second level what you need is the whole list of numbers, so to be able to find max and min of the lot. Since you need the extreme values for all arrays within the subkey you don't need to iterate over the second-level keys.
Also, you can fetch all array content unless the number is guaranteed to be at the specific place. Then use grep with Scalar::Util::looks_like_number to filter out elements that aren't numbers.
Finally, there is no benefit in sorting the keys.
use warnings;
use strict;
use feature 'say';
use Scalar::Util 'looks_like_number';
use List::MoreUtils 'minmax';
my %hash = (
'firstitem' => {
'1' => ["A","99"], '2' => ["B","88"], '3' => ["C","77"],
},
'seconditem' => {
'3' => ["C","100"], '4' => ["D","200"], '5' => ["E","300"],
},
);
foreach my $k1 (keys %hash)
{
my #nums =
grep { looks_like_number($_) }
map { #{ $hash{$k1}{$_} } }
keys %{$hash{$k1}};
my ($min, $max) = minmax #nums;
say "$k1: max = $max, min = $min";
}
This prints the expected values. If your real hash is exactly as shown you can extract arrayrefs directly by values %{$hash{$k1}}, with map { #$_ } to dereference them.
Since you need both extremes a good fit is minmax from List::MoreUtils, which is "the most efficient possible algorithm" for the job. The algorithm's performance is fully realized in the XS version of the module while the Perl version has some overhead.
Also note the core List::Util module, where individual min and max are, among other utilities.

How do I append a new hash to an array of hashes?

If I wanted to add a new hash to all the arrays in the mother_hash using a loop, what would be the syntax?
My hash:
my %mother_hash = (
'daughter_hash1' => [
{
'e' => '-4.3',
'seq' => 'AGGCACC',
'end' => '97',
'start' => '81'
}
],
'daughter_hash2' => [
{
'e' => '-4.4',
'seq' => 'CAGT',
'end' => '17',
'start' => '6'
},
{
'e' => '-4.1',
'seq' => 'GTT',
'end' => '51',
'start' => '26'
},
{
'e' => '-4.1',
'seq' => 'TTG',
'end' => '53',
'start' => '28'
}
],
#...
);
If you have a hash of arrays of hashes and want to add a new hash to
the end of each of the arrays, you can do:
push #{ $_ }, \%new_hash for (values %mother_hash);
This loop iterates over the values of %mother_hash (which are array refs in this case) and setting $_ for each iteration. Then in each iteration, we push the reference to the new hash %new_hash to the end of that array.
First I would point out the daughter hashes aren't hashes but arrays of anonymous hashes. To add another daughter hash:
$mother_hash{daughter_hash3} = [ { %daughter_hash3 } ];
This creates an anonymous array that contains an anonymous hash with the contents of %daughter_hash3.
For a loop:
$mother_hash{$daughter_hash_key} = [ { %daughter_hash } ];
where $daughter_hash_key is a string contain the key for the %mother_hash and %daughter_hash is the hash to add.
To add another hash to a daughter array with key $daughter_hash_key:
push #{ $mother_hash{$daughter_hash_key} }, { %daughter_hash };
I know ti's complicated but I suggest you use Data::Dumper to dump the contents of %mother_hash each time thru the loop to see if it grows correctly.
use Data::Dumper;
print Dumper \%mother_hash;
See perldoc Data::Dumper for details..
Data::Dumper is a standard module that comes with Perl. For a list of standard modules, see perldoc perlmodlib.
mother_hash is a hash of arrays of hashes.
To add another top-level array of hashes.
%mother_hash{$key} = [ { stuff }, { stuff } ];
To add another entry to an existing array
push #{%mother_hash{'key'}} { stuff };
To add another entry to the hash in the embedded array
%{#{%mother_hash{'top_key'}}[3]}{'new_inner_key'} = value;
When confused and attempting to match up the "types" of hash / array / scalar containing a hash reference / array reference, you can use the following technique
use Data::Dumper;
$Data::Dumper::Terse = 1;
printf("mother_hash reference = %s\n", Dumper(\%mother_hash));
printf("mother_hash of key 'top_key' = %s\n", Dumper(%mother_hash{top_key}));
and so on to find your way through a large data structure and validate that you are narrowing down to the region you want to access or alter.

Grep not returning expected value Perl

I'm trying to grep a value in Perl:
#array = ['hello', 'world'];
if (grep(/hello/i, #array) {
# do something
}
For some reason my code isn't picking this up. Perhaps there's another way I can do this.
the Array itself is inside a hash:
hash => {
array => ['hello', 'world'],
value => 'feature',
}
You're building your array wrong. This line creates a one-element array, and that element is an array reference.
#array = ['hello', 'world'];
When you grep over that one-element array, that one array reference doesn't match /hello/i.
What you want is:
#array = ('hello', 'world');
After
#array = ['hello', 'world'];
you have:
$ perl -MData::Dumper -e '#array = ['hello', 'world']; print Dumper \#array'
$VAR1 = [
[
'hello',
'world'
]
];
That is #array contains a reference to an anonymous array containing the strings 'hello' and 'world'.
Then, in grep, you evaluate this reference as a string. Therefore, your grep does a single comparison along the lines of
'ARRAY(0x7fa0e38032b8)' =~ /hello/i;
Clearly, that is not going to match.
#!/usr/bin/env perl
use strict;
use warnings;
my %hash = (array => ['hello', 'world']);
if (grep /hello/i, #{ $hash{array} }) {
print "\#array contains 'hello'\n";
}
The usage is indeed
if (grep(/hello/i, #array)) { ... }
But according to the comments, you don't have a named array. You have a reference to an array. As such, you replace #array with an array dereference.
if (grep(/hello/i, #$array_ref)) { ... }
That's short for
if (grep(/hello/i, #{ $array_ref })) { ... }
Since your reference comes from a hash, you could also do
if (grep(/hello/i, #{ $hash{$key} })) { ... }
The Array itself is inside a hash:
hash => {
array => ['hello', 'world'],
value => 'feature',
}
Use Data::Dumper to see how exactly you've defined your structure:
use Data::Dumper;
use feature qw(say); # Highly recommend "say" when using Data::Dumper!
my %hash = (
array => ['hello', 'world'],
value => 'feature',
);
...
say Dumper \%hash;
And see what prints out. (Note the backslash in front, so you're passing in a single reference_ and not a list of values).
What you'll see is something like this:
$var = {
'array' => [
'hello',
'world',
]
}
That array isn't just an array, it's a reference to an array. You need to dereference it to get it to work:
if ( grep {/hello/i } #{ $hash->{array} } )
Or...
my #array = #{ $hash->{array} );
if ( grep { /hello/i } #array;

Generating a unordered list hash from a array

I am trying to use hashes to generate an unordered list that i can further use in a jstree. But this array has to be generated only from an array that has been passed thru .
my #array = ( "New Order","Recurring Order","Previously Cancelled Order");
I want the output to look something like
$data = {
"New Order" => {
"Recurring Order" =>{
Previously cancelled Order = 1
}
}
};
I can simply do
my $data{$array[0]}{$array[1]}{$array[2]} = 1
but the array can be of n variables, so it becomes a bit more complicated than that. I am thinking of recursion, but i have been sitting here for the last hour trying to figure that out
This will generate the data structure as you have defined it. Not sure why you'd want it though.
my #input = ( "New Order","Recurring Order","Previously Cancelled Order");
my $data = 1;
$data = {$_ => $data} for reverse #input;
use Data::Dump;
dd $data;
If you're just wanting to randomize your array, then use List::Util;
use List::Util qw(shuffle);
my #newOrder = shuffle #input;
sub recursive {
my $v = shift #_;
return #_>1 ? { $v => recursive(#_) } : { $v => #_ };
}
my #array = ( "New Order","Recurring Order","Previously Cancelled Order");
use Data::Dumper; print Dumper recursive(#array, 1);
output
$VAR1 = {
'New Order' => {
'Recurring Order' => {
'Previously Cancelled Order' => 1
}
}
};

Resources