Perl, How to delete a hash in array of hashes? - arrays

I have an Array of Hashes as below.
#students= (
{
'math' => 95,
'phy' => 90,
'che' => 85
},
{
'math' => 50,
'phy' => 70,
'che' => 35
}
);
I want to delete a entire hash based on some conditions, for that i tried with below code but am getting an error saying delete argument is not a HASH or ARRAY element or slice. So please help me, how can i do?
for $i ( 0 .. $#students) {
for $key ( keys %{ $students[$i] } ) {
if ($key eq 'che') {
if ($students->{$key} == 35){
delete (%{$students[$i]});
}
}
}
}

Deleting is well suited for hash keys, but in your case you want to remove array elements so grep filtering could be applied:
#students = grep { $_->{che} != 35 } #students;

Related

How to iterate on a conditional array in Perl?

I iterate through files in Perl and I'd like to get the correct "Adresse" field in the file. "Adresse" is a hash. Either the file contains only one "Adresse", and I take it, or it contains several "Adresse" and "Adresse" is actually an Array containing several "Adresse", and I just need the one having "type" = "postale".
Here is my code:
my $ad;
my $adresse;
if(ref($doc->{'Organisme'}->{'Adresse'}) eq 'ARRAY') {
print "\nI'M AN ARRAY!\n";
foreach $ad ($doc->{'Organisme'}->{'Adresse'}) {
print Dumper $ad;
if ($ad->{'type'} == 'postale') {
my $adresse = $ad;
}
}
} else {
my $adresse = $doc->{'Organisme'}->{'Adresse'}
}
print $fd $adresse->{'Ligne'};
I get the error:
Not a HASH reference at ./scripts/actualiserDonnees.pl line 35
and line 35 is:
if ($ad->{'type'} == 'postale') {
Apparently the "foreach" doens't iterate through "$doc->{'Organisme'}->{'Adresse'}" when the latter is an array, because the Dumper gives me this:
$VAR1 = [
{
'Localisation' => {
"Pr\x{e9}cision" => '8',
'Longitude' => '1.9751304',
'Latitude' => '43.2279035'
},
'type' => 'physique',
'CodePostal' => '11270',
"Accessibilit\x{e9}" => {
'type' => 'ACC'
},
'NomCommune' => 'Laurac',
'Ligne' => 'Place Blanche-de-Laurac'
},
{
'Ligne' => '8 rue du Pont',
'CodePostal' => '11270',
'type' => 'postale',
'NomCommune' => 'Laurac'
}
];
If I didn't explain myself enough, feel free to ask questions.
Thanks in advance :)
my $adresse creates a new variable. Replace both instances of my $adresse = ... with $adresse = ...
$doc->{'Organisme'}->{'Adresse'} is a scalar (a reference to an array), so foreach $ad ($doc->{'Organisme'}->{'Adresse'}) only loops over one item (the reference to an array). You want foreach $ad (#{ $doc->{'Organisme'}->{'Adresse'} })

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;

Convert array to multidimensional hash

My task is convert array, containing hash with x keys to x-1 dimensional hash.
Example:
use Data::Dumper;
my $arr = [
{
'source' => 'source1',
'group' => 'group1',
'param' => 'prm1',
'value' => 1,
},
{
'source' => 'source1',
'group' => 'group1',
'param' => 'prm2',
'value' => 2,
},
];
my $res;
for my $i (#$arr) {
$res->{ $i->{source} } = {};
$res->{ $i->{source} }{ $i->{group} } = {};
$res->{ $i->{source} }{ $i->{group} }{ $i->{param} } = $i->{value};
}
warn Dumper $res;
my $res_expected = {
'source1' => {
'group1' => {
'prm1' => 1, # wasn't added, why ?
'prm2' => 2
}
}
};
However it doesn't work as expected, 'prm1' => 1 wasn't added. What is wrong and how to solve this task ?
The problem is that you are assigning to the source even if something was there, and you lose it. Just do a ||= instead of = and you'll be fine.
Or even easier, just use the fact that Perl autovivifies and leave that out.
my $res;
for my $i (#$arr) {
$res->{ $i->{source} }{ $i->{group} }{ $i->{param} } = $i->{value};
}
warn Dumper $res;
The first 2 lines in the for loop are what is causing your problem. They assign a new hash reference each iteration of the loop (and erase what was entered in the previous iteration). In perl, there is no need to set a reference as you did. Just eliminate the first 2 lines and your data structure will be as you wish.
The method you chose only shows 'prmt' => 2 because that was the last item entered.

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
}
}
};

Perl: Get parallel value in hash array

I have this:
my(%arr) = (
monsters => ["Test","Test2"],
kills => [-1, -2 ]);
Then later I search for Test2:
if ( grep { $_ eq "Test2"} #{ $arr{monsters} } )
{
#Get parallel value of Test2 (-2)
next;
}
How can I get the parallel value without knowing the index (an actual variable is used when searching and not a string literal)?
Rather than using a grep, just loop over the array and keep a count variable:
for my $idx( 0 .. $#{ $arr{monsters} } ) {
if ( $arr{monsters}[$idx] eq 'Test2' ) {
print "Kills = $arr{kills}[$idx]\n";
last;
}
}
A better way to handle this, however, might be to rethink your data structure. Instead of parallel arrays, consider an array of hashes:
my #monsters = ( { name => 'Test', kills => -1 }, { name => 'Test2', kills => -2 } );
Now, to find a specific monster:
my ( $monst ) = grep { $_->{name} eq 'Test2' } #monsters;
print $monst->{kills};
This would allow you to search by name and kills equally easily. If you are going to always search by name, then making a hash keyed on name and pointing to the number of kills (as #dmah suggests) might be better.
An even better way to handle this would be to wrap up your monsters in a class, and have each object keep track of its own kills, but I'll leave that as an exercise for the OP.
Try a hash of hashes:
my %arr = (
'Test' => {
'kills' => -1,
},
'Test2' => {
'kills' => -2,
},
);
print $arr{'Test2'}{'kills'}, "\n";

Resources