How to compare two arrays in Perl - arrays

I have two arrays. I want to find what elements are in the second but not the first, and what elements are in the first but not the second.
Is there any way with out writing multiple loops?
Each array is something like this $array_2d_1
0 ARRAY(0x9929210)
0 ARRAY(0x98df3d8)
0 71
1 22
2 15
3 10
4 51
1 ARRAY(0x9934900)
0 91
1 82
2 28
3 11
4 91

You can use the module List::Compare for this task:
use strict;
use warnings;
use List::Compare;
my #arr1 = qw/5 8 12 42 99 10/;
my #arr2 = qw/10 20 12 18 99 10/;
my $lc = List::Compare->new( \#arr1, \#arr2 );
my #arr1Only = $lc->get_Lonly;
print "\#arr1 only: #arr1Only\n";
my #arr2Only = $lc->get_Ronly;
print "\#arr2 only: #arr2Only\n";
Output:
#arr1 only: 42 5 8
#arr2 only: 18 20
Hope this helps!

You can't avoid looping. This solution has four loops, but they're not nested.
my %a1 = map { $_ => 1 } #a1;
my %a2 = map { $_ => 1 } #a2;
my #missing_from_a1 = grep !$a1{$_}, #a2;
my #missing_from_a2 = grep !$a2{$_}, #a1;

I think you are looking for something like set implementation in perl right. In standard perl there is no set implementation and here is the module in cpan that can achieve what you are trying to solve set. So, we have to find out a way that we can create unique element out of the array, and hash keys in perl is unique. By using hash implementation we can achieved the set implementation. More details you can look up here :
Set Implementation

To take the set difference between two sets, there must be loops involved somewhere. I am assuming you want to avoid writing out the loops every time you need to compute the set difference instead of having an unhealthy aversion to loops.
One simple way would be to abstract away the operation into a subroutine:
#!/usr/bin/env perl
use strict;
use warnings;
main();
sub main {
my #x = (1, 2, 3, 4, 5);
my #y = (3, 5, 7, 8, 9);
for my $sets ( ([\(#x, #y)], [\(#y, #x)]) ) {
print "#{ set_diff( #$sets ) }\n";
}
}
sub set_diff {
my $x = shift;
my %y = map { $_ => undef } #{ $_[0] };
return [ grep not( exists $y{$_} ), #$x ];
}
There are also a number of set implementations on CPAN.

Related

How to get the index of the maximum element in an array?

I want to find the maximum value in an array and print his index.
I wrote this for print the maximum value; it works but I don't know how print the index.
use feature "say";
use List::Util qw(max);
#x=qw(10 -2 -48 -5 7 34 28);
say "valore massimo: ".max #x;
The core List::Util comes with an almighty reduce, which can be used to direclty work out all kinds of results
use warnings;
use strict;
use feature 'say';
use List::Util qw(reduce);
my #x = qw(10 -2 -48 -5 7 34 28);
my $max_idx = reduce { $x[$a] > $x[$b] ? $a : $b } 0..$#x;
say "Maximal value of $x[$max_idx] is at index $max_idx";
Can wrap this in a simple function, to have a clear name for the operation (max_idx or something, and which can return both the element and its index, perhaps only in list context). Libraries for list utilities often merely package reduce expressions into handy functions.
A utility with the above functionality is max_by from List::UtilsBy, as shown in Silvio Mayolo's answer (but we don't have to make an array of indices first).
A nitpick but I 'd like to mention. The list of (obviously) numbers given in the question as qw(10 -2 -48 -5 7 34 28) is a list of strings ("words"), as qw operator builds things.
These are treated as numbers once they are used that way, as normally done by the interpreter, and all is well. However, since they are clearly to be numbers I prefer to introduce them as such
my #x = (10, -2, -48, -5, 7, 34, 28);
A touch more typing but I find that it conveys the intent more clearly. Again, by all means this is of no consequence for most (any?) code.
List::UtilsBy provides the max_by, for getting a maximum according to some other criterion (which could be another list).
use 5.010;
use List::UtilsBy qw/max_by/;
my #x = qw(10 -2 -48 -5 7 34 28);
my #indices = (0..#x-1);
say max_by { $x[$_] } #indices;
Generally, if you're doing nontrivial list manipulation in Perl, I recommend installing List::AllUtils, which is an all-in-one package including List::Util, List::SomeUtils, and List::UtilsBy.
For a small task such as this, you don't really need to use external libraries.
use strict;
use warnings;
use feature 'say';
my #x = (10, -2, -48, -5, 7, 34, 28);
my $max = 0; # first index is the max
for (0 .. $#x) {
if ($x[$_] > $x[$max]) {
$max = $_;
}
}
say "#x";
say "Max number is $x[$max] with index $max";
Output:
10 -2 -48 -5 7 34 28
Max number is 34 with index 5
Just loop over the indexes, check the values and save the index with the highest number.
The task you are doing is absolutely basic and crucial to programming. If you start to learn programming, you should be able to come up with a solution on your own.
Yes, there exists nice modules that make this task a lot more elegant, but if you are learning to programming, you should come up at least with a solution like this, ON YOUR OWN!
printf "%d\n", max_index(10,3,22,5,4,11,33); # prints 6
printf "%s\n", max_index(34,21,100,12,9); # prints 2
sub max_index {
my ( #list ) = #_;
my $max_index = 0;
my $max_value = shift #list;
my $idx = 0;
for my $current ( #list ) {
$idx++;
if ( $current > $max_value ) {
$max_index = $idx;
$max_value = $current;
}
}
return $max_index;
}
Homework:
What happens if you pass no element to the function? What should be returned?
Make it work with an array reference.
Use a classic for-loop for (..., ..., ...) { ... }, and don't use shift.
What happens if you pass strings to it, instead of numbers?
Do you know a solution to problem 4?
In situation if you would prefer to use only Perl due restriction on Perl module installation - to find index of maximum value in an array you could use following algorithm:
assume that first element in the array has $max value
compare following array elements with $max
store index and value for max element if satisfices the condition
use strict;
use warnings;
use feature 'say';
my #arr = qw(10 -2 -48 -5 7 34 28);
my($ind,$max) = find_max(\#arr);
say "arr[$ind] = $max";
sub find_max {
my $arr = shift;
my($i,$max)=(0,$arr->[0]);
for( 1..$#{$arr} ) {
($i,$max) = ($_,$arr->[$_]) if $max < $arr->[$_];
}
return ($i,$max);
}
Output
arr[5] = 34
You can try the following code
use List::Util qw(max);
my #x = qw(10 -2 -48 -5 7 34 28);
my ($index) = ( grep { $x[$_] eq max(#x) } 0..$#x );
print "max ", max(#x), " index $index\n";
Output
max 34 index 5

How to Iterate through multiple Perl arrays

I am hoping to make a loop that allows me to use less lines of code to make changes to a settings file with Perl. Currently my code reads an XML file and locates a settings ID and replaces the setting value in that ID with a new one. The current request involves a lot of changes to the settings file and the code is very long. I have set my values in an array and my settings ID's in an array. Like this:
#GreetGoalDP1 = (3, 5, 7, 10);
#GreetSIDSunDP1 = ('//xsd:Settings/xsd:Setting[#SID="7012"]/xsd:Value',
'//xsd:Settings/xsd:Setting[#SID="7013"]/xsd:Value',
'//xsd:Settings/xsd:Setting[#SID="7014"]/xsd:Value',
'//xsd:Settings/xsd:Setting[#SID="7015"]/xsd:Value');
and run the following.
my($matchSunDP1G1) = $xpc->findnodes($GreetSIDSunDP1[0]);
$matchSunDP1G1->removeChildNodes();
$matchSunDP1G1->appendText($GreetGoalDP1[0]);
#GreetB
my($matchSunDP1G2) = $xpc->findnodes($GreetSIDSunDP1[1]);
$matchSunDP1G2->removeChildNodes();
$matchSunDP1G2->appendText($GreetGoalDP1[1]);
#GreetC
my($matchSunDP1G3) = $xpc->findnodes($GreetSIDSunDP1[2]);
$matchSunDP1G3->removeChildNodes();
$matchSunDP1G3->appendText($GreetGoalDP1[2]);
#GreetD
my($matchSunDP1G4) = $xpc->findnodes($GreetSIDSunDP1[3]);
$matchSunDP1G4->removeChildNodes();
$matchSunDP1G4->appendText($GreetGoalDP1[3]);
I would like to run these changes through a loop just using the array [0] - [3] until completed as I have to do this same set of 4 multiple times. I am not too familiar with looping arrays. Is this something I can do in Perl? If so, what would be the most efficient way to do so?
A simple take
use warnings;
use strict;
...
for my $i (0..$#GreetGoalDP1) {
my ($matchSunDP1G) = $xpc->findnodes( $GreetSIDSunDP1[$i] );
$matchSunDP1G->removeChildNodes();
$matchSunDP1G->appendText( $GreetGoalDP1[$i] );
}
I take it that you don't need all those individual $matchSunDP1G1 etc. It's assumed that the two arays always have the same length, and their elements are needed in pairs at same indices.
The syntax $#aryname is for the last index in the array #aryname, and .. is the range operator, so 0 .. $#GreetGoalDP1 for your example is the list 0,1,2,3.
Then there are libraries that help with use of multiple arrays in parallel, that can be particularly useful when things get messier or more complicated. An example of using an iterator
use List::MoreUtils qw(each_array);
my $it = each_array #GreetSIDSunDP1, #GreetGoalDP1;
while ( my ($sidsun, $goal) = $it->() ) {
my ($matchSunDP1G) = $xpc->findnodes($sidsun);
$matchSunDP1G -> removeChildNodes();
$matchSunDP1G -> appendText( $goal );
}
If the lists are uneven in size the iterator keeps going through the length of the longer one. After the shorter one gets exhausted its would-be value is undef.
Following code sample demonstrates how you could use %hash for alternation you try to achieve.
my %hash = (
3 => '//xsd:Settings/xsd:Setting[#SID="7012"]/xsd:Value',
5 => '//xsd:Settings/xsd:Setting[#SID="7013"]/xsd:Value',
7 => '//xsd:Settings/xsd:Setting[#SID="7014"]/xsd:Value',
10 => '//xsd:Settings/xsd:Setting[#SID="7015"]/xsd:Value')
);
while( my($k,$v) = each %hash ) {
my $match = $xpc->findnodes($v);
$match->removeChildNodes();
$match->appendText($k);
}
Reference: hash, hash operations
Yet Another Way, using zip from the core List::Util module:
#!/usr/bin/env perl
use warnings;
use strict;
use List::Util qw/zip/;
...;
my #GreetGoalDP1 = (3, 5, 7, 10);
my #GreetSIDSunDP1 = ('//xsd:Settings/xsd:Setting[#SID="7012"]/xsd:Value',
'//xsd:Settings/xsd:Setting[#SID="7013"]/xsd:Value',
'//xsd:Settings/xsd:Setting[#SID="7014"]/xsd:Value',
'//xsd:Settings/xsd:Setting[#SID="7015"]/xsd:Value');
foreach my $pair (zip \#GreetSIDSunDP1, \#GreetGoalDP1) {
my ($matchSunDP1G1) = $xpc->findnodes($pair->[0]);
$matchSunDP1G1->removeChildNodes();
$matchSunDP1G1->appendText($pair->[1]);
}

Count Perl array size

I'm trying to print out the size of my array. I've followed a few other questions like this one on Stack Overflow. However, I never get the result I want.
All I wish for in this example is for the value of 3 to be printed as I have three indexes. All I get, from both print methods is 0.
my #arr;
$arr{1} = 1;
$arr{2} = 2;
$arr{3} = 3;
my $size = #arr;
print $size; # Prints 0
print scalar #arr; # Prints 0
What am I doing wrong, and how do I get the total size of an array when declared and populated this way?
First off:
my #arr;
$arr{1} = 1;
$arr{2} = 2;
$arr{3} = 3;
is nonsense. {} is for hash keys, so you are referring to %arr not #arr. use strict; and use warnings; would have told you this, and is just one tiny fragment of why they're considered mandatory.
To count the elements in an array, merely access it in a scalar context.
print scalar #arr;
if ( $num_elements < #arr ) { do_something(); }
But you would need to change your thing to
my #arr;
$arr[1] = 1;
$arr[2] = 2;
$arr[3] = 3;
And note - the first element of your array $arr[0] would be undefined.
$VAR1 = [
undef,
1,
2,
3
];
As a result, you would get a result of 4. To get the desired 'count of elements' you would need to filter the undefined items, with something like grep:
print scalar grep {defined} #arr;
This will take #arr filter it with grep (returning 3 elements) and then take the scalar value - count of elements, in this case 3.
But normally - you wouldn't do this. It's only necessary because you're trying to insert values into specific 'slots' in your array.
What you would do more commonly, is use either a direct assignment:
my #arr = ( 1, 2, 3 );
Or:
push ( #arr, 1 );
push ( #arr, 2 );
push ( #arr, 3 );
Which inserts the values at the end of the array. You would - if explicitly iterating - go from 0..$#arr but you rarely need to do this when you can do:
foreach my $element ( #arr ) {
print $element,"\n";
}
Or you can do it with a hash:
my %arr;
$arr{1} = 1;
$arr{2} = 2;
$arr{3} = 3;
This turns your array into a set of (unordered) key-value pairs, which you can access with keys %arr and do exactly the same:
print scalar keys %arr;
if ( $elements < keys %arr ) { do_something(); }
In this latter case, your hash will be:
$VAR1 = {
'1' => 1,
'3' => 3,
'2' => 2
};
I would suggest this is bad practice - if you have ordered values, the tool for the job is the array. If you have 'key' values, a hash is probably the tool for the job still - such as a 'request ID' or similar. You can typically tell the difference by looking at how you access the data, and whether there are any gaps (including from zero).
So to answer your question as asked:
my $size = #arr;
print $size; # prints 0
print scalar #arr; # prints 0
These don't work, because you never insert any values into #arr. But you do have a hash called %arr which you created implicitly. (And again - use strict; and use warnings; would have told you this).
You are initializing a hash, not an array.
To get the "size" of your hash you can write.
my $size = keys %arr;
I just thought there should be an illustration of your code run with USUW (use strict/use warnings) and what it adds to the troubleshooting process:
use strict;
use warnings;
my #arr;
...
And when you run it:
Global symbol "%arr" requires explicit package name (did you forget to declare "my %arr"?) at - line 9.
Global symbol "%arr" requires explicit package name (did you forget to declare "my %arr"?) at - line 10.
Global symbol "%arr" requires explicit package name (did you forget to declare "my %arr"?) at - line 11.
Execution of - aborted due to compilation errors.
So USUW.
You may be thinking that you are instantiating an element of #arr when you are typing in the following code:
$arr{1} = 1;
However, you are instantiating a hash doing that. This tells me that you are not using strict or you would have an error. Instead, change to brackets, like this:
$arr[1] = 1;

perl - operations allowed on array while iterating through it

What are the operations allowed on array, while iterating through it?
Is it possible to shift/unshift, pop/push, delete elements without confusing the iterator?
Is that any different for adding/removing key-value pair from hash?
Thank you for your help.
You can assign to existing elements, but should not add or remove them. So no shift, unshift, pop, push, or splice. perlsyn:
If any part of LIST is an array, foreach will get very confused if you add or remove elements within the loop body, for example with splice. So don't do that.
If you are iterating over a hash with each, you should also avoid adding or removing elements, except that you are explicitly allowed to remove the current element. each:
If you add or delete a hash's elements while iterating over it, the effect on the iterator is unspecified; for example, entries may be skipped or duplicated--so don't do that. Exception: It is always safe to delete the item most recently returned by each(), so the following code works properly:
But as it says, the worst that could happen is entries being skipped or duplicated; modifying an array you are looping over, on the other hand, can lead to segfaults.
As ysth has already pointed out, it is unwise to attempt to modify an array while iterating directly on its elements.
However, if one does want to modify an array dependent on the element values, the trick is to do it in reverse index order.
For example, say I have an array of numbers. I would like modifier the array so that every multiple of 4 has a string inserted after it, and every multiple of 5 is removed. I would accomplish that using the following:
use strict;
use warnings;
my #array = ( 1 .. 20 );
for my $i ( reverse 0 .. $#array ) {
# Insert after multiples of 4
if ( ( $array[$i] % 4 ) == 0 ) {
splice #array, $i + 1, 0, "insert";
}
# Remove multiples of 5
if ( ( $array[$i] % 5 ) == 0 ) {
splice #array, $i, 1;
}
}
use Data::Dump;
dd #array;
Outputs:
(
1 .. 4,
"insert",
6,
7,
8,
"insert",
9,
11,
12,
"insert",
13,
14,
16,
"insert",
17,
18,
19,
"insert",
)
Alternatively, if you want to transform an array, it's also possible to use map like so:
my #newarray = map {
( ( ($_) x !!( $_ % 5 ) ), # Remove multiples of 5
( ('insert') x !( $_ % 4 ) ), # Insert After multiples of 4
)
} ( 1 .. 20 );
use Data::Dump;
dd #newarray;

perl concat 5 arrays on same index

I have five arrays and I tried to write them into one array for several hours now. Strangely it works for the first three arrays but as soon as I add the fourth the indexes are messed up or something. The length of all arrays is the same so it should not be a problem to do it like this.
Examples:
INPUT:
#names = [jeff,george,ringo,chris]
#td = [10/04/2014,11/04/2014,12/04/2014,13/04/2014]
#vct1= [5,6,4,0]
#vct2= [1,1,2,2]
#vct3= [6,0,1,5]
Expected OUTPUT:
#ffo = [jeff 10/04/2014 5 1 6
george 11/04/2014 6 1 0
ringo 12/04/2014 4 2 1
chris 13/04/2014 0 2 5]
Here is my code for merging:
for ($i=0;$i<#vct1;++$i) {
push(#ffo,trim(#names[$i])."\t".trim(#td[$i])."\t".trim(#vct1[$i])."\t".trim(#vct2[$i]).trim(#vct3[$i])."\n");
}
I tried List::MoreUtils with zip and
#ffo = map { $names[$_], $td[$_], $vct1[$_], $vct2[$_], $vct3[$_], } 0 .. $#names;
but it did not work. Does anyone know why?
If I use the arrays like this it works:
#names = qw(jeff george ringo chris);
#td = qw(10/04/2014 11/04/2014 12/04/2014 13/04/2014);
#vct1 = qw(5 6 4 0);
#vct2 = qw(1 1 2 2);
#vct3 = qw(6 0 1 5);
for ($i=0;$i<#names;++$i) {
print #names[$i]."\t".#td[$i]."\t".#vct1[$i]."\t".#vct2[$i]."\t".#vct3[$i]."\n";
}
OUTPUT:
jeff 10/04/2014 5 1 6
george 11/04/2014 6 1 0
ringo 12/04/2014 4 2 1
chris 13/04/2014 0 2 5
I'll take a wild stab at answering this, although you have not supplied enough information for me to be sure. This code:
#names = [jeff,george,ringo,chris]
#td = [10/04/2014,11/04/2014,12/04/2014,13/04/2014]
#vct1= [5,6,4,0]
#vct2= [1,1,2,2]
#vct3= [6,0,1,5]
Does not compile. There are several things wrong with it:
It lacks semi-colons ; at the end of the lines,
The values are not quoted, i.e. "jeff","george", the string 10/04/2014 will be interpreted as three numbers being divided and will become 0.00124...,
You are assigning a single value to the arrays: an anonymous array reference [ ... ]
My guess is that you really have something like this:
#names = ["jeff","george","ringo","chris"];
#td = ["10/04/2014","11/04/2014","12/04/2014","13/04/2014"];
#vct1= [5,6,4,0];
#vct2= [1,1,2,2];
#vct3= [6,0,1,5];
And you do not realize that this will create a two-dimensional array, where the first element in each array contains the arrays you want. I.e.: $names[0][0] contains "jeff", and so on.
You say that it works when you use qw(), which is a function that will create a list of quoted arguments (mnemonic: QuoteWords). These two are equivalent statements:
#names = qw(jeff george ringo chris);
#names = ("jeff", "george", "ringo", "chris");
So... use either of these ways to assign values to your arrays.
And also, you should always use
use strict;
use warnings;
As they will tell you about all the mistakes you make. I am guessing if you add them to your code now, you will get quite a few warnings and fatal errors. Which is a good thing. Not knowing about what you did wrong does not help improve your code.
In your original code, you had this:
#names = [jeff,george,ringo,chris]
#td = [10/04/2014,11/04/2014,12/04/2014,13/04/2014]
#vct1= [5,6,4,0]
#vct2= [1,1,2,2]
#vct3= [6,0,1,5]
But you should not declare arrays with the [ ] operator, unless you are interested in an array of anonymous arrays.
Thus, #td, for example, contains only a single element, which is a reference to an anonymous array:
#! /usr/bin/perl
use strict;
use warnings;
my #td = [10/04/2014,11/04/2014,12/04/2014,13/04/2014];
foreach my $element (#td) {
print "$element\n"; # Prints a single element ARRAY(0x7f8e108070d0)
}
print "Size of \#td: ", scalar(#td), "\n"; # Prints 1
You avoid this problem when you use the qw operator which knows to return an array, not an array reference, containing the elements you want.
You first method should work, like this:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my #names = qw(jeff george ringo chris);
my #td = qw(10/04/2014 11/04/2014 12/04/2014 13/04/2014);
my #vct1= qw(5 6 4 0);
my #vct2= qw(1 1 2 2);
my #vct3= qw(6 0 1 5);
my #ffo;
for (my $i = 0; $i <= $#names; $i++) {
push #ffo, join("\t", $names[$i], $td[$i], $vct1[$i], $vct2[$i], $vct3[$i]);
}
print Dumper(\#ffo);
Running:
$VAR1 = [
'jeff 10/04/2014 5 1 6',
'george 11/04/2014 6 1 0',
'ringo 12/04/2014 4 2 1',
'chris 13/04/2014 0 2 5'
];

Resources