Compare two 2D array to match the string - arrays

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

Related

hash with array of hashes in perl

I know this topic has been covered but other posts usually has static hashes and arrays and the don't show how to load the hashes and arrays.
I am trying to process a music library. I have a hash with album name and an array of hashes that contain track no, song title and artist. This is loaded from an XML file generated by iTunes.
the pared down code follows:
use strict;
use warnings;
use utf8;
use feature 'unicode_strings';
use feature qw( say );
use XML::LibXML qw( );
use URI::Escape;
my $source = "Library.xml";
binmode STDOUT, ":utf8";
# load the xml doc
my $doc = XML::LibXML->load_xml( location => $source )
or warn $! ? "Error loading XML file: $source $!"
: "Exit status $?";
my %hCompilations;
my %track;
# extract xml fields
my #album_nodes = $doc->findnodes('/plist/dict/dict/dict');
for my $album_idx (0..$#album_nodes) {
my $album_node = $album_nodes[$album_idx];
my $trackName = $album_node->findvalue('key[text()="Name"]/following-sibling::*[position()=1]');
my $artist = $album_node->findvalue('key[text()="Artist"]/following-sibling::*[position()=1]');
my $album = $album_node->findvalue('key[text()="Album"]/following-sibling::*[position()=1]');
my $compilation = $album_node->exists('key[text()="Compilation"]');
# I only want compilations
if( ! $compilation ) { next; }
%track = (
trackName => $trackName,
trackArtist => $artist,
);
push #{$hCompilations{$album}} , %track;
}
#loop through each album access the album name field and get what should be the array of tracks
foreach my $albumName ( sort keys %hCompilations ) {
print "$albumName\n";
my #trackRecs = #{$hCompilations{$albumName}};
# how do I loop through the trackrecs?
}
This line isn't doing what you think it is:
push #{$hCompilations{$album}} , %track;
This will unwrap your hash into a list of key/value pairs and will push each of those individually onto your array. What you want is to push a reference to your hash onto the array.
You could do that by creating a new copy of the hash:
push #{$hCompilations{$album}} , { %track };
But that takes an unnecessary copy of the hash - which will have an effect on your program's performance. A better idea is to move the declaration of that variable (my %track) inside the loop (so you get a new variable each time round the loop) and then just push a reference to the hash onto your array.
push #{$hCompilations{$album}} , \%track;
You already have the code to get the array of tracks, so iterating across that array is simple.
my #trackRecs = #{$hCompilations{$albumName}};
foreach my $track (#trackRecs) {
print "$track->{trackName}/$track->{trackArtist}\n";
}
Note that you don't need the intermediate array:
foreach my $track (#{$hCompilations{$albumName}}) {
print "$track->{trackName}/$track->{trackArtist}\n";
}
first of all you want to push the hash as a single element, so instead of
push #{$hCompilations{$album}} , %track;
use
push #{$hCompilations{$album}} , {%track};
in the loop you can access the tracks with:
foreach my $albumName ( sort keys %hCompilations ) {
print "$albumName\n";
my #trackRecs = #{$hCompilations{$albumName}};
# how do I loop through the trackrecs?
foreach my $track (#trackRecs) {
print $track->{trackName} . "/" . $track->{trackArtist} . "\n";
}
}

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

Array comparison

I have two arrays and I'm using list::compare module. The first array is
#log, "$each_line[3]|$each_line[14]"
The second array is
#log2 "$each_line2[3]|$each_line2[14]|$each_line2[37]"
Is there any way to make these arrays match and print this?
"$each_line[3]|$each_line[14]|$each_line2[37]"
Since you don't say how the matching is done, the example seems to imlpy it's basically an "or" of corresponding array elements.
As such
my #array1 = split(/\|/, $string1);
my #array2 = split(/\|/, $string2);
my $length1 = scalar(#array1);
my $length2 = scalar(#array2);
my #new = ();
my $max = $length1 < $length2 ? $length2 : $length1;
for (my $index = 0; $index < $max; $index++) {
push #new, $index < $length1 ? $array1[$index] : $array2[$index];
}

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

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

Resources