Please explain perl statement - perl

I am reading Intermediate Perl book and in Chapt10 there is this code. I added few print statements but core logic is untouched.
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my #input = qw(Gilligan Skipper Professor Ginger Mary Ann);
my #sorted_positions = sort { $input[$a] cmp $input[$b] } 0 .. $#input;
print Dumper( \#sorted_positions );
my #ranks;
#ranks[#sorted_positions] = ( 1 .. #sorted_positions );
print Dumper( \#ranks );
foreach ( 0 .. $#ranks ) {
print "$input[$_] sorts into position $ranks[$_]\n";
}
When i check the Dumper output then for #sorted_positions array it is printing
$VAR1 = [
5,
0,
3,
4,
2,
1
];
which make sense to me but for #ranks array it is printing
$VAR1 = [
2,
6,
5,
3,
4,
1
];
I am unable to understand what this line is doing.
#ranks[#sorted_positions] = ( 1 .. #sorted_positions );
I am able understand what output means in reference to the program but not able to understand how that output is coming i.e. what exactly is perl doing inside that statement.

The line:
#ranks[#sorted_positions] = ( 1 .. #sorted_positions );
is equivalent to:
#ranks[5,0,3,4,2,1] = (1,2,3,4,5,6);
which is equivalent to:
$ranks[5] = 1;
$ranks[0] = 2;
$ranks[3] = 3;
$ranks[4] = 4;
$ranks[2] = 5;
$ranks[1] = 6;
The example is using slices which are documented in the perldata man page.

Let suppose you want to assign string 'x' into the first position of an array, 'y' into the second position and 'z' into the third position. Instead of doing three assignments, you can do them at the same time;
#array[0,1,2] = ("x", "y", "z");
You don't have to do these in order;
#array[2,0,1] = ("z", "x", "y"); # same result
The right-hand side of the line in question produces a list of numbers starting with 1 and finishing at the integer value returned by the expression #sorted_positions (which is 6 as there are 6 things in #sorted_positions) - ie its identical to;
(1,2,3,4,5,6)
So, the whole statement is identical to:
#ranks[5,0,3,4,2] = (1,2,3,4,5,6) ;
So, if we take just one iteration of this:
foreach ( 0 .. $#ranks ) {
print "$input[$_] sorts into position $ranks[$_]\n";
}
we get;
print "$input[0] sorts into position $ranks[0]\n"
# ie: Gilligan sorts into position 2
Hope that helps.

Related

Perl: Create a binary number and convert it into hex

I want to create a binary number from the given user input.
Input - Array of number
Output - Binary number
A binary number should be created such that it has one on all the places which has been given as input.
In the given case input is 1, 3, and 7 so my binary no should be 1000101, so it has 1's on 1, 3 and 7 places from left.
#x = [ 1, 3, 7 ];
$z = 0;
for( $i = 0; $i < 10; $i++ ){
foreach $elem ( #x ){
if( $elem == $i ){
join( "", $z, 1 );
}
else{
join( "", $z, 0 );
}
}
}
print "Value of z: $z";
After execution, I am getting the value of z as 0.
I need to convert this binary to hexadecimal.
Is there some function which converts binary to hexadecimal?
[ ] creates an array and returns a reference to that array, so you are assigning a single scalar to (poorly named) #x.
You are also misusing join. Always use use strict; use warnings qw( all );! It would have caught this error.
Fixed:
my #bits = ( 1, 3, 7 );
my $num = 0;
$num |= 1 << $_ for #bits;
# 76543210
printf("0b%b\n", $num); # 0b10001010
printf("0x%X\n", $num); # 0x8A
It seems that you want 0b1000101, so we need to correct the indexes.
my #bits_plus_1 = ( 1, 3, 7 );
my $num = 0;
$num |= 1 << ( $_ - 1 ) for #bits_plus_1;
# 6543210
printf("0b%b\n", $num); # 0b1000101
printf("0x%X\n", $num); # 0x45
A few problems:
#x = [ 1, 3, 7 ]; is not an array of three integers. It's an array containing a single array reference. What you want is round brackets, not square brackets: #x = ( 1, 3, 7 );
The string returned by join is not assigned to $z
But even then your code is buggy:
it appends a bit at the end of $z, not the beginning
there's a trailing zero that has no business being there.

perl: sprintf for element in list

I've been really confused about this, I'm trying to create a big matrix of numbers and I want to use sprintf with perl to have a nicer output. I'm trying to use sprintf like so
my $x = 0;
my $y = 0;
for ($x=1; $x<=$steps; $y++) { # loop through lines
for ($y=0; $y<=$distances; $y++) {
my $format = sprintf ("%s",$matrix[$x][$y]);
but this is really doing my head in, as I am looping through all the values of $x and $y and getting their combinations. So I am not sure if I'm meant to use more formatting arguments like so
my $format = sprintf ("%s%s%s",$matrix[$x][$y]);
(of course this is giving me compilation errors as it's not right)
But when I only use one argument, I can't put spaces in between my columns :/ Can somebody explain what's happening? I really don't understand what I'm meant to do to get the formatting nice. I'm looking to just align the columns and have a couple of whitespaces between them. Thank you all so much.
I would be thinking in terms of using map, as a way to display every element:
#!/usr/bin/env perl
use strict;
use warnings;
my #matrix = ( [1,2,3,4],
[5,6,7,8],
[9,10,11,12], );
print join ("\n", map { join ( "\t", #$_ ) } #matrix );
This is formatting on tab-stops, rather than fixed width columns, and outputs:
1 2 3 4
5 6 7 8
9 10 11 12
If you particularly wanted sprintf though:
foreach my $row ( #matrix ) {
print map { sprintf("%5s", $_) } #$row,"\n";
}
(5 columns wide).
In each of these, I'm working on whole rows - that only really applies though, if I'm right about the assumptions I've made about which elements you're displaying.
At a very basic level - your code could work as:
#!/usr/bin/env perl
use strict;
use warnings;
my #matrix = ( [ 1, 2, 3, 4 ],
[ 5, 6, 7, 8 ],
[ 9, 10, 11, 12 ], );
my $steps = 2;
my $distances = 3;
for ( my $x = 1; $x <= $steps; $x++ ) { # loop through lines
for ( my $y = 0; $y <= $distances; $y++ ) {
printf( "%5s", $matrix[$x][$y] );
}
print "\n";
}
Although note - that will only work with equal numbers of columns. You could, however, do something like:
#!/usr/bin/env perl
use strict;
use warnings;
my #matrix = ( [ 1, 2, ],
[ 3, 4, 5, ],
[ 6, 7, 8, 9, 10, 11, 12 ], );
my $steps = 2;
my $distances = 3;
for ( my $x = 1; $x <= $steps; $x++ ) { # loop through lines
for ( my $y = 0; $y <= $distances; $y++ ) {
printf( "%5s", $matrix[$x][$y] // '' );
}
print "\n";
}
Which omits the first row (because you set $x to 1), and iterates up to 4 columns:
3 4 5
6 7 8 9
This omits the extra values on the last line, and uses // to test if the cell is empty or not.
for my $row (#matrix) {
my $format = join(' ', ('%5.2f') x #$row)."\n";
printf($format, #$row);
}
If all rows have the same number of columns, you could calculate the format once.
if (#matrix) {
my $format = join(' ', ('%5.2f') x #{$matrix[0]})."\n";
for my $row (#matrix) {
printf($format, #$row);
}
}
If the size of the columns isn't unknown in advance, you'll need to need to perform the following in order:
Format the cells (if needed),
Find the length of the largest cell of each column, then
Print out the matrix with padding.
The following assumes every row of the matrix is the same length.
use List::Util qw( max );
if (#matrix) {
for my $row (#matrix) {
$_ = sprinf('%.2f', $_) for #$row;
}
my $num_cols = #{$matrix[0]};
my #col_sizes = (0) x $num_cols;
for my $row (#matrix) {
$col_sizes[$x] = max(0, $col_sizes[$x], $row->[$x]);
}
my $format = join(' ', map { "%$_s" } #col_sizes)."\n";
for my $row (#matrix) {
printf($format, #$row);
}
}

Assignment of multiple array subroutine parameters in Perl doesn't work

I'm confused about perl subroutine parameters in this example
when i use references in subroutine parameters it works:
#a = ( 1, 2 );
#b = ( 5, 8 );
#c = add_vecpair( \#a, \#b );
print "#c\n";
print $a[0];
sub add_vecpair { # assumes both vectors the same length
my ( $x, $y ) = #_; # copy in the array references
my #result;
#$x[0] = 2;
for ( my $i = 0; $i < #$x; $i++ ) {
$result[$i] = $x->[$i] + $y->[$i];
}
return #result;
}
but when i don't use references as parameters like this:
#a = ( 1, 2 );
#b = ( 5, 8 );
#c = add_vecpair( #a, #b );
print "#c\n";
print $a[0];
sub add_vecpair { # assumes both vectors the same length
my ( #x, #y ) = #_; # copy in the array references
my #result;
print #y;
for ( my $i = 0; $i < #x; $i++ ) {
$result[$i] = $x[$i] + $y[$i];
}
return #result;
}
..it doesn't work. When do i need to use references as subroutine parameters?
Short version: The issue is this line:
my (#x, #y) = #_;
Assignments are greedy. #x is treated first, and is given as many values from #_ as it can handle. And as it can handle all of them, it ends up getting all of the contents of #_, and #y get none.
The result is the same as this:
my #x = #_; # Gets all of the arguements
my #y; # Gets nothing, and is therefore declared but uninitialized.
This is why using references is recommended when subroutines take more than one value as arguement, and at least one of those values are arrays or hashes.
Longer version:
#_ is a composite of all of the arguements passed to the subroutine, so the original container doesn't matter. Consider the code snippets below. The first one is yours, the second one does the exact same thing, but more clearly displays what is happening.
#a = (1, 2);
#b = (5, 8);
add_vecpair(#a,#b);
....is the same as:
add_vecpair(1, 2, 5, 8);
To further hilight the problem, hashes get really messy if treated this way:
%a = ('a' => 1,
'b' => 2);
%b = ('c' => 3,
'd' => 4);
somefunction(%a, %b);
...is the same as:
somefunction('a', 1, 'b', 2, 'c', 3, 'd', 4);
When you call Perl subroutines with array or hash parameters, they are flattened out to a single list. Therefore in the second case your two array parameters loose their identities and #_ becomes a single array with the elements of both #a and #b.

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

How can I partition a Perl array into equal sized chunks?

I have a fixed-sized array where the size of the array is always in factor of 3.
my #array = ('foo', 'bar', 'qux', 'foo1', 'bar', 'qux2', 3, 4, 5);
How can I cluster the member of array such that we can get
an array of array group by 3:
$VAR = [ ['foo','bar','qux'],
['foo1','bar','qux2'],
[3, 4, 5] ];
my #VAR;
push #VAR, [ splice #array, 0, 3 ] while #array;
or you could use natatime from List::MoreUtils
use List::MoreUtils qw(natatime);
my #VAR;
{
my $iter = natatime 3, #array;
while( my #tmp = $iter->() ){
push #VAR, \#tmp;
}
}
I really like List::MoreUtils and use it frequently. However, I have never liked the natatime function. It doesn't produce output that can be used with a for loop or map or grep.
I like to chain map/grep/apply operations in my code. Once you understand how these functions work, they can be very expressive and very powerful.
But it is easy to make a function to work like natatime that returns a list of array refs.
sub group_by ($#) {
my $n = shift;
my #array = #_;
croak "group_by count argument must be a non-zero positive integer"
unless $n > 0 and int($n) == $n;
my #groups;
push #groups, [ splice #array, 0, $n ] while #array;
return #groups;
}
Now you can do things like this:
my #grouped = map [ reverse #$_ ],
group_by 3, #array;
** Update re Chris Lutz's suggestions **
Chris, I can see merit in your suggested addition of a code ref to the interface. That way a map-like behavior is built in.
# equivalent to my map/group_by above
group_by { [ reverse #_ ] } 3, #array;
This is nice and concise. But to keep the nice {} code ref semantics, we have put the count argument 3 in a hard to see spot.
I think I like things better as I wrote it originally.
A chained map isn't that much more verbose than what we get with the extended API.
With the original approach a grep or other similar function can be used without having to reimplement it.
For example, if the code ref is added to the API, then you have to do:
my #result = group_by { $_[0] =~ /foo/ ? [#_] : () } 3, #array;
to get the equivalent of:
my #result = grep $_->[0] =~ /foo/,
group_by 3, #array;
Since I suggested this for the sake of easy chaining, I like the original better.
Of course, it would be easy to allow either form:
sub _copy_to_ref { [ #_ ] }
sub group_by ($#) {
my $code = \&_copy_to_ref;
my $n = shift;
if( reftype $n eq 'CODE' ) {
$code = $n;
$n = shift;
}
my #array = #_;
croak "group_by count argument must be a non-zero positive integer"
unless $n > 0 and int($n) == $n;
my #groups;
push #groups, $code->(splice #array, 0, $n) while #array;
return #groups;
}
Now either form should work (untested). I'm not sure whether I like the original API, or this one with the built in map capabilities better.
Thoughts anyone?
** Updated again **
Chris is correct to point out that the optional code ref version would force users to do:
group_by sub { foo }, 3, #array;
Which is not so nice, and violates expectations. Since there is no way to have a flexible prototype (that I know of), that puts the kibosh on the extended API, and I'd stick with the original.
On a side note, I started with an anonymous sub in the alternate API, but I changed it to a named sub because I was subtly bothered by how the code looked. No real good reason, just an intuitive reaction. I don't know if it matters either way.
Or this:
my $VAR;
while( my #list = splice( #array, 0, 3 ) ) {
push #$VAR, \#list;
}
Another answer (a variation on Tore's, using splice but avoiding the while loop in favor of more Perl-y map)
my $result = [ map { [splice(#array, 0, 3)] } (1 .. (scalar(#array) + 2) % 3) ];
Try this:
$VAR = [map $_ % 3 == 0 ? ([ $array[$_], $array[$_ + 1], $array[$_ + 2] ])
: (),
0..$#array];
Another generic solution, non-destructive to the original array:
use Data::Dumper;
sub partition {
my ($arr, $N) = #_;
my #res;
my $i = 0;
while ($i + $N-1 <= $#$arr) {
push #res, [#$arr[$i .. $i+$N-1]];
$i += $N;
}
if ($i <= $#$arr) {
push #res, [#$arr[$i .. $#$arr]];
}
return \#res;
}
print Dumper partition(
['foo', 'bar', 'qux', 'foo1', 'bar', 'qux2', 3, 4, 5],
3
);
The output:
$VAR1 = [
[
'foo',
'bar',
'qux'
],
[
'foo1',
'bar',
'qux2'
],
[
3,
4,
5
]
];
As a learning experience I decided to do this in Perl6
The first, perhaps most simplest way I tried was to use map.
my #output := #array.map: -> $a, $b?, $c? { [ $a, $b // Nil, $c // Nil ] };
.say for #output;
foo bar qux
foo1 bar qux2
3 4 5
That didn't seem very scalable. What if I wanted to take the items from the list 10 at a time, that would get very annoying to write. ... Hmmm I did just mention "take" and there is a keyword named take lets try that in a subroutine to make it more generally useful.
sub at-a-time ( Iterable \sequence, Int $n where $_ > 0 = 1 ){
my $is-lazy = sequence.is-lazy;
my \iterator = sequence.iterator;
# gather is used with take
gather loop {
my Mu #current;
my \result = iterator.push-exactly(#current,$n);
# put it into the sequence, and yield
take #current.List;
last if result =:= IterationEnd;
}.lazy-if($is-lazy)
}
For kicks let's try it against an infinite list of the fibonacci sequence
my $fib = (1, 1, *+* ... *);
my #output = at-a-time( $fib, 3 );
.say for #output[^5]; # just print out the first 5
(1 1 2)
(3 5 8)
(13 21 34)
(55 89 144)
(233 377 610)
Notice that I used $fib instead of #fib. It was to prevent Perl6 from caching the elements of the Fibonacci sequence.
It might be a good idea to put it into a subroutine to create a new sequence everytime you need one, so that the values can get garbage collected when you are done with them.
I also used .is-lazy and .lazy-if to mark the output sequence lazy if the input sequence is. Since it was going into an array #output it would have tried to generate all of the elements from an infinite list before continuing onto the next line.
Wait a minute, I just remembered .rotor.
my #output = $fib.rotor(3);
.say for #output[^5]; # just print out the first 5
(1 1 2)
(3 5 8)
(13 21 34)
(55 89 144)
(233 377 610)
.rotor is actually far more powerful than I've demonstrated.
If you want it to return a partial match at the end you will need to add a :partial to the arguments of .rotor.
Use the spart function from the List::NSect package on CPAN.
perl -e '
use List::NSect qw{spart};
use Data::Dumper qw{Dumper};
my #array = ("foo", "bar", "qux", "foo1", "bar", "qux2", 3, 4, 5);
my $var = spart(3, #array);
print Dumper $var;
'
$VAR1 = [
[
'foo',
'bar',
'qux'
],
[
'foo1',
'bar',
'qux2'
],
[
3,
4,
5
]
];
Below a more generic solution to the problem:
my #array = ('foo', 'bar', 1, 2);
my $n = 3;
my #VAR = map { [] } 1..$n;
my #idx = sort map { $_ % $n } 0..$#array;
for my $i ( 0..$#array ){
push #VAR[ $idx[ $i ] ], #array[ $i ];
}
This also works when the number of items in the array is not a factor of 3.
In the above example, the other solutions with e.g. splice would produce two arrays of length 2 and one of length 0.