In perl how to compare two arrays of objects (comparison logic being coded in a separate subroutine)? - arrays

Let me be specific to my problem instead of generalizing it and confusing the audience. In my code I have set of network addresses (members of object-group actually) stored in individual arrays. I would like to compare whether Group A is a subset of Group B.
I am using Net::IP module to parse the IP addresses and use "overlaps" sub-routine to determine if an element (could be individual IP or a subnet) is a superset of another element.
The challenge I am facing is in returning success status only if each element of Group A, belongs to any one element of Group B.
Here is a way I thought of and proceeding to try to code it likewise:
$status = "match";
foreach $ip (#group_a) {
if a_in_b($ip,#group_b) #this sub-routine would be similar but with different comparison function
{
next;
}
else
{
$status = "no match";
last;}
}
Please suggest me if there is a better way to do it, would love to pick up new techniques. The above technique doesn't look sound at all! As I was searching for for some solutions, some references seem to suggest as if I could try using the smart match operator and overload it. But overloading is beyond my level of sophistication in perl, so kindly help!
EDIT:
Updated my code as per suggestion. Here is the working version (still need to add bits and pieces for error catching)
use Net::IP;
use strict;
use warnings;
my #subnet = ("10.1.128.0/24","10.1.129.0/24","10.1.130.0/24","10.1.108.4");
my #net = ("10.1.128.0/21","10.1.108.0/22");
sub array_subset {
my ($x, $y) = #_;
a_in_b ($_, #$y) or return '' foreach #$x;
return 1;
};
sub a_in_b {
my $node1 = shift(#_);
my #ip_list = #_;
for my $node2 (#ip_list) {
print $node2, "\n";
my $ip1 = new Net::IP ($node1) || die;
my $ip2 = new Net::IP ($node2) || die;
print "$node1 $node2 \n";
if ($ip1->overlaps($ip2)==$IP_A_IN_B_OVERLAP) {
return 1;
}
}
return "";
}
if (array_subset(\#subnet, \#net)) {
print "Matches";
}else
{
print "Doesn't match"
}

Overloading ~~ is a bit of overkill. I would suggest using List::MoreUtils:
use List::MoreUtils qw/all/;
if (all { a_in_b($_, #bignet) } #smallnet) {
# do something
};
Or just rewrite your own code as a sub, and in a more perlish way:
sub array_subset {
my ($x, $y) = #_;
a_in_b ($_, #$y) or return '' foreach #$x;
return 1;
};
# somewhere in the code
if (array_subset(\#subnet, \#net)) {
# do something
};

Related

Compare two 2D array to match the string

I want to compare string in two 2D array but the size are not the same. So, I want to shift the element in one of the array to match with all of the element in another array but i run out of idea on how the looping should be.
This is my first time using perl language. I learned c language before.
#!/usr/intel/pkgs/perl/5.14.1/bin/perl
use Data::Dumper qw(Dumper);
#clk = (
'prescc_ux_aux_clk',
'prescc_ux_prim_clk',
'usb2_phy_side_clk',
'usb3_phy_side_clk',
'ux_prim_clk',
'ux_side_clk',
'ux_xtal_frm_refclk',
'uxd_aux_clk',
'uxd_pgcb_clk',
'uxd_prescc_aux_clk',
'uxd_prim_clk',
'uxd_side_clk',
'uxd_suspend_clk');
#clkack = (
'ccu_ux_xtal_frm_refclk_ack',
'ibbs_ux_prim_clkack',
'sbr_ux_side_clkack',
'uxd_aux_clkack',
'uxd_pgcb_clkack',
'uxd_prim_clkack',
'uxd_side_clkack');
foreach(#clk){
#clkline = map {[split /_/,$_]} #clk;
}
foreach(#clkack){
#clkackline = map{[split /_/,$_]} #clkack;
}
#print Dumper #clkline;
$match = 0;
$clkack_row = #clkackline; #no. of row in clkackline
$clk_row = #clkline;
for ($i=0; $i<$clkack_row; $i++){
$clkackcolumn = #{$clkackline[$i]};
for ($j=0; $j<$clkackcolumn; $j++){
for ($m=0; $m<$clk_row; $m++){
$clkcolumn = #{$clkline[$m]};
for ($n=0; $n<$clkcolumn; $n++){
if ($clkline[$i][$j] eq $clkacline[$m][$n]){
$match = $match + 1;
print "$match\n";
}
}
}
}
}
I expect it to loop the #clkackline array and compare it with the #clkline array. If it's matching then it will give how many match it have, hence the $match variable.
Edited:
I need to split it by '_' so that i can get the element divided by only the word inside another array.
Eg:
$clk[0] = ux_prim_clk will result in;
$clkline[0][0] = ux, $clkline[0][1] = prim, $clkline[0][2] = clk.
Then i need to compare with the #clkackline array element by element but in sequential order.
Eg:
$clkline[0][0] = $clkackline[0][0],
$clkline[0][1] = $clkackline[0][1],
$clkline[0][2] = $clkackline[0][2].
But another problem is i need to compare #clkline with all of the element in clkackline. But since the size will be the constraint, then i need to shift the #clkackline to match with #clkline or vice versa.
Eg:
First check:
$clkline[1][0] = $clkackline[0][0],
$clkline[1][1] = $clkackline[0][1],
$clkline[1][2] = $clkackline[0][2].
Second check:
$clkline[1][0] = $clkackline[0][1],
$clkline[1][1] = $clkackline[0][2],
$clkline[1][2] = $clkackline[0][3].
This is just an example of course, but you can see that i need it to delete the first column in $clkackline[0].
Then i need to calculate the percentage of how much it will match.
Eg:
ux_prim_clk vs uxd_prim_clkack will return 33.33%.
Then store the element with highest match inside another array (eg: #clknew)
I think you may be over complicating your problem, to answer your original question of comparing two lists I have a script that will compare two lists and tell you what doesn't exist in each. If this inst exactly what you need let me know and we can change it up to fit your use. As with just about anything in Perl there is probably a module that will do all this for you.
#!/usr/bin/env perl
use strict;
use warnings;
use List::Util qw(any);
my #list1 = ('prescc_ux_aux_clk',
'prescc_ux_prim_clk',
'usb2_phy_side_clk',
'usb3_phy_side_clk',
'ux_prim_clk',
'ux_side_clk',
'ux_xtal_frm_refclk',
'uxd_aux_clk',
'uxd_pgcb_clk',
'uxd_prescc_aux_clk',
'uxd_prim_clk',
'uxd_side_clk',
'uxd_suspend_clk'
);
my #list2 = ('ccu_ux_xtal_frm_refclk_ack',
'ibbs_ux_prim_clkack',
'sbr_ux_side_clkack',
'uxd_aux_clkack',
'uxd_pgcb_clkack',
'uxd_prim_clkack',
'uxd_side_clkack'
);
print "\n==== LIST 1 TO LIST 2 COMPARISON, Does not exist in list 2 ====\n";
foreach my $first (#list1) {
if ( any { $_ eq $first} #list2) { next; }
else { print $first . "\n"; }
}
print "\n==== LIST 2 TO LIST 1 COMPARISON, Does not exist in list 1 ====\n";
foreach my $first (#list2) {
if ( any {$_ eq $first} #list1) { next; }
else { print $first . "\n"; }
}

Moose: Array of Objects->loop through Attribute

I'm new to Perl Moose, and I'm trying to achieve this simple task. I have my Moose class "TestObject" defined:
package TestObject;
use Moose;
use namespace::autoclean;
has 'Identifier' => (is =>'ro',isa=>'Str');
around BUILDARGS => sub
{
my $orig = shift;
my $class = shift;
if ( #_ == 1 && ! ref $_[0] ) {
return $class->$orig(Identifier => $_[0]);
}
else {
return $class->$orig(#_);
}
};
__PACKAGE__->meta->make_immutable;
1;
In another script I'm trying to access the attribute "Identifier" directly from an array of "TestObjects":
use TestObject;
use experimental 'smartmatch';
my #aArray1=(TestObject->new("z003"),TestObject->new("t302"),TestObject->new("r002"));
my $sIdent="t302";
if($sIdent~~#aArray1->Identifier)
{
print "Element with Identifier".$sIdent." found.";
}
This doesn't work. I could implement a workaround like this:
my #aIdent=();
foreach my $sObject(#aArray1)
{
push(#aIdent,$sObject->Identifier);
}
if($sIdent~~#aIdent)
{
print "Element with Identifier".$sIdent." found.";
}
but that doesn't seem to be the most elegant solution. What is the most elegant solution to solve this problem?
Do not do this with the smartmatch operator. It's experimental for a reason, and it might be removed from future Perl versions, or change the way it works, as it's done before.
Instead, this can be achieved with a simple grep.
my #aArray1 = (
TestObject->new("z003"),
TestObject->new("t302"),
TestObject->new("r002"),
);
my $sIdent = "t302";
if ( grep { $_->Identifier eq $sIdent } #aArray1 ) {
print "Element with Identifier" . $sIdent . " found.";
}
If you want that to be a bit shorter, you can also use first from List::Util. This is a bit faster as it will stop looking after the first match.
use List::Util 'first';
my #aArray1 = (
TestObject->new("z003"),
TestObject->new("t302"),
TestObject->new("r002"),
);
my $sIdent = "t302";
if ( first { $_->Identifier eq $sIdent } #aArray1 ) {
print "Element with Identifier" . $sIdent . " found.";
}
A few words of advice on your code:
Do not ever name a class anything with object. It is going to confuse you, future you and the maintenance guy. If you do not understand the difference between class and object, read up on that please.
Variable names and functions in Perl are always written in lower case by convention, and we use snake case. Camel case is reserved for package names.

Scalar or array function

I have to do function which will work as scalar and as array. For example:
#t = testfunc(1, 2, 3, 4);
$x = testfunc(1, 2, 3, 4);
Anbody have idea how I can do it?
It have to print "scalar" if it's $x and print "array" if #t. I tried to do something like this:
sub testfunc()
{
print "test";
}
But even this doesn't work :/
This feature is named "call context". Use the wantarray keyword.
#t = testfunc(1, 2, 3, 4);
$x = testfunc(1, 2, 3, 4);
sub testfunc {
if ( wantarray ) {
print "List context\n";
}
# False, but defined
elsif ( defined wantarray ) {
print "Scalar context\n";
}
# False and undefined
else {
print "Void context\n";
}
}
There's a function in perl called wantarray which returns:
true if the sub is called in a list context
false if scalar
undef if neither.
As an example:
use strict;
use warnings;
sub wantarray_test {
if ( not defined wantarray() ) {
print "Called in void context by ", caller(), "\n";
}
else {
if ( wantarray() ) {
print "Called in list context by ", caller(), "\n";
return ( "A", "list", "of", "results" );
}
else {
print "called in a scalar context by ", caller(), "\n";
return "scalar result";
}
}
}
my #result = wantarray_test();
print "#result\n";
my $result = wantarray_test();
print $result, "\n";
wantarray_test();
Bonus question:
What do you think you'll get if you:
print wantarray_test();
You can do even more than this if you're so inclined with Contextual::Return - this will allow you to test for more detailed contexts, such as the difference between scalar and boolean. (This is useful for example, if you'd want to test a percentage - you may not want to treat '0' as 'false').
But be careful with context sensitive functions. It's quite easy to build in some unexpected behaviour, which is something that can bite you badly down the line.
As a related note - you should not declare your sub the way you have. There's a mechanism in perl called prototypes that define what sort of arguments your subroutine is expecting. See: perlsub. You should not define your sub as:
sub testfunc()
{
# some stuff
}
This specifies a prototype, and should be avoided unless you're sure that's what you want.
Use wantarray function
#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';
say my $x = testfunc(1,2,3,4);
say my #x = testfunc(1,2,3,4);
sub testfunc {
return wantarray ? #_ : "#_";
}
The strangely named wantarray is the standard approach - it lets you distinguish between list and scalar contexts. You can also use Want from CPAN which goes a bit further than the standard wantarray builtin function (see the Want documentation).
The following should return the same results as the wantarray solutions:
use Want;
sub testfuncadelic {
if (want('LIST')) {
rreturn #_; }
elsif (want('SCALAR')) {
rreturn "#_" ; }
return
}
The following should cut and paste into a shell as a pseudo-"oneliner" (Unix syntax) to demonstrate:
% cpanm Want
% perl -MWant -E '
sub testfunc {
if (want("LIST")) {
rreturn "array in list context ", #_;}
elsif (want("SCALAR")) {
rreturn "string in scalar context #_"; }
return }
$result = testfunc(qw/1 2 3 4/ ); say $result;
#results = testfunc(qw/1 2 3 4/ ); say #results;'
Output
string in scalar context 1 2 3 4
array in list context 1234
Edit
#Sobrique has the most thorough treatment of the standard approach here - he/she would get my acceptance if I had asked the question. One thing I neglected above is that (obviously, 'doh!) if wantarray is undef then you have void context! Perhaps I assumed this and didn't realize it could be used explicitly to detect void contxt. So wantarray can give you all three of the standard calling contexts. Wanted on the other hand is about tricky fun stuff and I did not mean to imply it should be used instead of wantarray. I still think wantarray should have a better name :-)

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