Get key details from the value through grep - perl

I am trying to find the key name as output by matching $country_value variable in grep through the hash I have.
#!/usr/bin/perl -w
use strict;
use warnings;
my $country_value = 1;
my $country = {
'IN' => [
1,
5
],
'US' => [
2,
6
],
'UK' => [
3,
7
]
};
my $country_details = grep { $_ eq $country_value } values %{$country};
print $country_details;
print "\n";
As per the hash, I need to get the output as IN because the value of IN is 1 and the $country_value is 1, which is what I am trying to find out.
But, I get the output as 0 instead of IN.
Can someone please help?

In your code, values returns a reference to an array. You need to dereference that to get a list for grep.
use warnings;
use strict;
my $country_value = 1;
my $country = {
'IN' => [
1,
5
],
'US' => [
2,
6
],
'UK' => [
3,
7
]
};
my $country_details;
for my $name (keys %{$country}) {
if (grep { $_ == $country_value } #{ $country->{$name} }) {
$country_details = $name;
last;
}
}
print $country_details, "\n";
Prints:
IN

Related

Join from a deep hashref in perl

I have a function called scrape_html what returns an array of deep hashrefs.
The next code prints the correct result:
use 5.014;
use warnings;
for my $table (scrape_html()) {
say join("\t",
$table->{tr}->[0]->{td}->[1],
$table->{tr}->[2]->{td}->[1],
$table->{tr}->[4]->{td}->[1],
);
}
it prints, for example:
r0c1 r2c1 r4c1
I want make it shorter with cleaner row numbers, like the next:
use 5.014;
use warnings;
for my $table (scrape_html()) {
say $table->{tr}->[$_]->{td}->[1] for (qw(0 2 4) );
}
This prints:
r0c1
r2c1
r4c1
The problem is - how to join it? The next code
use 5.014;
use warnings;
for my $table (scrape_html()) {
say join("\t",
(
$table->{tr}->[$_]->{td}->[1] for (qw(0 2 4) )
)
);
}
says:
syntax error at soex.pl line 7, near "] for "
Execution of soex.pl aborted due to compilation errors.
What is the correct syntax?
If someone want a demo of scrape_html
sub scrape_html {
return (
{
'tr' => [
{
'td' => [
'r0c0',
'r0c1'
]
},
{
'td' => [
'r1c0',
'r1c1',
]
},
{
'td' => [
'r2c0',
'r2c1'
]
},
{
'td' => [
'r3c0',
'r3c1'
]
},
{
'td' => [
'r4c0',
'r4c1'
]
},
{
'td' => [
'r5c0',
'r5c1'
]
},
]
}
);
}
You want to use map which returns transformed elements,
say join("\t",
(
map $table->{tr}->[$_]->{td}->[1], qw(0 2 4)
)
);

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'
];

how to assign lines of variable length to a variable in perl?

