How can I find the indices where two arrays differ in value? - perl

I would like to find out which element has changed between two arrays that contains same data inside it.
#prev=("0","1","0","1");
#curr=("0","1","0","0");
From the above two arrays clearly the last element of the array has changed.
How will I know that only a particular element has changed and rest all of them should not have changed, given that I know which index of the array should change?

To find out the indices where the values have changed:
my #indices_of_change = grep { $prev[$_] ne $curr[$_] } 0 .. $#prev;
e.g. with:
my #prev = ( 0, 1, 0, 1 );
my #curr = ( 0, 1, 0, 0 ); # #indices_of_change == ( 3 );
Of course, there are several caveats attached:
Are #curr and #prev supposed to be the same length? What is the expected behavior if they are not?
The context of DWIM needs to be clarified. Are strings expected to be inside the array(s)? Is '00' supposed to equal to '0'?

If you don't mind me pimping my own module, this is exactly why Array::Compare was written.
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use Array::Compare;
my #prev = qw(0 1 0 1);
my #curr = qw(0 1 0 0);
my $cmp = Array::Compare->new;
my #diffs = $cmp->full_compare(\#prev, \#curr);
say "Difference at these indexes: #diffs";

You can use the zip function from List::MoreUtils to allow comparison like this. Using zip has the advantage that it works with arrays that are not of equal length.
use List::MoreUtils qw/zip/;
my #changed =
map { $_[0] != $_[1] }
zip #prev, #curr;

