Perl `split` does not `split` to default array - perl

I have this strange problem with split in that it does not by default split into the default array.
Below is some toy code.
#!/usr/bin/perl
$A="A:B:C:D";
split (":",$A);
print $_[0];
This does not print anything. However if I explicitly split into the default array like
#!/usr/bin/perl
$A="A:B:C:D";
#_=split (":",$A);
print $_[0];
It's correctly prints A. My perl version is v5.22.1.

split does not go to #_ by default. #_ does not work like $_. It's only for arguments to a function. perlvar says:
Within a subroutine the array #_ contains the parameters passed to that subroutine. Inside a subroutine, #_ is the default array for the array operators pop and shift.
If you run your program with use strict and use warnings you'll see
Useless use of split in void context at
However, split does use $_ as its second argument (the string that is split up) if nothing is supplied. But you must always use the return value for something.

You have to assign the split to an array:
use strict;
use warnings;
my $string = "A:B:C:D";
my #array = split(/:/, $string);
print $array[0] . "\n";

Related

run a subroutine by using argument

My code:
my $aaa = "abc";
sub myp{
print "$_";
}
myp($aaa);
I hope myp can print the argument it get.
But it said
Use of uninitialized value $_ in string at ./arg line 17.
The arguments to a subroutine in Perl are passed in the #_ array. This is not the same as the $_ variable.
A common idiom is to "unpack" these arguments in the first line of a function, e.g.
sub example {
my ($arg1, $arg2) = #_;
print "$arg1 and $arg2";
}
It's also possible to refer to arguments directly as elements of #_, e.g. as $_[0], but this is much harder to read and as such is best avoided.
I usually do something like:
my $first_arg = shift #_;
my $second_arg = shift #_;
You can also use the method of the other response:
my ($first_arg, $second_arg) = #_;
But be careful saying:
my $first_arg = #_;
Since you will get the number of arguments passed to the subroutine.
When you refer to $_ you are referencing the default string variable, you probably want in this case to refer #_, if you want to get a specific argument, you must say $_[narg], be also careful passing arrays to subroutines if you do:
some_sub(#myarray);
You will pass the entire array as it was the argument list, instead you should say:
some_sub(\#myarray);

Perl: passing hash by ref using rule1

I am still unclear about why by ref portion is showing undefined value for %Q and $_ uninitialized. I have been looking through perlreftut and still unable to see what I have done wrong. Passing the hash as a flat array has no issue.
Doing it by ref with testRef(\%mkPara) passes a scalar hash reference to the subroutine, right? So, does my %Q = %{$_} not turn it back into a hash?
use strict;
use diagnostics;
use warnings;
my %mkPara = ('aa'=>2,'bb'=>3,'cc'=>4,'dd'=>5);
sub testFlat
{
my %P = #_;
print "$P{'aa'}, $P{'bb'}, ", $P{'cc'}*$P{'dd'}, "\n";
}
sub testRef
{
my %Q = %{$_}; #can't use an undefined value as HASH reference
#print $_->{'aa'}, "\n";#Use of uninitialized value
print $Q{'aa'},"\n";
}
#testFlat(%mkPara);
testRef(\%mkPara);
When you use arguments in a function call (\%mkPara in your case), you can access them through #_ array inside the function.
Here, you pass a single argument to the function : \%mkPara, which you can then access by accessing the first element of #_ by using $_[0].
$_ is the default variable for some builtin functions/operators (print, m//, s///, chomp and a lot more). Usually seen in while or for loops. But in your code, you have no reason to use it (you are never setting it to anything, so it's still set to undef, hence the error "Can't use an undefined value as a HASH reference".
So your function should actually be :
sub testRef
{
my %Q = %{$_[0]}; # instead of %{$_}
print $_[0]->{'aa'}, "\n"; # instead of $_->{'aa'}
print $Q{'aa'},"\n";
}
If needed, you can find more about functions on perlsub.
However, as #Ikegami pointed out in the comments, using my %Q = %{$_[0]}; creates a copy of the hash you sent to the function, which in most cases (including that one where you just print a key of the hash) is very suboptimal as you could just use a hashref (like you are doing when you do $_[0]->{'aa'}).
You can use hash references like this (roughly the same example as the answer of #Zaid) :
sub testRef
{
my ( $Q ) = #_;
print $Q->{aa} ;
print $_, "\n" for keys %$Q;
}
testRef(\%mkPara);
There are quite a lot of resources about references online, for instance perlreftut that you were already looking at.
This can seem a bit tricky at first, but the reason is that $_ is not the same as #_.
From perlvar:
$_ is the implicit/"default" variable that does not have to be spelled out explicitly for certain functions (e.g. split )
Within a subroutine the array #_ contains the parameters passed to that subroutine
So the reason why
my %Q = %{$_};
says you can't use an undefined value as hash reference is because $_ is not defined.
What you really need here is
my %Q = %{$_[0]};
because that is the first element of #_, which is what was passed to testRef in the first place.
In practice I tend to find myself doing things a little differently because it lends itself to flexibility for future modifications:
sub testRef {
my ( $Q ) = #_;
print $_, "\n" for keys %$Q; # just as an example
}

Not an ARRAY reference error in "pop($str)"

I am learning Perl for work and I'm trying to practise with some basic programs.
I want my program to take a string from STDIN and modify it by taking the last character and putting it at the start of the string.
I get an error when I use variable $str in $str = <STDIN>.
Here is my code:
my $str = "\0";
$str = <STDIN>;
sub last_to_first {
chomp($str);
pop($str);
print $str;
}
last_to_first;
Exec :
Matrix :hi
Not an ARRAY reference at matrix.pl line 13, <STDIN> line 1.
Why your approach doesn't work
The pop keyword does not work on strings. Strings in Perl are not automatically cast to character arrays, and those array keywords only work on arrays.
The error message is Not an ARRAY reference because pop sees a scalar variable. References are scalars in Perl (the scalar here is something like a reference to the address of the actual array in memory). The pop built-in takes array references in Perl versions between 5.14 and 5.22. It was experimental, but got removed in the (currently latest) 5.24.
Starting with Perl 5.14, an experimental feature allowed pop to take a scalar expression. This experiment has been deemed unsuccessful, and was removed as of Perl 5.24.
How to make it work
You have to split and join your string first.
my $str = 'foo';
# turn it into an array
my #chars = split //, $str;
# remove the last char and put it at the front
unshift #chars, pop #chars;
# turn it back into a string
$str = join '', #chars;
print $str;
That will give you ofo.
Now to use that as a sub, you should pass a parameter. Otherwise you do not need a subroutine.
sub last_to_first {
my $str = shift;
my #chars = split //, $str;
unshift #chars, pop #chars;
$str = join '', #chars;
return $str;
}
You can call that sub with any string argument. You should do the chomp to remove the trailing newline from STDIN outside of the sub, because it is not needed for switching the chars. Always build your subs in the smallest possible unit to make it easy to debug them. One piece of code should do exactly one functionality.
You also do not need to initialize a string with \0. In fact, that doesn't make sense.
Here's a full program.
use strict;
use warnings 'all';
my $str = <STDIN>;
chomp $str;
print last_to_first($str);
sub last_to_first {
my $str = shift;
my #chars = split //, $str;
unshift #chars, pop #chars;
$str = join '', #chars;
return $str;
}
Testing your program
Because you now have one unit in your last_to_first function, you can easily implement a unit test. Perl brings Test::Simple and Test::More (and other tools) for that purpose. Because this is simple, we'll go with Test::Simple.
You load it, tell it how many tests you are going to do, and then use the ok function. Ideally you would put the stuff you want to test into its own module, but for simplicity I'll have it all in the same program.
use strict;
use warnings 'all';
use Test::Simple tests => 3;
ok last_to_first('foo', 'ofo');
ok last_to_first('123', '321');
ok last_to_first('qqqqqq', 'qqqqqq');
sub last_to_first {
my $str = shift;
my #chars = split //, $str;
unshift #chars, pop #chars;
$str = join '', #chars;
return $str;
}
This will output the following:
1..3
ok 1
ok 2
ok 3
Run it with prove instead of perl to get a bit more comprehensive output.
Refactoring it
Now let's change the implementation of last_to_first to use a regular expression substitution with s/// instead of the array approach.
sub last_to_first {
my $str = shift;
$str =~ s/^(.+)(.)$/$2$1/;
return $str;
}
This code uses a pattern match with two groups (). The first one has a lot of chars after the beginning of the string ^, and the second one has exactly one char, after which the string ends $. You can check it out here. Those groups end up in $1 and $2, and all we need to do is switch them around.
If you replace your function in the program with the test, and then run it, the output will be the same. You have just refactored one of the units in your program.
You can also try the substr approach from zdim's answer with this test, and you will see that the tests still pass.
The core function pop takes an array, and removes and returns its last element.
To manipulate characters in a string you can use substr, for example
use warnings;
use strict;
my $str = <STDIN>;
chomp($str);
my $last_char = substr $str, -1, 1, '';
my $new_str = $last_char . $str;
The arguments to substr mean: search the variable $str, at offset -1 (one from the back), for a substring of length 1, and replace that with an empty string '' (thus removing it). The substring that is found, here the last character, is returned. See the documentation page linked above.
In the last line the returned character is concatenated with the remaining string, using the . operator.
You can browse the list of functions broken down by categories at Perl functions by category.
Perl documentation has a lot of goodies, please look around.
Strings are very often manipulated using regular expressions. See the tutorial perlretut, the quick start perlrequick, the quick reference perlreref, and the full reference perlre.
You can also split a string into a character array and work with that. This is shown in detail in the answer by simbabque, which packs a whole lot more of good advice.
This is for substring function used for array variables:
my #arrays = qw(jan feb mar);
last_to_first(#arrays);
sub last_to_first
{
my #lists = #_;
my $last = pop(#lists);
#print $last;
unshift #lists, $last;
print #lists;
}
This is for substring function used for scalar variables:
my $str = "";
$str = <STDIN>;
chomp ($str);
last_to_first($str);
sub last_to_first
{
my $chr = shift;
my $lastchar = substr($chr, -1);
print $lastchar;
}

How to get the last item of a split in Perl?

$k="1.3.6.1.4.1.1588.2.1.1.1.6.2.1.37.32";
#a= split('\.',$k);
print #a[-1]; # WORKS!
print (split '\.',$k)[-1]; # Fails: not proper syntax.`
I'd like to print the last element of a split without having to use an intermediary variable. Is there a way to do this? I'm using Perl 5.14.
Perl is attributing the open parenthesis( to the print function. The syntax error comes from that the print() cannot be followed by [-1]. Even if there is whitespace between print and (). You need to prefix the parenthesis with a + sign to force list context if you do not want to add parens to your print.
print +(split'\.', $k)[-1];
If you are not using your syntax as the parameter to something that expects to have parens, it will also work the way you tried.
my $foo = (split '\.', $k)[-1];
print $foo;
Instead of creating a complete list and slicing it to get the last element, you could use a regex capture:
use strict;
use warnings;
my $k = "1.3.6.1.4.1.1588.2.1.1.1.6.2.1.37.32";
my ($last) = $k =~ /(\d+)$/;
print $last;
Output:
32
rindex() split last position while index() split from first position found
print substr( $k, rindex($k, '.')+1 );

Perl: Special array #_ is not really an alias?

The special array, #_ , where all the arguments passed to a function are present, is actually an alias to the arguments passed. Hence, any change we make directly to this special array #_ will reflect in the main as well. This is clear.
#!/usr/bin/perl
use warnings;
use strict;
$\="\n";
sub func {
print \#_;
$_++ for(#_);
}
my #arr=(2..4);
print \#arr;
func(#arr);
print "#arr";
For the above program, I expected the reference of #arr and #_ to point to the same location since it is an alias. But it is not so.
On running the above:
ARRAY(0x1b644d0)
ARRAY(0x1b644e0)
3 4 5
If they are pointing to 2 different locations, how the changes done in #_ is reflecting in #arr?
Am I seeing something wrong? Please advice.
This might answer you question:
use warnings;
use strict;
$\="\n";
sub func {
print \#_;
$_++ for(#_);
print \$_ for #_;
}
my #arr=(2..4);
print \#arr;
func(#arr);
print "#arr";
print \$_ for #arr;
Output
ARRAY(0x17fcba0)
ARRAY(0x1824288)
SCALAR(0x17fcc48)
SCALAR(0x18196f8)
SCALAR(0x1819710)
3 4 5
SCALAR(0x17fcc48)
SCALAR(0x18196f8)
SCALAR(0x1819710)
As you see, individual arguments have the same address but the container is not the same. If you push an item to #_ in func the #arr will not change (so you can do shift in funct). So, each argument is an alias and array elements are passed as individual items. #_ contains all items passed into the subroutine. If you want to modify an array argument you need to pass it by reference.
#_ isn't aliased; its elements are.
Remember that
func(#arr);
is the same as
func($arr[0], $arr[1], ...);
because the only thing that can be passed to a sub is a list of scalars, and an array evaluates to a list of its elements in list context.
So that means
func(#arr);
is basically the same as
local #_;
alias $_[0] = $arr[0];
alias $_[1] = $arr[1];
...
&func;
Changing the elements of #_ will change elements of #arr, but adding and removing elements of #_ won't change #arr since they are different arrays.
>perl -E"#a=(4..6); sub { $_[0] = '!'; say #_; }->(#a); say #a;"
!56
!56
>perl -E"#a=(4..6); sub { splice(#_,0,1,'!'); say #_; }->(#a); say #a;"
!56
456