Associative array - PERL - arrays

I need to know the way of creating the Associative array in Perl.
Basically now I have the code which is implemented as follows:
my $getEmployeeListOfTeamQuery = "SELECT profiles.userid
FROM user_group_map,profiles
WHERE user_group_map.group_id = $teamId
AND profiles.userid = user_group_map.user_id
AND profiles.active = 'y'
AND profiles.login_name NOT LIKE 'qa_%'
AND profiles.disabledtext = ''
GROUP BY profiles.login_name
ORDER BY profiles.login_name";
my $getEmployeeListOfTeam = $dbh->prepare($getEmployeeListOfTeamQuery);
$getEmployeeListOfTeam -> execute();
my #techs = ();
while(my ($tech) - $getEmployeeListOfTeam->fetchrow_array) {
push #techs,$tech;
}
So above code will be having the query in $getEmployeeListOfTeamQuery, Created the array names as techs.
Then tried pushing the values in to the the array.
Here it is working fine.
My question here is regarding the creation of associative array.
That is I need to query as follows : "SELECT profiles.userid, profiles,username....."
Hence I need to create a Associative array with "userid" as key and "username" as value.

I worry about the resources that you are using to learn Perl. Perl programmers haven't used the term "associative array" since Perl 5 was released over twenty years ago. We now call these structures "hashes". If you're learning from resources that use the term "associative array", then they are almost certainly horribly outdated.
But, to answer your question. The code is pretty simple.
my $sql = 'select profiles.userid, profiles.username...';
my $sth = $dbh->prepare($sql);
$sth->execute;
my %techs_hash;
while (my #row = $sth->fetchrow_array) {
$techs_hash{$row[0]} = $row[1];
}

Use selectcol_arrayref() and the Columns attribute:
my $aref = $dbh->selectcol_arrayref('select userid, login_name ...', {Columns => [1, 2]});
my %hash = #$aref;

you can fetch all rows into hash of hashes by one call to selectall_hashref:
my $tech=$dbh->selectall_hashref("select profile.userid, profiles.username, ...",'userid');
or you can fetch all rows into array of hashes with selectall_arrayref with attribute {Slice=>{}} :
my $tech=$dbh->selectall_arrayref("select profile.userid, profiles.username, ...",{Slice=>{}});
and then turn it into desired hash (this is exactly what you want):
my $result;
$result->{$_->{userid}}=$_->{username} foreach #$tech;

my #l;
while(my $h = $sth->fetchrow_hashref){
push #l,$h;
}
my %hash;
map { $hash{$_->{userid}} = $_->{username} } #l;

Related

Merge Perl hashes into one array and loop through it