You can use Data::Difference to compare arrays. And it's quite easy to mangle the returned diff to your needs:
use strict;
use warnings;
use Data::Difference qw(data_diff);
use Data::Dumper;
my #prev = ( "0", "1", "0", "1" );
my #curr = ( "0", "1", "0", "0" );
my #diff = data_diff ( \#prev, \#curr );
my #indices_of_change = map { #{ $_->{ 'path' } } } #diff;
print Dumper ( \#indices_of_change );

Related

How to check if an array has an element that's not Integer?

For example, I have an array = [1,2,3,4,5,a,#,4]
how can I check if an array has an element that's not integer?
If an array has an element that's not integer than I am planning to fail the script which is die function.
Type::Tiny provides a quick method (all) to check if all elements in a list match a specific type consrtaint.
use strict; use warnings; use feature qw( say );
use Types::Common qw( Int PositiveInt );
my #array = ( 1, 2, 3, "foo" );
if ( not Int->all( #array ) ) {
say "They're not all integers";
}
if ( PositiveInt->all( #array ) ) {
say "They're all positive integers";
}
# Another way to think about it:
my $NonInteger = ~Int;
if ( $NonInteger->any( #array ) ) {
say "There's at least one non-integer";
}
If quick&dirty is good enough:
my #a = (1,2,3,-4,0,5,4," -32 ");
die if grep { !defined or !/^\s*-?\d+\s*$/ } #a;
This however doesn't handle for example valid integers (in Perl) such as "1_200_499" or "0e0" well.

How to get only integer values in array and how to add 100 for each integer values in array list

I have an array that contains a list of values including strings and integers. Now I want to get only integer values and want to add 100 for each integer value. For example:
#array = ( "my", 214, 3871, "fg" );
Now I want to increase 100 for each integer value.
The final result should be this:
#array = ( "my", 314, 3971, "fg" );
You could use the map function:
use Modern::Perl;
use Data::Dump qw(dump);
my #array = ("my", 214, 3871, "fg");
#array = map { /^\d+$/ ? $_+100 : $_ } #array;
dump#array;
output:
("my", 314, 3971, "fg")
Simply iterate the array. Check if the current value is an integer and if yes, add 100.
The foreach works because it will alias elements in the array. It is not making copies. So you are editing the array elements one by one although you have assigned them to a named variable (thus making your code more readable).
use strict;
use warnings;
use Data::Printer;
my #array=("my", 214,3871,"fg");
foreach my $element (#array) {
$element += 100 unless $element =~ /[^0-9]/;
}
p #array;
Output:
[
[0] "my",
[1] 314,
[2] 3971,
[3] "fg"
]
Your question title says you only want the integer values.
my #only_ints_plus_hundred = map { $_ + 100 } grep { !/[^0-9]/ } #array;

How to check for range of values in an Array in Perl

I am wondering if this is possible in Perl without a for loop.
I have an array filled with numbers ranging from 1 to 7 (could be repeating).
I am sorting the array first. Then I get the lowest element.
What I need is, if the value of the first element is 1, then I want to check if the array contains (2,3,4,5).
Can I do this in one line, without a loop?
I don't understand why you sort array first but for checking of existence of some values in array you can use for example this approach:
sub check2345 {
my %h;
#h{#_}=();
return 4 == grep exists $h{$_}, 2 .. 5;
}
if you rely on one line (expression):
do{my%h;#h{#array}=();4==grep exists$h{$_},2..5}
You can do this in one line by using a grep/keys/map construct:
#!perl
use strict;
use warnings;
use 5.010;
my #arr = ( 1, 2, 4, 5 );
say "has 2, 3, 4, 5" if 4 == grep { $_ == 2 || $_ == 3 || $_ == 4 || $_ == 5 } keys %{{ map { $_ => 1 } #arr }};
If your elements are going to be integers, you can shorten the grep to:
grep { $_ >= 2 && $_ <= 5 }
If you want to make things a little more supportable (i.e. if your end bounds might be changing), you could try:
#!perl
use strict;
use warnings;
use 5.010;
my #arr = ( 1, 2, 4, 5 );
my $first = 2;
my $last = 5;
say "has them all" if ($last-$first+1) == grep { $_ >= $first && $_ <= $last } keys %{{ map { $_ => 1 } #arr }};
(Note that in both of my examples the script should print nothing, since the array doesn't have all of the elements (2, 3, 4, 5)).
Edit: Based on Hynek's comment below, I've removed the useless use of map and allowed for duplicate values in the original array.

Is there a compact Perl operation to slice alternate elements from an array?

If I have an array myarray in Python, I can use the slice notation
myarray[0::2]
to select only the even-indexed elements. For example:
>>> ar = [ "zero", "one", "two", "three", "four", "five", "six" ]
>>> ar [ 0 : : 2 ]
['zero', 'two', 'four', 'six']
Is there a similar facility in Perl?
Thanks.
A Perl array slice is the # in front of the array name, then the list of indices you want:
#array[#indices];
There's not a built-in syntax to select multiples, but it's not so hard. Use grep to produce a list of indices that you want:
my #array = qw( zero one two three four five six );
my #evens = #array[ grep { ! ($_ % 2) } 0 .. $#array ];
If you are using PDL, there are lots of nice slicing options.
There's array slices:
my #slice = #array[1,42,23,0];
There's a way to to generate lists between $x and $y:
my #list = $x .. $y
There's a way to build new lists from lists:
my #new = map { $_ * 2 } #list;
And there's a way to get the length of an array:
my $len = $#array;
Put together:
my #even_indexed_elements = #array[map { $_ * 2 } 0 .. int($#array / 2)];
Granted, not quite as nice as the python equivalent, but it does the same job, and you can of course put that in a subroutine if you're using it a lot and want to save yourself from some writing.
Also there's quite possibly something that'd allow writing this in a more natural way in List::AllUtils.
I've written the module List::Gen on CPAN that provides an alternative way to do this:
use List::Gen qw/by/;
my #array = qw/zero one two three four five six/;
my #slice = map {$$_[0]} by 2 => #array;
by partitions #array into groups of two elements and returns an array of array references. map then gets this list, so each $_ in the map will be an array reference. $$_[0] (which could also be written $_->[0]) then grabs the first element of each group that by created.
Or, using the mapn function which by uses internally:
use List::Gen qw/mapn/;
my #slice = mapn {$_[0]} 2 => #array;
Or, if your source list is huge and you may only need certain elements, you can use List::Gen's lazy lists:
use List::Gen qw/by gen/;
my $slicer = gen {$$_[0]} by 2 => #array;
$slicer is now a lazy list (an array ref) that will generate it's slices on demand without processing anything that you didn't ask for. $slicer also has a bunch of accessor methods if you don't want to use it as an array ref.
I'll do this in a two-step process: first generate the desired indices, and then use a slice operation to extract them:
#indices = map { $_ * 2 } (0 .. int($#array / 2));
my #extracted = #array[#indices];
Step-by-step, thats:
generate a list of integers from 0 to the last element of the array divided by two
multiply each integer by two: now we have even numbers from zero to the index of the last element
extract those elements from the original array
Perl 6 will improve things dramatically, but (so far?) Perl 5 has pretty limited slicing capability: you have to explicitly specify the indexes you want, and it can't be open-ended.
So you'd have to do:
#ar = ( "zero", "one", "two", "three", "four", "five", "six" );
print #ar[ grep $_ % 2 == 0, 0..$#ar ]
One way to make this prettier is to wrap it in something like autobox.
For example using autobox::Core:
use autobox::Core;
my #ar = qw/zero one two three four five six/;
# you could do this
#ar->slice_while( sub{ not $_ % 2 } );
# and this
#ar->slice_by(2);
# or even this
#ar->evens;
This is how you can define these autobox methods:
sub autobox::Core::ARRAY::slice_while {
my ($self, $code) = #_;
my #array;
for (my $i = 0; $i <= $#{ $self }; $i++) {
local $_ = $i;
push #array, $self->[ $i ] if $code->();
}
return wantarray ? #array : \#array;
}
sub autobox::Core::ARRAY::slice_by {
my ($self, $by) = #_;
my #array = #$self[ map { $_ * $by } 0 .. int( $#{$self} / $by )];
return wantarray ? #array : \#array;
}
sub autobox::Core::ARRAY::evens {
my $self = shift;
my #array = $self->slice_by(2);
return wantarray ? #array : \#array;
}
/I3az/
If you don't care about the order, and if the odd-numbered elements of the list are unique, you can concisely convert the array to a hash and take the values:
#even_elements = values %{{#array}};
#odd_elements = keys %{{#array}};
(No, this is not a serious answer)
Another way would be by using grep:
my #array = qw( zero one two three four five six );
print map { "$_ " } #array[grep { !($_ & 1) } 0 .. $#array]; #even
Output:zero two four six
print map { "$_ " } #array[grep { ($_ & 1) } 0 .. $#array]; #odd
Output:one three five
If you don't mind using an obscure feature of $| you can do this:
{
local $|; # don't mess with global $|
#ar = ( "zero", "one", "two", "three", "four", "five", "six" );
$| = 0;
#even = grep --$|, #ar;
$| = 1;
#odd = grep --$|, #ar;
print "even: #even\\n";
# even: zero two four six
print "odd: #odd\\n";
# odd: one three five
}
or, as a 1 liner:
{ local $|=0; #even = grep --$|, #ar; }
Basically, --$| flip flops between a 0 and 1 value (despite the -- which normally decrements a numeric value), so grep sees a "true" value every other time, thus causing it to return every other item starting with the initial value of $|. Note that you must start with 0 or 1, not some arbitrary index.
Here is the simplest code without creating any index arrays:
sub even { my $f=0; return grep {++$f%2} #_; }
sub odd { my $f=1; return grep {++$f%2} #_; }

In Perl, how can I find the index of a given value in an array?

$VAR1 = [
'830974',
'722065',
'722046',
'716963'
];
How can I calculate the array index for the value "722065"?
The firstidx function from List::MoreUtils can help:
use strict;
use warnings;
use List::MoreUtils qw(firstidx);
my #nums = ( '830974', '722065', '722046', '716963' );
printf "item with index %i in list is 722065\n", firstidx { $_ eq '722065' } #nums;
__END__
item with index 1 in list is 722065
using List::Util, which is a core module, unlike List::MoreUtils, which is not:
use List::Util qw(first);
my #nums = ( '830974', '722065', '722046', '716963' );
my $index = first { $nums[$_] eq '722065' } 0..$#nums;
Here is how you would find all the positions at which a given value appears:
#!/usr/bin/perl
use strict;
use warnings;
my #x = ( 1, 2, 3, 3, 2, 1, 1, 2, 3, 3, 2, 1 );
my #i = grep { $x[$_] == 3 } 0 .. $#x;
print "#i\n";
If you only need the first index, you should use List::MoreUtils::first_index.
If you only need to look up the one item, use firstidx as others have said.
If you need to do many lookups, build an index.
If your array items are unique, building an index is quite simple. But it's not much more difficult to build one that handles duplicate items. Examples of both follow:
use strict;
use warnings;
use Data::Dumper;
# Index an array with unique elements.
my #var_uniq = qw( 830974 722065 722046 716963 );
my %index_uniq = map { $var_uniq[$_] => $_ } 0..$#var_uniq;
# You could use hash slice assinment instead of map:
# my %index_uniq;
# #index_uniq{ #var_uniq } = 0..$#var_uniq
my $uniq_index_of_722065 = $index_uniq{722065};
print "Uniq 72665 at: $uniq_index_of_722065\n";
print Dumper \%index_uniq;
# Index an array with repeated elements.
my #var_dupes = qw( 830974 722065 830974 830974 722046 716963 722065 );
my %index_dupes;
for( 0..$#var_dupes ) {
my $item = $var_dupes[$_];
# have item in index?
if( $index_dupes{$item} ) {
# Add to array of indexes
push #{$index_dupes{$item}}, $_;
}
else {
# Add array ref with index to hash.
$index_dupes{$item} = [$_];
}
}
# Dereference array ref for assignment:
my #dupe_indexes_of_722065 = #{ $index_dupes{722065} };
print "Dupes 722065 at: #dupe_indexes_of_722065\n";
print Dumper \%index_dupes;
Here's hastily written attempt at a reverse look-up using a hash.
my $VAR1 = [ '830974', '722065', '722046', '716963' ];
my %reverse;
$reverse{$VAR1->[$_]} = $_ for 0 .. #$VAR1 - 1;
print $reverse{722065};
This does not account for arrays with duplicate values. I do not endorse this solution for production code.
check out the Perl FAQ
use strict;
use Data::Dumper;
sub invert
{
my $i=0;
map { $i++ => $_ } #_;
}
my #a = ('a','b','c','d','e');
print Dumper #a;
print Dumper invert #a;