Need help creating a conditional statement with array - perl

I wanted to create a conditional statement that would say if an element of an array is odd or even after getting the elements of the array from a line. Here's the code:
#! /usr/bin/perl
use warnings;
use strict;
my $numbers='23 45 34 12 9 3 56';
chomp $numbers;
my #getnum= (split(/ /, $numbers));
my $a;
if($getnum[0]>10){
$a=$getnum[0];
}
if($a%2==0){
print $a, " is even";
}
else{
print $a, " is odd";
}
Now the problem is I only did it for the first element. Is there a way I can do this for all elements without creating a conditional statement for each? Thanks for your help!

You need to use a for (or foreach) loop.
for my $n (#numbers) { # Loops over #numbers, assigning each to $n
if ( $n % 2 == 0 ) {
print "$n is even"
}
}
Additionally, this is rather un-idiomatic
my $numbers='23 45 34 12 9 3 56';
chomp $numbers;
my #getnum= (split(/ /, $numbers));
If you have a string you wish to split on whitespace, there is a special way to do that in Perl
split( ' ', $string );
This will split on arbitrary whitespace (and will strip leading and trailing whitespace), eg.
my #words = split( ' ', ' one two three ' );
# #words is ('one', 'two', 'three')
But if you are just hardcoding the number in your script itself, you can bypass the split all-together and use the 'quote-words' syntax
my #numbers = qw( 23 45 34 12 9 3 56 );
Hope this helps.

Related

Splitting and tallying substrings within mixed integer-string data

