How to check for range of values in an Array in Perl - 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.

Related

Perl, How to sort hash (of arrays) keys according to specific positions in arrays

I have hash of array references. I want to sort hash keys according to those arrays' last element and if they are equal, then i want to sort them according to previous element and so on.
i have written a simple custom sort subroutine which sorts according to last element
our %hash = (); #
sub customsort($$)
{ ${$hash{$_[0]}}[-1] <=> ${$hash{$_[1]}}[-1] }
I know i need to pass another argument $j instead of predefined -1 for fixed last element. Then i will set up a loop inside subroutine with some checks, etc. However i couldn't figure out how to pass it while using the subroutine in actual part of code
foreach my $key (sort customsort keys (%hash) ) {..}
Thanks in advance
Here's one way to do it:
#!/usr/bin/env perl
use strict;
use warnings;
use 5.010;
my %hash = (
foo => [ 1, 2, 3, 4, 5 ],
bar => [ 8, 6, 7, 5, 3, 0, 9 ],
baz => [ 5, 5, 5, 5, 5 ],
baz2 => [ 5, 5, 5, 5, 5 ],
);
sub customsort {
my $res;
my $index = -1;
while (1) {
return 0 if ($#{$hash{$a}} + $index < 0) || ($#{$hash{$b}} + $index < 0);
$res = ${$hash{$a}}[$index] <=> ${$hash{$b}}[$index];
return $res if $res;
$index--;
}
}
my #sorted = sort customsort keys %hash;
say $_ for #sorted;
I'm using $a and $b instead of the ($$) prototype because Perl prototypes are generally best avoided, but also note that, according to perldoc sort, using the prototype is slower. So just embrace the magic of $a and $b.
The return 0 if... line is to prevent warnings if you have arrays of different lengths (bar) that have to look back beyond the beginning of a shorter array, and to prevent infinite loops if you have identical arrays (baz and baz2).

syntax for multiple array in a single foreach loop in perl

I need to know the proper syntax for multiple arrays in a single foreach loop.
In TCL, we can have multiple list in a single foreach loop. But how to do it with perl?
#TCL eg:
foreach i $a j $e {
}
Current versions of the List::MoreUtils module provide a zip_unflatten command which will combine two (or more) arrays into a single array containing references to an array of the first element of each list, then the second, and so on:
#!/usr/bin/env perl
use strict;
use warnings;
use 5.010;
use List::MoreUtils 'zip_unflatten';
my #a = 1 .. 4;
my #e = 5 .. 9;
my #z = zip_unflatten(#a, #e);
for my $pair (#z) {
my $i = $pair->[0] // '-';
my $j = $pair->[1] // '-';
say "$i\t$j";
}
(The // operator I use here is "defined-OR", which is basically the same as ||, except it works on whether the variable to its left has a defined value rather than whether it has a true value.)
The TCL syntax isn't clear, so I went to the source, i.e. tclsh:
% set a { 1 2 3 4 }
1 2 3 4
% set e { 4 5 6 7 0 9 }
4 5 6 7 8 9
% foreach i $a j $e { puts "'$i' '$j'" }
'1' '4'
'2' '5'
'3' '6'
'4' '7'
'' '0'
'' '9'
TCL foreach:
Values in each list are used in order from first to last, and each value is used exactly once. The total number of loop iterations is large enough to use up all the values from all the value lists. If a value list does not contain enough elements for each of its loop variables in each iteration, empty values are used for the missing elements.
In Perl "empty" elements are undef, so I guess the closest translation to Perl would be:
#!/usr/bin/perl
use strict;
use warnings;
use List::Util qw(max);
my #a = (1, 2, 3, 4);
my #e = (4, 5, 6, 7, 0, 9);
my $max_index = max($#a, $#e);
foreach my $index (0..$max_index) {
my $i = $a[$index] // '';
my $j = $e[$index] // '';
# your "TCL" loop code goes here...
print "$index '$i' '$j'\n";
}
exit 0;
Test output:
$ perl dummy.pl
0 '1' '4'
1 '2' '5'
2 '3' '6'
3 '4' '7'
4 '' '0'
5 '' '9'
The List::UtilsBy function zip_by may be useful for this task. Unfortunately there is no good way to declare multiple iteration variables in a foreach loop currently, so we use arrayrefs.
use strict;
use warnings;
use List::UtilsBy 'zip_by';
my #arr1 = ...;
my #arr2 = ...;
foreach my $zipped (zip_by { [#_] } \#arr1, \#arr2) {
my ($i, $j) = #$zipped;
...
}
The List::SomeUtils zip function does this same task more concisely, but has an awkward prototype requiring arrays, where zip_by is more flexible.
To someone who doesn't know TCL, it's not clear what you want, but I think you want the following:
for my $i (0..$#foos) {
my $foo = $foos[$i];
my $bar = $bars[$i];
...
}
With just core Perl, there's also the option of using shift, but note that this method modifies (empties) the arrays, so use copies of them if it matters:
#!/usr/bin/env perl
use strict;
use warnings;
use 5.010;
my #a = 1 .. 4;
my #e = 5 .. 9;
while (#a || #e) {
my $i = shift #a // '-';
my $j = shift #e // '-';
say "$i\t$j";
}

Get the index from the comparison of two different length arrays

#!usr/bin/perl -W
use strict;
my #a = (3,5,8,6,7,9);
my #b = (3,7,8);
my #index;
my $match;
foreach my $i (0 .. $#a) {
$match = 0;
foreach my $j (0 .. $#b) {
if ($a[$i] == $b[$i]) {
$match = 1;
push (#index, $j);
last;
}
}
if ($match == 1) {
print "the values which got matched are $a[$i] at a and index is $i\n";
}
}
print "the index of b matched is #index";
Hi I want to get the indices and the values where the array elements got matched.
my #a=(3,5,8,6,7,9);
my #b=(5,9,3);
I want to compare #a and #b and get the index of matched value from a.
(compared values,ia)
output to be something like this ([5,9,3],[1,5,0]). b value 5 is matching at a in index 1.
Can someone help me with this. I was trying to get first the matched array elements and push the index when match is found. But am not getting the expected result.
Why your code does not work
You have a fairly simple typo in your code.
# V
if ($a[$i] == $b[$i]) {
$match = 1;
# V
push (#index, $j);
last;
}
You are using the $i index of the current element in #a to access the same value in #b every time, and then push $j which you never used to compare. You need to compare $a[$i] with $b[$j]. That one letter change will make the program work.
if ($a[$i] == $b[$j]) {
$match = 1;
push (#index, $j);
last;
}
A better way to do it
Your implementation is very inefficient. What you want to do is look up stuff from #b for #a. An easy way to do that is to build a lookup hash (or an index, like the letters on the side of a phone book).
use strict;
use warnings;
use 5.012; # for say and each on array
my #a = ( 3, 5, 8, 6, 7, 9 );
my #b = ( 5, 9, 3 );
my #index;
my $match;
my %index_of_a;
while ( my ( $index, $value ) = each #a ) {
$index_of_a{$value} = $index; # will overwrite
}
foreach my $value (#b) {
if ( exists $index_of_a{$value} ) {
say "The index of '$value' in \#a is '$index_of_a{$value}'";
}
}
The code iterates over the values of #a. It uses the array form of each1, which was added in Perl 5.12. It then puts them in a hash by value, so you can look up the index when you know a value.
We then iterate the values in #b and check if there is an index for #a in our hash for the current value.
The output looks like this
The index of '5' in #a is '1'
The index of '9' in #a is '5'
The index of '3' in #a is '0'
If a value exists in #a more than once, the last occurrence will be used. My code does not keep track of which index of #b matched when. I will leave that to you.
1) while I generally don't like to use each, for arrays where you want both the value and the index I find it a lot more readable than a C-style for loop.

Selecting elements from an array and putting it in another array in Perl

I have an array containing 10 numbers. I want to pick numbers in array index 0,2,4,6,8 and put them in a new array. Likewise with index 1,3,5,7,9. I am new to Perl (started a few days ago).
My program:
my #b;
#a = (1,2,3,4,5,6,7,8,9,10);
for($i=0;$i<=$#a;$i++)
{
push(#b,$a[$i+1]);
}
print "#b";
What am I doing wrong?
I suggest avoiding for loop as it's easier to make mistake somewhere in its usage, and use foreach
my #a = (1,2,3,4,5,6,7,8,9,10);
my (#even, #odd);
foreach my $i (0 .. $#a) {
if ($i % 2) { push #odd, $a[$i] } else { push #even, $a[$i] }
}
You can also use map to test array index modulo % 2, and then for #even decide to filter it by () or take value for it using $a[$_]
my #even = map { $_%2 ? () : $a[$_] } 0 .. $#a;
my #odd = map { $_%2 ? $a[$_] : () } 0 .. $#a;
Simple.
my #a = (1,2,3,4,5,6,7,8,9,10);
my (#even, #odd);
for ( #a ) {
$_ % 2 ? push #odd, $_ : push #even, $_;
}
A few things:
Use the pragmas use strict; and use warnings;. These will catch a lot of errors. If you use use strict;, you'll have to declare your variables with my (sometimes you'll use our, but 99% of the time, you'll use my)
In your for loop, you're using the default variable $_. This variable is evil for a variety of reasons. (One, it's global in scope, so something else could change this variable on your and you wouldn't know.). Declare your variables except in situations where you must use $_.
Standard is to put the { on the line with the for and while. Another is to avoid the C style for loop (and to avoid foreach which is just an alias to for)
Use spaces. It's much easier to read $i <= $#a than $i<=$a.
Here's my interpretation of your program:
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say); #A nicer 'print'
my #a = qw(12 13 14 15 16 17 18 19 20);
my #even;
my #odd;
for my $element (0..$#a) {
if ( $element % 2 ) {
push #odd, $a[$element];
}
else {
push #even, $a[$element];
}
}
say '#even = ' . join ': ', #even;
say '#odd = ' . join ': ', #odd;
The output:
#even = 12: 14: 16: 18: 20
#odd = 13: 15: 17: 19
Note my for loop. I use the 0..$#a to go through each element of the array. The $# is returns the last index of the array. Note that this is easier to understand than the for($i=0;$i<=$#a;$i++) that you used. It's one of the reasons why C style for loops are discouraged.
I use the modulo operator % to parse my even/odd. Modulo is like remainder division. If the number is odd, the modulo % 2 will be a 1. Otherwise, it's zero. Modulo operations are great for anything that works on a cycle.
But let's get back to your program. Here's your original code with a few minor tweaks.
I added the use strict; and use warnings;. These catch about 99% of your programming errors.
I use use feature qw(say); because say is nicer when it comes to debugging. I can take a statement, copy it, and then throw say qq(...); around it and see what it's doing.
I added a bunch of say statements to reveal the logic of your code.
Let's watch what happens. Here's your program slightly modified:
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say);
my #b;
my #a = (1,2,3,4,5,6,7,8,9,10);
my $i;
for($i=0; $i<=$#a; $i++) {
say "Index = $i Element = $a[$i + 1]";
say qq(push(\#b, $a[$i+1]););
push(#b,$a[$i+1]);
}
print "#b";
And here's the output:
Index = 0 Element = 2
push(#b, 2);
Index = 1 Element = 3
push(#b, 3);
Index = 2 Element = 4
push(#b, 4);
Index = 3 Element = 5
push(#b, 5);
Index = 4 Element = 6
push(#b, 6);
Index = 5 Element = 7
push(#b, 7);
Index = 6 Element = 8
push(#b, 8);
Index = 7 Element = 9
push(#b, 9);
Index = 8 Element = 10
push(#b, 10);
Use of uninitialized value in concatenation (.) or string at ./test.pl line 11.
Index = 9 Element =
Use of uninitialized value within #a in concatenation (.) or string at ./test.pl line 12.
push(#b, );
Use of uninitialized value $b[9] in join or string at ./test.pl line 15.
I can see the how each push statement is being executed, and look at that, you're pushing in each and every element. Actually, you're not because you used $a[$i+1] as what you're pushing.
Using use warnings and I can see that I am trying to push the non-existant $a[10] into your #b array.
Let's change your for loop to go to every other element
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say);
my #b;
my #a = qw(1 2 3 4 5 6 7 8 9 10);
my $i;
for ($i=0; $i <= $#a; $i += 2) {
push #b, $a[$i];
}
The first element is $a[0]. The next element in the loop is $a[2] because I added 2 to the index instead of just incrementing it by 1. Now, I'll go through all the even elements and skip all of the odd elements.
And the output:
1 3 5 7 9
(Note that $a[0] = 1. That's why they're all odd numbers. It's why I started at 12 in my program, so $a[0] = 12 which is the even number).
My preference would be to use the while and avoid the for(...; ...; ...) construct:
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say);
my #b;
my #a = qw(1 2 3 4 5 6 7 8 9 10);
my $i = 0;
while ( $i < $#a ) {
push #b, $a[$i];
$i += 2;
}
Even:
for($i=0;$i<=$#a;$i+=2)
{
push(#b,$a[$i]);
}
Odd:
for($i=1;$i<=$#a;$i+=2)
{
push(#b,$a[$i]);
}
List::MoreUtils has an indexes function:
use List::MoreUtils qw{indexes} ;
use 5.10.0 ;
my #a = (1,2,3,4,5,6,7,8,9,10) ;
# index of array where even
say foreach indexes { $_ % 2 == 0 } #a ;
# index of array where odd
say foreach indexes { $_ % 2 != 0 } #a ;
I admit this may be sort of inelegant and it's possibly cheating here to use a module - especially one that is not in CORE. It would be convenient if List::MoreUtils and List::Utils were just one CORE module, but still not as elegant as some the other answers here.

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