I have a file I want to read that has a variable number of ids for each location that looks like this:
loc1 id1 id4 id5 id9
loc2 id2
loc3 id1 id11 id23
I would like to store this as follows locs(loc) = {all ids belonging to that location}
So that later, when I read another file I can do something like
if (grep id, locs(loc)){do something}
I tried to do this using a hash, but this is not working. I tried:
open my $loclist, '<', $ARGV[0];
my %locs;
while (<$loclist>) {
my #loclist_rec = split;
my $loclist_loc = #rlist_rec[0];
$locs{$loclist_loc} = #loclist_rec;
}
but this isnt working.
I new to perl and still trying to understand the different datatypes.
Any ideas? Thanks a lot!
This should do what you want.
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
open my $loclist, '<', "test.txt" or die $!;
my %locs;
while (<$loclist>) {
my ($loclist_loc, #loclist_rec) = split;
$locs{$loclist_loc} = \#loclist_rec;
}
print Dumper \%locs;
OUTPUT:
$ perl test.pl
$VAR1 = {
'loc2' => [
'id2'
],
'loc1' => [
'id1',
'id4',
'id5',
'id9'
],
'loc3' => [
'id1',
'id11',
'id23'
]
};
Also a possible choice would be a hash of hashes. When you want to look up an id, you could say if ($locs{$loc}{$id}) {do something}. The data structure would be
$VAR1 = {
'loc2' => {
'id2' => 1
},
'loc1' => {
'id1' => 1,
'id5' => 1,
'id4' => 1,
'id9' => 1
},
'loc3' => {
'id1' => 1,
'id11' => 1,
'id23' => 1
}
};

Extracting CN's with LDAP?

I have this code
#!/usr/bin/perl
use warnings;
use strict;
use Net::LDAP;
use Data::Dumper;
my $dn="CN=...";
my $password="xxx";
my $ldap = Net::LDAP->new('example.com') or die "$#";
my $mesg = $ldap->bind($dn, password=>$password);
if ($mesg->code) { die "uuuu $mesg"; }
$mesg = $ldap->search(base => "dc=test,dc=example,dc=com", filter => "(name=LIST)",);
my $ref = $mesg->entry->get_value("member", asref => 1);
print Dumper $ref;
foreach my $string (#{$ref}) {
$string =~ /CN=(.+?),.*/;
print $1 . "\n";
}
which outputs the CN's using regular expressions:
aaaa
bbbb
cccc
...
Using Dumper can I see the structure
$VAR1 = [
'CN=aaaa,OU=test,DC=test,DC=example,DC=com',
'CN=bbbb,OU=test,DC=test,DC=example,DC=com',
'CN=cccc,OU=test,DC=test,DC=example,DC=com',
So I am wondering if there is a more "LDAP" way to extract these CN's, instead of using regular expressions?
Update:
Based on Javs answer this is the solution.
my $ref = $mesg->entry->get_value("member", asref => 1);
foreach my $string (#{$ref}) {
print ldap_explode_dn($string)->[0]{CN} . "\n";
}
You can:
use Net::LDAP::Util qw(ldap_explode_dn);
and use it on your attribute like this:
ldap_explode_dn($mesg->entry->get_value('member'));
to get this array of hashes:
$VAR1 = [
{
'CN' => 'aaaa'
},
{
'OU' => 'test'
},
{
'DC' => 'test'
},
{
'DC' => 'example'
},
{
'DC' => 'com'
}
];
You do realize that CN is usually an attribute in LDAP directories?
Why not just query for the attribute CN for all returned objects? Then no parsing required.

How do I read elements of a hash using foreach?

use Data::Dumper;
%hash = (
Key => {test => [[testvalue, 10], [testvalue, 20]]},
Key2 => {test => [[testvalue, 30], [testvalue, 40]]},
);
my $parm = $hash{Key}{test};
foreach my $test_p (#{$parm}) {
print Dumper $test_p;
}
It is not displaying in the way I expect.
A comma is missing at the end of the first line.
%hash = (
Key => {
test => [
[ testvalue , 10 ],
[ testvalue , 20 ]
]
},
Key2 => {
test => [
[ testvalue , 30 ],
[ testvalue , 40 ]
]
}
);
my $parm = $hash{Key}{test} ;
foreach my $test_p (#$parm) {
use Data::Dumper;
print Dumper $test_p;
}
You can try this:
my %hash = (
Key => {test => [['testvalue', 10], ['testvalue', 20]]},
Key2 => {test => [['testvalue', 30], ['testvalue', 40]]},
);
my $parm = $hash{Key}{test};
foreach my $test_p (#{$parm}) {
print Dumper $test_p;
}
foreach my $test (keys %hash) {
my $test1 = $hash{$test};
print Dumper $test;
foreach my $test2 (keys %{$test1}) {
print Dumper $test2;
my $test3 = $hash{$test}{$test2};
foreach my $test_p (#{$test3}) {
print Dumper #{$test_p};
}
}
}
Perhaps:
#/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
sub main{
my %hash = ( Key => { test => [[ "testvalue" , 10 ], [ "testvalue" ,20]] },
Key2 => { test => [[ "testvalue" , 30 ], [ "testvalue" ,40]] } );
foreach my $key (sort keys %hash){
my $parm = $hash{$key}{test};
print Dumper $_ foreach(#$parm);
}
}
main();
Outputs:
$VAR1 = [
'testvalue',
10
];
$VAR1 = [
'testvalue',
20
];
$VAR1 = [
'testvalue',
30
];
$VAR1 = [
'testvalue',
40
];