Input Data (example):
40A3B35A3C
30A5B28A2C2B
Desired output (per-line) is a single number determined by the composition of the code 40A3B35A3C and the following rules:
if A - add the proceeding number to the running total
if B - add the proceeding number to the running total
if C - subtract the proceeding number from the running total
40A 3B 35A 3C would thus produce 40 + 3 + 35 - 3 = 75.
Output from both lines:
75
63
Is there an efficient way to achieve this for a particular column (such as $F[2]) in a tab-delimited .txt file using a one-liner? I have considered splitting the entire code into individual characters, then performing if statement checks to detect A/B/C, but my Perl knowledge is limited and I am unsure how to go about this.
When you use split with a capture, the captured group is returned from split, too.
perl -lane '
#ar = split /([ABC])/, $F[2];
$s = 0;
$s += $n * ("C" eq $op ? -1 : 1) while ($n, $op) = splice #ar, 0, 2;
print $s
' < input
Or maybe more declarative:
BEGIN { %one = ( A => 1,
B => 1,
C => -1 ) }
#ar = split /([ABC])/, $F[2];
$s = 0;
$s += $n * $one{$op} while ($n, $op) = splice #ar, 0, 2;
print $s
When working through a string like this, it's useful to know that regular expressions can return a list of results.
E.g.
my #matches = $str =~ m/(\d+[A-C])/g; #will catch repeated instances
So you can do something like this:
#!/usr/bin/env perl
use strict;
use warnings;
while (<DATA>) {
my $total;
#break the string into digit+letter groups.
for (m/(\d+[A-C])/g) {
#separate out this group into num and code.
my ( $num, $code ) = m/(\d+)([A-C])/;
print "\t",$num, " => ", $code, "\n";
if ( $code eq "C" ) {
$total -= $num;
}
else {
$total += $num;
}
}
print $total, " => ", $_;
}
__DATA__
40A3B35A3C
30A5B28A2C2B
perl -lne 'push #a,/([\d]+)[AB]/g;
push #b,/([\d]+)[C]/g;
$sum+=$_ for(#a);$sum-=$_ for(#b);
print $sum;#a=#b=();undef $sum' Your_file
how it works
use the command line arg as the input
set the hash "%op" to the
operations per letter
substitute the letters for operators in the
input evaluate the substituted input as an expression
use strict;
use warnings;
my %op=qw(A + B + C -);
$ARGV[0] =~ s/(\d+)(A|B|C)/$op{$2} $1/g;
print eval($ARGV[0]);

How can I use the until function with appropriate way

I have a file that I want to filter which is like that:
##matrix=axtChain 16 91,-114,-31,-123,-114,100,-125,-31,-31,-125,100,-114,-123,-31,-114,91
##gapPenalties=axtChain O=400 E=30
chain 21455232 chr20 14302601 + 37457 14119338 chr22 14786829 + 3573 14759345 1
189 159 123
24 30 22
165 21 20
231 105 0
171 17 19
261 0 2231
222 2 0
253 56 48
chain 164224 chr20 14302601 + 1105938 1125118 chr22 14786829 + 1081744 1100586 8
221 352 334
24 100 112
34 56 56
26 50 47
…………………….
chain 143824 chr20 14302601 + 1105938 1125118 chr22 14786829 + 1081744 1100586 8
So, briefly,there are blocks separated by a blank line.
Each block begins with the line " chain xxxxx " and continues with lines with numbers.
I want to filter out the file and keep just the blocks with chain and the number that follows be greater than 3000.
I wrote the following script to do that:
#!/usr/bin/perl
use strict;
use warnings;
use POSIX;
my $chain = $ARGV[0];
#It filters the chains with chains >= 3000.
open my $chain_file, $chain or die "Could not open $chain: $!";
my #array;
while( my $cline = <$chain_file>) {
#next if /^\s*#/;
chomp $cline;
#my #lines = split (/ /, $cline);
if ($cline =~/^chain/) {
my #lines = split (/\s/, $cline);
if ($lines[1] >= 3000) {
#print $lines[1];
#my #lines = split (/ /, $cline);
#print "$cline\n";
push (#array, $cline);
}
}
until ($cline ne ' ') {
push (#array, $cline);
}
foreach (#array) {
print "$_\n";
}
undef(#array);
}
The problem is that I can print just the headers (chain XXXXX…..) and not the numbers that follows at the next lines of each block.
I'm using the until function till will find the blank line, but it doesn't work.
If someone could help me with that….
Thank you very much in advance,
Vasilis.
The first problem here is that ' ' is a single space, not a blank line ("" or '' should be fine since you've already chomp-ed the line.
The second problem is that
until ( $cline ne "" )
is the same as
while ( $cline eq "" )
which is the opposite of what you need to push lines to #array.
That said, the flip-flop operator is probably a more suitable construct for what you're after:
my #array;
while ( <$chain_file> ) { # Using $_ instead of $cline
chomp;
if ( do { /^chain\s+(\d+)/ && $1 >= 3000 } .. /^$/ ) {
# Accumulate lines in #array
push #array, $_; # False until LHS evaluates to true ...
} # ... then true until RHS evaluates to true
else {
for ( #array ) {
print $_, "\n"; # Print matches
}
#array = (); # Reset/clear out #array
}
}
It's usually best not to use unless instead of while. It negates the boolean expression many times leaving you with a double negative to solve. Here's an example
while ( $foo ne $bar ) {
Even though this is a negative expression, I can pretty easily figure out when to exit my loop. However:
until ( $foo eq $bar ) {
Just takes time to figure out.
Also, ' ' does not make a blank line: Use the regular expression $cline =~ /^\s*$/. However, even beyond that the loop:
until ($cline ne ' ') {
push (#array, $cline);
}
will go on forever if $cline does equal blank. You're never changing the value of $cline.
You can use what I use to call state variables (until Perl actually created a variable type called state and now I have no idea what to call them.) This variable tracks where you are in your file. Are you inside a chain section of the file? Do you want these lines or not? This way, you only have a single loop. You set your state variables and then process your loop.
In this example, I have a state variable called $keep_line which is asking whether or not I want to keep the lines I want to read in. If the line starts with chain and the second field is greater than 3000, I want to keep the entire block (if I understand what you're attempting to do). (By the way, I'm keeping blank lines. Is that okay?)
my $keep_lines = 0; # Aren't in lines I want to keep
my #good_lines; # Array where you want to keep the good lines
while ( my $line = <$chain_file> ) {
chomp $line; # Good habit to always chomp a input as soon as it's read.
if ( $line =~ /^chain/ ) { # This is a chain line. Do I want to keep this group?
my #fields = ( /\s+/, $line );
if ( $field[1] > 3000 ) { # Yes, if the second field is greater than 3000
$keep_lines = 1; # Keep this and all other lines until the next chain line
}
else {
$keep_lines = 0; # Skip until the next chain line
}
}
if ( $keep_lines ) {
push #good_lines, $line;
}
}
I also smell a function here: Instead of the tangle of if clauses, I would probably make this a function that returns the value I set $keep_lines to.
while ( my $line = <$chain_file> ) {
chomp $line; # Good habit to always chomp a input as soon as it's read.
$keep_lines = keep_this_section( $line );
if ( $keep_lines ) {
push #good_lines, $line;
}
}
Simplifies the code quite a bit.
I would also declare some constants to remove those Mysterious Moes. Those are things like 3000 and /^chain/ that have some sort of mysterious, but important meaning in your program. You can use constant as a pragma to define Perl constants. It's part of standard Perl:
use constant {
KEEP_LIMIT => 3000,
SECTION_HEADER => qr/^chain/,
};
Now, I can do things like this:
if ( $line =~ SECTION_HEADER ) {
instead of:
if ( $line =~ /^chain/ ) {
and
if ( $field[1] > KEEP_LIMIT ) {
instead of
if ( $field[1] > 3000 ) {
There are problems with the constant pragma. The biggest is that it just doesn't interpolate in places where Perl will normally interpolate variables. This include double quoted strings and hash keys. If I have $foo{KEEP_LIMIT}, Perl will interpret the key as a string KEEP_LIMIT and not as a constant of KEEP_LIMIT.
Many developers use Readonly which is just so much better in so many ways. Unfortunately, Readonly isn't a standard Perl module, so you have to install it via CPAN, and that's sometimes not possible or desirable to do. So, I tend to use constant.

$array can't print anything

This is my program , I want to let user type a matrix line by line and print the while matrix , but I can't see the matrix
The user will type
1 2 3
4 5 6
7 8 9
like this
and I want to let it show
1 2 3
4 5 6
7 8 9
Perl program
$Num = 3;
while($Num > 0 )
{
$Row = <STDIN>;
$Row = chomp($Row);
#Row_array = split(" ",$Row);
push #P_matrix , #Row_array;
#Row_array = ();
$Num = $Num - 1;
}
for($i=0;$i<scalar(#P_matrix);$i++)
{
for($j=0;$j<scalar(#P_matrix[$i]);$j++)
{
printf "$d ",$P_matrix[$i][$j];
}
print "\n";
}
I change the expression => printf "$d ",$P_matrix[$i][$j]; to print $P_matrix[$i][$j]
but still don't work.
To create a multi-dimensional array, you have to use references. Use
push #P_matrix, [ #Row_array ];
to create the desired structure.
Also, chomp does not return the modified string. Simply use
chomp $Row;
to remove a newline from $Row. Moreover, chomp is not needed at all if you split on ' '.
printf uses % as the formatting character, not $.
You can use Data::Dumper to inspect complex data structures. Use strict and warnings to help you avoid common problems. Here is how I would write your program:
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my #p_matrix;
push #p_matrix , [ split ' ' ] while <>;
warn Dumper \#p_matrix;
for my $i (0 .. $#p_matrix)
{
for my $j (0 .. $#{ $p_matrix[$i] })
{
printf '%d ', $p_matrix[$i][$j];
}
print "\n";
}
First and foremost please use use strict; use warnings;
Issues in your code:
You have a single dimensional array, but your are trying to access
it like two dimensional array. In order to make 2 dimensional array push the array reference of Row_array in #P_matrix as [#Row_array].
Where is $d defined? declare $d as my $d or our $d if you mean $d as scalar variable.
OR
For using %d, use need sprintf. Please read this.

Cutting apart string in Perl

I have a string in Perl that is 23 digits long. I need to cut it apart into different pieces. First 2 digits in one variable, next 3 in another variable, next 4 into another variable, etc. Basically the 23 digits needs to end up as 6 separate variables (2,3,4,4,3,7) characters, in that order.
Any ideas how I can cut the string up like this?
There are lots of ways to do it, but the shortest is probably unpack:
my $string = '1' x 23;
my #values = unpack 'A2A3A4A4A3A7', $string;
If you need separate variables, you can use a list assignment:
my ($v1, $v2, $v3, $v4, $v5, $v6) = unpack 'A2A3A4A4A3A7', $string;
Expanding on Alex's method, rather than specify each start and end, use the list you gave of lengths.
#!/usr/bin/env perl
use strict;
use warnings;
my $string = "abcdefghijklmnopqrstuvw";
my $pos = 0;
my #split = map {
my $start = $pos;
my $end = $_;
$pos += $end;
substr( $string, $start, $end);
} (2,3,4,4,3,7);
print "$_\n" for #split;
This said you probably should look at unpack which is used for fixed width fields. I have no experience with it though.
You could use a regex, viz:
$string =~ /\d{2}\d{3}\d{4}\d{4}\d{3}\d{7}/
and capture each part by surrounding with brackets ().
You then find each capture in the variables $1, $2 ...
or get them all in the returned list
See perldoc perlre
You want to use perldoc substr.
$substring = substr($string, $start, $length);
I'd also use `map' on a list of [start, length] pairs to make your life easier:
$string = "123456789";
#values = map {substr($string, $_->[0], $_->[1])} ([1, 3], [4, 2] , ...);
Here's a sub that will do it, using the already discussed unpack.
sub string_slices {
my $str = shift;
return unpack( join( 'A', '', #_ ), $str );
}

Is there any way to grab a slice to the end of an anonymous array in Perl?

So this has been making me go bonkers for the last half hour. Is there any way for me to grab an array slice to the end of an anonymous array? I've tried:
(split(' ',$test_line))[1..$#_]
and I've tried:
(split(' ',$test_line))[1..-1]
but aggravatingly, neither of those work. I really don't want to have an extra temp variable instantiated to the intermediate array (which I don't need). And I really don't want to use an ugly and unreadable one liner (found a few of those online). Is there really no straight forward way to do this?
A list, which is what you have in your example, can not be sliced from the end. This is mainly because lists are not proper data structures in Perl, but more a construct that the interpreter uses to move data around. So knowing that you can only slice a list from the begining, your options are to either put it in an array variable and then slice, change your algorithm to return what you want, or the following:
If you are assigning this value to something, you can use undef in each slot you dont want:
my (undef, #list) = split ' ' => $test_line;
If you post some more code, I can revise.
Alternatively, you can use some tools from functional programming. The pair of functions drop and take can be useful to resize a list without additional variables:
sub take {
my $n = shift;
#_[0..$n-1]
}
sub drop {
my $n = shift;
#_[$n..$#_]
}
and then your example becomes
drop 1, split ' ' => $test_line;
drop 1 is also commonly called tail
sub tail {drop 1, #_}
and of course, since all of these are so short, if you wanted to inline it:
sub {shift; #_}->(split ' ' => ...)
When the OP said slice, I thought of splice:
#allTheWordsExceptTheFirstTwo = splice #{[split' ', $test_line]}, 2;
#allExceptTheFirstAndLastTwo = splice #{[split' ', $test_line]}, 2, -2;
You can use negative ranges in the array subscript to address an arbitrary number of elements from the end:
my $x = join ' ' => 'a' .. 'z';
my #x = (split ' ', $x)[-13 .. -1];
However, this requires you to know the total number of elements in the result of split to eliminate just the first element.
If this happens in only one place, using a do block should work:
my $x = join ' ', 'a' .. 'z';
my #x = do { my #y = (split ' ', $x); #y[1 .. $#y] };
In your case, I would factor out the whole operation to a subroutine if it is supposed to be used frequently, passing the string rather than the result of the split to the subroutine (can be further generalized by allowing the user to pass the split pattern as well:
my $x = join ' ', 'a' .. 'g';
my #x = skip_first_n_from_split(3, $x);
print Dump \#x;
sub skip_first_n_from_split {
my ($n, $x) = #_;
my #y = split ' ', $x;
return #y[$n .. $#y];
}
Having fun:
#!/usr/bin/perl
use strict; use warnings;
my $x = join ' ', 1 .. 8;
my #skippers = map make_skipper(' ', $_), 0 .. 7;
print "#$_\n" for map $_->($x), #skippers;
sub make_skipper {
my ($pattern, $n) = #_;
return sub {
my $string = shift;
my $i = 0;
return [ grep $i++ >= $n, split $pattern, $string ];
}
}
Output:
1 2 3 4 5 6 7 8
2 3 4 5 6 7 8
3 4 5 6 7 8
4 5 6 7 8
5 6 7 8
6 7 8
7 8
8
I don't believe you can specify an index for the last element of an arbitrary list expression, but how about:
split(' ', (split ' ', $test_line, 2)[1])
By the way, there are no anonymous arrays here (or in your original question), only lists.
This just got answered 'neatly' on Perlmonks by BrowserUK, this is what you'd do:
my #slice = sub{ #_[1..$#_] }->( split ' ', $test_line );
if you're open to splitting twice:
my #g = (split ' ', $test_str)[1..split(' ', $test_str)];
or more correctly (since split returns the number of fields found (one more than the last field's index since it is 0-based):
my #g = (split ' ', $test_str)[1..split(' ', $test_str)-1];
unfortunately these throw a deprecated warning under 'warnings' pragma, and clobbers the contents of #_ (unless you're using 5.12, then you're good, otherwise go with a temporary variable, inline sub or a loop).