I'm creating a Perl plugin for cPanel which has to get all domains in the account of a user and display it in a HTML select field. Originally, I'm a PHP developer, so I'm having a hard time understanding some of the logic of Perl. I do know that cPanel plugins can also be written in PHP, but for this plugin I'm limited to Perl.
This is how I get the data from cPanel:
my #user_domains = $cpliveapi->uapi('DomainInfo', 'list_domains');
#user_domains = $user_domains[0]{cpanelresult}{result}{data};
This is what it looks like using print Dumper #user_domains:
$VAR1 = {
'addon_domains' => ['domain1.com', 'domain2.com', 'domain3.com'],
'parked_domains' => ['parked1.com', 'parked2.com', 'parked3.com'],
'main_domain' => 'main-domain.com',
'sub_domains' => ['sub1.main-domain.com', 'sub2.main-domain.com']
};
I want the data to look like this (thanks #simbabque):
#domains = qw(domain1.com domain2.com domain3.com main-domain.com parked1.com parked2.com parked3.com);
So, I want to exclude sub_domains and merge the others in 1 single-dimensional array so I can loop through them with a single loop. I've struggled the past few days with what sounds like an extremely simple task, but I just can't wrap my head around it.
You need something like this
If you find you have a copy of List::Util that doesn't include uniq then you can either upgrade the module or use this definition
sub uniq {
my %seen;
grep { not $seen{$_}++ } #_;
}
From your dump, the uapi call is returning a reference to a hash. That goes into $cp_response and then drilling down into the structure fetches the data hash reference into $data
delete removes the subdomain information from the hash.
The lists you want are the values of the hash to which $data refers, so I extract those. Those values are references to arrays of strings if there is more than one domain in the list, or simple strings if there is only one
The map converts all the domain names to a single list by dereferencing array references, or passing strings straight through. That is what the ref() ? #$_ : $_ is doing. FInally uniq removes multiple occurrences of the same name
use List::Util 'uniq';
my $cp_response = $cpliveapi->uapi('DomainInfo', 'list_domains');
my $data = $cp_response->{cpanelresult}{result}{data};
delete $data->{sub_domains};
my #domains = uniq map { ref() ? #$_ : $_ } values %$data;
output
parked1.com
parked2.com
parked3.com
domain1.com
domain2.com
domain3.com
main-domain.com
That isn't doing what you think it' doing. {} is the anonymous hash constructor, so you're making a 1 element array, with a hash in it.
You probably want:
use Data::Dumper;
my %user_domains = (
'addon_domains' => ['domain1.com', 'domain2.com', 'domain3.com'],
'parked_domains' => ['parked1.com', 'parked2.com', 'parked3.com'],
'main_domain' => 'main-domain.com',
'sub_domains' => ['sub1.main-domain.com', 'sub2.main-domain.com'],
);
print Dumper \%user_domains;
And at which point the 'other' array elements you can iterate through either a double loop:
foreach my $key ( keys %user_domains ) {
if ( not ref $user_domains{$key} ) {
print $user_domains{$key},"\n";
next;
}
foreach my $domain ( #{$user_domains{$key}} ) {
print $domain,"\n";
}
}
Or if you really want to 'flatten' your hash:
my #flatten = map { ref $_ : #$_ ? $_ } values %user_domains;
print Dumper \#flatten;
(You need the ref test, because without it, the non-array main-domain won't work properly)
So for the sake of consistency, you might be better off with:
my %user_domains = (
'addon_domains' => ['domain1.com', 'domain2.com', 'domain3.com'],
'parked_domains' => ['parked1.com', 'parked2.com', 'parked3.com'],
'main_domain' => ['main-domain.com'],
'sub_domains' => ['sub1.main-domain.com', 'sub2.main-domain.com'],
);

Read MongoDB array into perl and walk data

I am trying to capture an array from my MongoDB database into my Perl script and read each element. This is something that I thought would be simple, but for some dumb reason it is kicking my rearend.
My MongoDB Document (in part)
"members" : [
"5713b2d46d210e51836de591",
"me",
"you",
"him",
"her"
],
Perl code
$document = $database -> get_collection('my_collection')->find_one({_id => $oid});
#members = $document->{'members'};
print Dumper #members;
foreach $member (#members)
{
print "member = $member\n";
}
exit;
Output I am getting:
$VAR1 = [
'5713b2d46d210e51836de591',
'me',
'you',
'him',
'her'
];
member = ARRAY(0x47fa398)
Looking at the last line I see that I am being passed a reference to the array instead of the values. So I tried accessing via $member[0] or $member[1] but that just returns the same ARRAY(0x*****).
PLEASE HELP, I am sure it is something stupid.
Thanks!
Steven
I'm not familiar with Mongo, but looking at the output, your #members array has one element - an array ref (as you suspected). Since Mongo is returning an arrayref, you're best to store that in a scalar and access it like so;
my $members = $document->{'members'};
print "second item returned is: ", $members->[1];
print "The complete contents:\n";
for my $item ( #$members ) {
print " ", $item;
}

Accessing returned values as an array

I have simple XML that I want to read in Perl and make hash containing all read keys.
Consider this code:
my $content = $xml->XMLin("filesmap.xml")->{Item};
my %files = map { $_->{Path} => 1 } #$content;
This snippet works great when XML file contains many Item tags. Then $content is a reference to array. But when there is only one Item I get an error when dereferencing as array. My assumption is that $content is a reference to the scalar, not array.
What is the practice to make sure I get array of values read from XML?
What you need is to not use XML::Simple and then it's really trivial. My favourite for fairly straightforward XML samples is XML::Twig
use XML::Twig;
my $twig = XML::Twig -> new -> parsefile ( 'filesmap.xml' );
my #files = map { $_ -> trimmed_text } $twig -> get_xpath ( '//Path' );
With a more detailed XML sample (and desired result) I'll be able to give you a better answer.
Part of the problem with XML::Simple is it tries to turn an XML data structure into perl data structures, and - because hashes are key-values and unordered but arrays are ordered, it has to guess. And sometimes it does it wrong, and other times it's inconsistent.
If you want it to be consistent, you can set:
my $xml = XMLin( "filesmap.xml", ForceArray => 1, KeyAttr => [], ForceContent => 1 );
But really - XML::Simple is just a route to pain. Don't use it. If you don't like XML::Twig, try XML::LibXML instead.
What I would say you need is a flatten-ing step.
my %files
= map { $_->{Path} => 1 }
# flatten...
map { ref() eq 'ARRAY' ? #$_ : $_ }
$xml->XMLin("filesmap.xml")->{Item}
;
You can do a check and force the return into an array reference if necessary:
my $content = $xml->XMLin("filesmap.xml")->{Item};
$content = ref $content eq 'ARRAY'
? $content
: [$content];

creating a complete array from different parts of strings and removing duplication in perl

I am having an issue aain.
I think I am just tired, because my brain does not want to think anymore.
anyway.
I have multiple strings which I collect from a spreadsheet, each string has the same layout and I am searching for a specific part in the string. This is the easy part though. So the strings will look like this.
this is a string from Japan
this is a string from China
this is a string from America
this is a string from China
this is a string from England
this is a string from Japan
these strings are not local but I collect it from the excel sheet, so I am then calling to find the location of each string which is at the end, in this case I will take the viariable like this.
use Spreadsheet::Read;
my $book = ReadData ("INPUT.xlsx");
my #rows = Spreadsheet::Read::rows ($book->[1]);
my $count;
$count = 0;
my #clause_all;
foreach my $tab(#rows) {
$count ++;
my #row = Spreadsheet::Read::cellrow ($book->[1], $count);
print $row[5]; # $row[5] would be the location like "japan, china, america etc.
}
Here is the part I am struggling though, the loop is seeing $row[5] as a single term, I need to now remove duplication and need to somehow join each line's $row[5] in order to get an array and then throw out the duplicates. I tried doing this, but it does not work due to the singular form of each $row[5]
my %special = ();
foreach (#my_array)
{
$special{$_} = 1;
}
my #deduped = keys %special;
print "#deduped\n";
If I however create my own test array like this it works, besides for it throwing them out of the original order, anyway, so it MUST be a matter of getting locations $row[5] stored in array.
#my_test_array = ("Japan", "China", "America", "China", "England", "Japan")
my %special = ();
foreach (#my_test_array)
{
$special{$_} = 1;
}
my #deduped = keys %special;
print "#deduped\n";
Thanks in advance!
--------------------------------
Edit!
--------------------------------
Well, this did work, but not sure how neat this is. :)
use Spreadsheet::Read;
my $book = ReadData ("NSA_DB.xlsx");
my #rows = Spreadsheet::Read::rows ($book->[1]);
my $count;
$count = 0;
my #clause_all;
foreach my $tab(#rows) {
$count ++;
my #row = Spreadsheet::Read::cellrow ($book->[1], $count);
push #array, "$row[3]\n";
}
my %special = ();
foreach (#array)
{
$special{$_} = 1;
}
my #deduped = keys %special;
print "#deduped";
Thanks again.
It is not very clear from the question, but if you want to add only unique values to array in order these values appear in source spreadsheet:
my %added;
my #array;
for (whatever) {
push #array, $_ unless exists $added{$_};
$added{$_} = 1;
}
According to update on original question:
use Spreadsheet::Read;
my $book = ReadData ("NSA_DB.xlsx");
my #rows = Spreadsheet::Read::rows ($book->[1]);
my #array;
my %added;
for (my $count = 1; $count <= #rows; $count++) {
my #row = Spreadsheet::Read::cellrow ($book->[1], $count);
push #array, $row[3] unless $added{$row[3]};
$added{$row[3]} = 1;
}
print join("\n", #array), "\n";

How to create objects out of each element in array?

I have a module with a new constructor:
package myClass;
sub new
{
my $class = shift;
my $arrayreference = shift;
bless $arrayreference, $class;
return $arrayreference;
};
I want to do something like:
foreach $ref (#arrayref)
{
$array1 = myClass->new($ref);
}
$array1 is being rewritten each time, but I want each element in the array to have a distinct object name (ex. $array1, $array2, $array3 etc.)
If you are working with a plural data structure (an array), then you need to store the result into a plural container (or multiple scalar containers). The idomatic way to do this is to use the map function:
my #object_array = map {myClass->new($_)} #source_array;
If you know that #source_array contains a fixed number of items, and you want scalars for each object:
my ($foo, $bar, $baz) = map {myClass->new($_)} #source_with_3_items;
I think you should use some hash or array to contain the objects.
foreach $ref (#arrayref)
{
push #array, myClass->new($ref);
$hash{$key++} = myClass->new($ref);
}
thus you can access them with $array[42] or $hash{42}.
There is essentially no name difference between $array[1] and $array1. There is a programmatic difference in that $array[1] can be "pieced together" and, under modern Perl environments $array1 can't. Thus I can write $array[$x] for any valid $x and get an item with a "virtual name" of $array.$x.
my #objects = map { MyClass->new( $_ ); } #data_array;
Thus, if you just want to append a number, you probably just want to collect your objects in an array. However, if you want a more complex naming scheme, one or more levels of hashes is probably a good way to go.
If you had a way to derive the name from the object data once formed, and had a method called name, you could do this:
my %object_map
= map { my $o = MyClass->new( $_ ); ( $o->name => $o ); } #data_array
;
Are you are trying to do it in place?
my #objects = (
{ ...args for 1st object... },
{ ...args for 2nd object... },
...
);
$_ = Class->new($_) for #objects;
However, you should avoid reusing variables like that.
my #object_data = (
{ ...args for 1st object... },
{ ...args for 2nd object... },
...
);
my #objects = map Class->new($_), #object_data;
I agree with Ade YU and Eric Strom, and have +1'd their answers: you should use one of their approaches. But what you ask is technically possible, using symbolic references, so for completeness' sake:
foreach my $i (0 .. $#arrayref)
{
no strict refs;
my $varname = 'array' . ($i + 1);
${$varname} = myClass->new($arrayref[$i]);
}

Resources