Why can't my Perl code implement the reverse function? - perl

Here is my code named reverse.pl
#!usr/bin/perl -w
use 5.016;
use strict;
while(my $line=<>)
{
my #array=();
push (#array,$line);
#array=reverse#array;
say #array;
}
Test file named a.txt
A B C D
E F G H
I J K L
M N O P
Q R S T
My command is perl reverse.pl a.txt
Why it can't implement the reverse function?
I want to show the result is:
D C B A
H G F E
and so on.

Reverse in a scalar context reverses a scalar.
Reverse in a list context reverses the list, but not each scalar within the list.
You explicitly turn your scalar $line into a list with one item and then reverse the order of the items.
Try this:
#!/usr/bin/perl
use 5.016;
use strict;
while (my $line=<>) {
chomp($line);
say scalar reverse $line;
}
If you have an array and want to reverse each element (but not the elements), use map:
my #array = qw(Alpha Beta Gamma);
#array = map { scalar reverse $_ } #array;
print "#array\n";
If you want to do both (reverse each element and the elements themselves), do:
#array = map { scalar reverse $_ } reverse #array;
or:
#array = reverse map { scalar reverse $_ } #array;

When you say:
push #array, $line;
You're creating an array of one value that's equal to the line.
$array[0] = "A B C D";
When you say:
#array = reverse #array;
You are reversing that single member array. The first element becomes the last, and the last element becomes the first, etc.. However, you only have one element, so there's nothing to reverse.
What you want to do is create an array with your line:
my #array = split /\s+/, $line;
This will create an array with each character being a separate element of the array. For example, your first line:
$array[0] = "A";
$array[1] = "B";
$array[2] = "C";
$array[3] = "D";
Now, if you use reverse on this array, you'll get:
$array[0] = "D";
$array[1] = "C";
$array[2] = "B";
$array[3] = "A";
Here's the program:
use strict;
use warnings;
use feature qw(say);
while ( my $line = <> ) {
chomp $line;
my #array = split /\s+/, $line;
say join " ", reverse $line;
}
The join function takes an array, and joins each element into a single line -- thus rebuilding your line.
By the way, I could have done this:
#array = reverse #array;
say "#array"; #Quotes are important!
This is because Perl will automatically join an array with whatever character is in $". This is a Perl variable that is used for joining arrays when that array is placed in quotation marks, and the default value is a single space.
Personally, I rather prefer the say join " ", reverse $line;. It's more obvious what is going on, and doesn't depend upon the value of rarely used variables.

Related

Why function does not receive arguments?

I have next code:
my $str = '';
new( (split ',', $str )[0] )
Here I split my $str and asks exactly one element from result list
But when check incoming data at #_ I see zero elements
Why function does not receive arguments?
I expect one element
Here is some code that tests what you say in your question.
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
sub new {
say 'new() received ' . #_ . ' argument(s).';
say "The first argument was '$_[0].'" if #_;
}
my $str = 'one,two,three';
new( (split ',', $str )[0] );
When I run it, I get the following output:
$ perl split_test
new() received 1 argument(s).
The first argument was 'one.'
This seems to be working as expected. So it seems likely that your problem lies in parts of the code that you haven't shared with us.
It seems I found the answer.
Problem was because of special case when I slice empty list.
This special case is useful at while condition:
while ( ($home, $user) = (getpwent)[7,0] ) {
printf "%-8s %s\n", $user, $home;
}
Here is documentation for this
#a = ()[0,1]; # #a has no elements
#b = (#a)[0,1]; # #b has no elements
#c = (sub{}->())[0,1]; # #c has no elements
#d = ('a','b')[0,1]; # #d has two elements
#e = (#d)[0,1,8,9]; # #e has four elements
#f = (#d)[8,9]; # #f has two elements

Sort comma-delimited file by three columns with custom criteria in Perl

I have a comma-delimited, text file. I want to sort the file by the 3rd column first, then the 2nd column, then the 1st column.
However, I want the 3rd column to be sorted alphabetically, with the longest value first.
For example, AAA, then AA, then A, then BBB, then BB, then B, then CCC, then CC, and so on.
Input (alpha-sort-test2.txt):
JOHN,1,A
MARY,3,AA
FRED,5,BBB
SAM,7,A
JOHN,3,AAA
JOHN,2,AAA
BETTY,2,AAA
JARROD,7,AAA
JOANNE,2,BB
AMANDA,2,DD
AMY,5,B
PETE,7,CC
MATT,4,B
SARAH,3,CCC
GEORGE,3,CC
AMANDA,3,AAA
The Perl code that I have so far is as follows:
$infile = "alpha-sort-test2.txt";
$outfile = "alpha-sort-test-sorted2.txt";
open (INFILE, "<$infile") or die "Could not open file $infile $!";
open (OUTFILE, ">$outfile");
my #array = sort howtosort <INFILE>;
foreach (#array)
{
chomp;
print "$_\n";
print OUTFILE "$_\n";
}
sub howtosort
{
my #flds_a = split(/,/, $a);
my #flds_b = split(/,/, $b);
$flds_a[2] cmp $flds_b[2];
}
close INFILE;
close OUTFILE;
Current output (alpha-sort-test-sorted2.txt):
JOHN,1,A
SAM,7,A
MARY,3,AA
AMANDA,3,AAA
JOHN,3,AAA
JOHN,2,AAA
BETTY,2,AAA
JARROD,7,AAA
AMY,5,B
MATT,4,B
JOANNE,2,BB
FRED,5,BBB
PETE,7,CC
GEORGE,3,CC
SARAH,3,CCC
AMANDA,2,DD
Desired output:
BETTY,2,AAA
JOHN,2,AAA
AMANDA,3,AAA
JOHN,3,AAA
JARROD,7,AAA
MARY,3,AA
JOHN,1,A
SAM,7,A
FRED,5,BBB
JOANNE,2,BB
MATT,4,B
AMY,5,B
SARAH,3,CCC
GEORGE,3,CC
PETE,7,CC
AMANDA,2,DD
Thanks in advance.
There's a little complication with that criterion for the third field.
Lexicographical comparison goes char by char, so abc is lesser-than ax but longer strings are greater, with all else equal. So ab is lesser-than b but ab is greater-than a.
Thus that requirement for the third field mixes these two things and breaks cmp right down the middle. If we were to use cmp then ab comes before b (correct) but aa comes after a (not wanted). I don't see how to make use of cmp at all for that requirement.
So here's a very basic implementation of it, for these criteria
use warnings;
use strict;
use feature 'say';
use Path::Tiny qw(path); # convenience
my $file = shift // die "Usage: $0 file\n";
my #lines = path($file)->lines({ chomp => 1 });
my #sorted =
map { $_->[0] }
sort { custom_sort($a, $b) }
map { [$_, split /,/] }
#lines;
say for #sorted;
sub custom_sort {
my ($aa, $bb) = #_;
# Last field for both terms, their lengths
my ($af, $bf) = map { $_->[-1] } $aa, $bb;
my ($len_a, $len_b) = map { length } $af, $bf;
# Strip and return first characters and compare them lexicographically
# Then compare lengths of original strings if needed
# Keep going until difference is found or one string is depleted
while (
(my $ca = substr $af, 0, 1, "") and
(my $cb = substr $bf, 0, 1, "") )
{
if ($ca gt $cb) {
return 1
}
elsif ($ca lt $cb) {
return -1;
}
elsif ($len_a < $len_b) {
return 1
}
elsif ($len_a > $len_b) {
return -1
}
}
# Still here, so third field was the same; use other two criteria
return
$aa->[2] <=> $bb->[2]
||
$aa->[1] cmp $bb->[1];
}
This prints out the desired list.
Some comments
Before invoking sort we first form an arrayref, with the whole string and its individual fields, so that the string need not be split later on every single comparison; this is Schwartzian transform
Criterion for the third-field: compare character by character alphabetically until a difference is found; if one string is contained in the other then the longer one wins. So the char-by-char comparison of abc and ab stops at b and abc 'wins'
The (optional) fourth argument in substr is the replacement for the returned substring, found per the second and third argument. So here an empty string replaces one-long substring that starts at 0 -- it removes and returns the first character. This is quite like using shift on an array
If the third fields are exactly the same then the second fields are compared numerically and if they are the same then the first fields are compared alphabetically
After the comparison we retrieve the original string from the sorted arrayrefs

Efficient way to read columns in a file using Perl

I have an input file like so, separated by newline characters.
AAA
BBB
BBA
What would be the most efficient way to count the columns (vertically), first with first, second with second etc etc.
Sample OUTPUT:
ABB
ABB
ABA
I have been using the following, but am unable to figure out how to remove the scalar context from it. Any hints are appreciated:
while (<#seq_prot>){
chomp;
my #sequence = map substr (#seq_prot, 1, 1), $start .. $end;
#sequence = split;
}
My idea was to use the substring to get the first letter of the input (A in this case), and it would cycle for all the other letters (The second A and B). Then I would increment the cycle number + 1 so as to get the next line, until I reached the end. Of course I can't seem to get the first part going, so any help is greatly appreciated, am stumped on this one.
Basically, you're trying to transpose an array.
This can be done easily using Array::Transpose
use warnings;
use strict;
use Array::Transpose;
die "Usage: $0 filename\n" if #ARGV != 1;
for (transpose([map {chomp; [split //]} <>])) {
print join("", map {$_ // " "} #$_), "\n"
}
For an input file:
ABCDEFGHIJKLMNOPQRS
12345678901234
abcdefghijklmnopq
ZYX
Will output:
A1aZ
B2bY
C3cX
D4d
E5e
F6f
G7g
H8h
I9i
J0j
K1k
L2l
M3m
N4n
O o
P p
Q q
R
S
You'll have to read in the file once for each column, or store the information and go through the data structure later.
I was originally thinking in terms of arrays of arrays, but I don't want to get into References.
I'm going to make the assumption that each line is the same length. Makes it simpler that way. We can use split to split your line into individual letters:
my = $line = "ABC"
my #split_line = split //, $line;
This will give us:
$split_line[0] = "A";
$split_line[1] = "B";
$split_line[2] = "C";
What if we now took each letter, and placed it into a #vertical_array.
my #vertical_array;
for my $index ( 0..##split_line ) {
$vertical_array[$index] .= "$split_line[$index];
}
Now let's do this with the next line:
$line = "123";
#split_line = split //, $line;
for my $index ( 0..##split_line ) {
$vertical_array[$index] .= "$split_line[$index];
}
This will give us:
$vertical_array[0] = "A1";
$vertical_array[1] = "B2";
$vertical_array[2] = "C3";
As you can see, I'm building the $vertical_array with each interation:
use strict;
use warnings;
use autodie;
use feature qw(say);
my #vertical_array;
while ( my $line = <DATA> ) {
chomp $line;
my #split_line = split //, $line;
for my $index ( 0..$#split_line ) {
$vertical_array[$index] .= $split_line[$index];
}
}
#
# Print out your vertical lines
#
for my $line ( #vertical_array ) {
say $line;
}
__DATA__
ABC
123
XYZ
BOY
FOO
BAR
This prints out:
A1XBFB
B2YOOA
C3ZYOR
If I had used references, I could probably have built an array of arrays and then flipped it. That's probably more efficient, but more complex. However, that may be better at handling lines of different lengths.

Perl Checking if a scalar contains one of the elements in an array

I have an array
my #array = qw/FOO BAR BAZ/;
and a scalar read from a file containing data like
+++123++585+++FOO
or
+++589++458+++XYZ
I am looking for a nice way to check if an element of the array matches part of the input string.
I know I could just loop over the array and match that way but was wondering if there is a more perl like way.
You can create a regex that matches all of the #array:
my $regex = join '|', map quotemeta, #array;
$string =~ $regex;
Yes, there is far better way. You can construct regular expression. It will be alternatives of fixed strings which is fortunately translated into trie (Aho-Corasick) which leads into linear search time. It is the most efficient way at all.
my #array = qw/FOO BAR BAZ/;
my $re = join '|', map quotemeta, #array;
$re = qr/$re/;
for my $string (#strings) {
if ($string =~ $re) {
...
}
}
That is exactly what grep is for. Here's a small snippet:
use strict;
use warnings;
my $str = "+++123++585+++FOO";
my $blank = "+++123++585+++XYZ";
my #array = qw/FOO BAR BAZ/;
print grep {$str =~ $_} #array, "\n";
print grep {$blank =~ $_} #array, "\n";
This would just return:
FOO
grep, reduce and map are what we call higher order functions in FP world, though reduce might be called fold there. Have a look at MJD's Higher Order Perl for more of these.
grep
map
reduce
Higher Order Perl

How to copy the contents of array into a single variable in Perl?

I have a data in an array as below. I want to copy all the content in a single variable. How can I do this ?
IFLADK
FJ
FAILED
FNKS
FKJ
FAILED
You could assign a reference to the array
my $scalar = \#array;
… or join all the strings in the array together
my $scalar = join "\n", #array;
With reference to previous question How to read n lines above the matched string in perl? Storing multiple hits in an array:
while (<$fh>) {
push #array, $_;
shift #array if #array > 4;
if (/script/) {
print #array;
push #found, join "", #array; # <----- this line
}
}
You could just use a scalar, e.g. $found = join "", #array, but then you would only store the last match in the loop.
Suppose the loop is finished, and now you have all the matches in array #found. If you want them in a scalar, just join again:
my $found = join "", #found;
Or you can just add them all at once in the loop:
$found .= join "", #array;
It all depends on what you intend to do with the data. Having the data in a scalar is rarely more beneficial than having it in an array. For example, if you are going to print it, there is no difference, as print $found is equivalent to print #found, because print takes a list of arguments.
If your intent is to interpolate the matches into a string:
print "Found matches: $found";
print "Found matches: ", #found;
$whole = join(' ', #lines)
But if you're reading the text from a file, it's easier to just read it all in one chunk, by (locally) undefining the record delimiter:
local $/ = undef;
$whole = <FILE>
Depends on what you are trying to do, but if you are wanting to package up an array into a scalar so that it can be retrieved later, then you might want Storable.
use Storable;
my #array = qw{foo bar baz};
my $stored_array = freeze \#array;
...
my #retrieved_array = #{ thaw($stored_array) };
Then again it could be that your needs may be served by just storing a reference to the array.
my #array = qw{foo bar baz};
my $stored_array = \#array;
...
my #retrieved_array = #$stored_array;