I'm having a problem with my if-else statement. Even if the $key I'm using matches the text in my if statement, the else statement I have at the bottom is also evaluated, so I'm getting multiple values for each $key, which is a problem. What am I doing wrong here?
my $key = $line[0];
if ($key eq 'this_setting') {
my $counter1 = $counter + 1;
my $counter2 = $counter + 2;
$value = join(' ', #line[$counter1..$counter2]);
$my_setting_hash{$key} = $value;
}
if ($key eq 'some_setting_abc') {
my $counter1 = $counter + 1;
my $counter2 = $counter + 2;
$value = join(' ', #line[$counter1..$counter2]);
$my_setting_hash{$key} = $value;
}
if ($key eq 'another_setting_123') {
my $counter1 = $counter + 1;
my $counter3 = $counter + 3;
$value = join(' ', #line[$counter1..$counter3]);
$my_setting_hash{$key} = $value;
}
else {
my $counter1 = $counter + 1;
$value = $line[$counter1];
$my_setting_hash{$key} = $value;
}
Why is this else statement not bypassed if one of my if statements is evaluated?
You need to chain them together with elsif:
my $key = $line[0];
if ($key eq 'this_setting') {
my $counter1 = $counter + 1;
my $counter2 = $counter + 2;
$value = join(' ', #line[$counter1..$counter2]);
$my_setting_hash{$key} = $value;
}
elsif ($key eq 'some_setting_abc') {
my $counter1 = $counter + 1;
my $counter2 = $counter + 2;
$value = join(' ', #line[$counter1..$counter2]);
$my_setting_hash{$key} = $value;
}
elsif ($key eq 'another_setting_123') {
my $counter1 = $counter + 1;
my $counter3 = $counter + 3;
$value = join(' ', #line[$counter1..$counter3]);
$my_setting_hash{$key} = $value;
}
else {
my $counter1 = $counter + 1;
$value = $line[$counter1];
$my_setting_hash{$key} = $value;
}
Otherwise, the first two if statements are independent of the third if/else statement.
As has already been pointed out, you need the keyword elsif
However, another solution is to put your special rules for each key into a hash and so that you can share code:
my %key_length = (
this_setting => 1,
some_setting_abc => 1,
another_setting_123 => 2,
);
my $key = $line[0];
my $index_low = $counter + 1;
my $index_high = $index_low + ($key_length{$key} // 0);
$my_setting_hash{$key} = join ' ', #line[ $index_low .. $index_high ];
Let's say $key's value is 'some_setting_abc'. Your first if does not apply, but the second if does. The third if does not apply either but that one has an else therefore that is executed. As #TedHopp pointed out, you need a single if with chained elsifs and a final else instead.
However, I want to point out that there is a lot of duplication in your code. Life is simpler when you write code a bit more succinctly:
my $key = $line[0];
my $index = $counter + 1;
if (($key eq 'this_setting') or ($key eq 'some_setting_abc')) {
$my_setting_hash{$key} = join ' ', #line[$index .. ($index + 1)];
}
elsif ($key eq 'another_setting_123') {
$my_setting_hash{$key} = join ' ', #line[$index .. ($index + 2)];
}
else {
$my_setting_hash{$key} = $line[$index];
}
Related
I am trying to read a file that contains the letters 'n', 's', 'e', 'w'.
How do I look for these letters and add them to a variable called $ns and $ew,
$ns means to add 'n' or subtract 's'.
$ew means to add 'e' or subtract 'w'.
This is what I have:
open (FILE, 'randwalk1')
or die "Could not open file";
my $ns = 0;
my $ew = 0;
while (my $file1 = <FILE>) {
if ($file1 =~ /n+/) {
$ns = $ns + 1;
} elsif ($file1 =~ /s/) {
$ns = $ns - 1;
} elsif ($file1 =~ /e/) {
$ew = $ew + 1;
} elsif ($file1 =~ /w/) {
$ew = $ew - 1;
}
}
The input file (randwalk1) looks like:
ensweswewnnewwwsweenwsssssnewwennnesewewwewsnnewwwsewsenssns
newnwssswennesnseewnwsnwnwnnwwwewes
neesnwnsenwsnnnewwswnnneesswssnswenwsnnwewswwsnennneeeeswewe
You check if the line has an n, you find one, then you ignore the rest of the line. You need to check each character of the line.
my $home_returns = 0;
my $ns = 0;
my $ew = 0;
while (my $line = <>) {
chomp($line);
for my $char (split //, $line) {
if ($char eq "n") { ++$ns; }
elsif ($char eq "s") { --$ns; }
elsif ($char eq "e") { ++$ew; }
elsif ($char eq "w") { --$ew; }
++$home_returns if $ns == 0 && $ew == 0;
}
}
or
my $home_returns = 0;
my %counts = map { $_ => 0 } qw( n s e w );
while (my $line = <>) {
chomp($line);
for my $char (split //, $line) {
++$counts{$char};
++$home_returns
if $counts{n} == $counts{s}
&& $counts{e} == $counts{w};
}
}
I'm creating a Caesar cipher using Perl, but I cant seem to find the error in the code.
I keep getting the error message:
Argument "hello" isn't numeric in addition (+) at ./Lab03.pl line 66, <> line 1.
which is the line $translated += $symbol.
use warnings;
$x = 26;
sub getMode {
$e = "encrypt decrypt";
while ( 'True' ) {
print "Do you wish to encrypt or decrypt a message? \n";
$mode = <STDIN>;
chomp( $mode );
if ( $mode = split( //, $e ) ) {
return $mode;
}
else {
print "Enter either 'encrypt' or 'decrypt'.\n";
}
}
}
sub getMessage {
print "Enter your message:";
$input = <STDIN>;
chomp( $input );
return $input;
}
sub getKey {
$key = 0;
while ( 'True' ) {
print "Enter the key number (1-26): ";
$key = int( <> );
chomp( $key );
if ( $key >= 1 and $key <= $x ) {
return $key;
}
}
}
sub getTranslatedMessage {
( $mode, $message, $key ) = #_;
if ( $mode =~ /^d/ ) {
$key = -$key;
$translated = '';
}
foreach $symbol ( $message ) {
if ( $symbol =~ /[A-Za-z]/ ) {
$num = ord( $symbol );
$num += $key;
}
if ( $symbol =~ /^[A-Z]/ ) {
if ( $num > ord( 'Z' ) ) {
$num -= 26;
}
elsif ( $num < ord( 'A' ) ) {
$num += 26;
}
elsif ( $symbol = /^[a-z]/ ) {
if ( $num > ord( 'z' ) ) {
$num -= 26;
}
elsif ( $num < ord( 'a' ) ) {
$num += 26;
}
$translated += chr( $num );
}
}
else {
$translated += $symbol;
}
}
return $translated;
}
$mode = getMode();
$message = getMessage();
$key = getKey();
print "Your translated text is: '\n' ";
print( getTranslatedMessage( $mode, $message, $key ) );
In Perl, + is numeric addition only. String concatenation is . / .=.
Also:
if ($mode = split(//,$e)){
is incorrect. I believe you want something like:
my %valid_mode = ( 'encrypt' => 1, 'decrypt' => 1);
...
if ( $valid_mode{$mode} ) {
return $mode
The code you have is setting $mode into the number of characters in $e (in an inefficient way).
Here:
foreach $symbol ($message){
in Perl, strings are first class entities; they aren't automatically interpreted as arrays of characters. So to loop over the characters, you need to so something else. The simplest way is:
foreach $symbol ( split //, $message ) {
Here:
elsif ($symbol= /^[a-z]/){
= should be =~.
There is also a problem with which code is in which blocks that prevents upper case characters from being added to the output. It looks to me like the closing brace for your fir st if ($symbol =~ should be just before the later else, and other braces possibly fixed up to match.
Putting all your }'s on a line of their own, indented the same as the line with the corresponding { is a much better idea. It will help you see mismatched braces much more easily.
Here is corrected code, with use strict added and all variables declared:
use warnings;
use strict;
my $x = 26;
sub getMode{
my %valid_mode = ( 'encrypt' => 1, 'decrypt' => 1 );
while ('True'){
print"Do you wish to encrypt or decrypt a message? \n";
my $mode = <STDIN>;
chomp ( $mode);
if ($valid_mode{$mode}) {
return $mode;
}
else {
print "Enter either 'encrypt' or 'decrypt'.\n";
}
}
}
sub getMessage{
print"Enter your message:";
my $input = <STDIN>;
chomp ($input);
return $input;
}
sub getKey{
my $key = 0;
while ('True'){
print"Enter the key number (1-26): ";
$key = int(<>);
chomp ($key);
if ($key >= 1 and $key <= $x){
return $key;
}
}
}
sub getTranslatedMessage{
my ($mode, $message, $key) = #_;
if ($mode =~ /^d/){
$key = -$key;
}
my $translated = '';
foreach my $symbol (split //, $message){
if ($symbol =~ /[A-Za-z]/){
my $num = ord($symbol);
$num += $key;
if ($symbol =~ /^[A-Z]/){
if ($num > ord('Z')){
$num -= 26;
}
elsif ($num < ord('A')){
$num += 26;
}
}
elsif ($symbol=~ /^[a-z]/){
if ($num > ord('z')){
$num -= 26;
}
elsif ($num < ord('a')){
$num += 26;
}
}
$translated .= chr($num);
}
else{
$translated .= $symbol;
}
}
return $translated;
}
my $mode = getMode();
my $message = getMessage();
my $key = getKey();
print"Your translated text is:\n";
print(getTranslatedMessage($mode, $message, $key));
print "\n";
Over all, I suggest you write smaller chunks of code and test them to make sure they worked before assembling them all together.
The problem with the following code is only in one function of the code. The problem function is with a comment head and close. This is my first post to StackOverflow so bear with me. The following script has some modules and other functions that I know work by testing them with the problem function commented out but I just cannot seem to get that one function to work. When ran, the script runs until the enviroment kills the execution.
Basically what this program does is takes a PDB file, copies everything out of the PDB file and creates a new one and pastes all of the original input file content into the new file and appends the cavities(coordinates of center of the cavity and the specified probe radius) that the program is supposed to find.
The problem function within the code is supposed to distinguish between a void space within a bound box of the structure and a cavity. Cavities are considered to be a closed space somewhere within the structure. A void space is any space or coordinate within the bounding box of max and min coorindates where there isn't an atom.The cavity must be large enough to fit into a specified probe radius. There is also a specified resolution when searching through the 3D hashtable of coordinates.
Can anyone tell me why my code isn't working. Anything immediate. I have tested and tested and cannot seem to find the error.
Thank you.
#!/usr/bin/perl
# Example 11-6 Extract atomic coordinates from PDB file
use strict;
use warnings;
use BeginPerlBioinfo; # see Chapter 6 about this module
#open file for printing
open(FH,">results.pdb");
open(PDB,"oneAtom.pdb");
while(<PDB>) { print FH $_; }
close(PDB);
# Read in PDB file
my #file = get_file_data('oneAtom.pdb');
# Parse the record types of the PDB file
my %recordtypes = parsePDBrecordtypes(#file);
# Extract the atoms of all chains in the protein
my %atoms = parseATOM ( $recordtypes{'ATOM'} );
#define some variables and get the atom indices stored in atom_numbers array
my #atom_numbers = sort {$a <=> $b} keys %atoms;
my $resolution = 4.;
my $lo = 1000;
my $hi = -1000;
my $p_rad = 1;
my %pass;
#set the grid boundaries
foreach my $l ( #atom_numbers ) {
for my $i (0..2) {
if ( $atoms{$l}[$i] < $lo ) { $lo = $atoms{$l}[$i]; }
if ( $atoms{$l}[$i] > $hi ) { $hi = $atoms{$l}[$i]; }
}
}
$lo = $lo - 2* $resolution;
$hi = $hi + 2* $resolution;
#compute min distance to the pdb structure from each grid point
for ( my $i = $lo ; $i <= $hi ; $i = $i + $resolution ) {
for ( my $j = $lo ; $j <= $hi ; $j = $j + $resolution ) {
for ( my $k = $lo ; $k <= $hi ; $k = $k + $resolution ) {
my $min_dist = 1000000;
foreach my $l ( #atom_numbers ) {
my $distance = sqrt((($atoms{$l}[0]-($i))*($atoms{$l}[0]-($i))) + (($atoms{$l}[1]-($j))*($atoms{$l}[1]-($j))) + (($atoms{$l}[2]-($k))*($atoms{$l}[2]-($k))));
$distance = $distance - ( $p_rad + $atoms{$l}[3] );
if ( $distance < $min_dist ) {
$min_dist = $distance;
}
}
$pass{$i}{$j}{$k} = $min_dist;
if ( $pass{$i}{$j}{$k} > 0 ) {
$pass{$i}{$j}{$k} = 1;
} else { $pass{$i}{$j}{$k} = 0;
}
}
}
}
#define a starting point on the outside of the grid and place first on list of points
#my #point = ();
my $num_cavities = 0;
#define some offsets used to compute neighbors
my %offset = (
1 => [-1*$resolution,0,0],
2 => [1*$resolution,0,0],
3 => [0,-1*$resolution,0],
4 => [0,1*$resolution,0],
5 => [0,0,-1*$resolution],
6 => [0,0,1*$resolution],
);
##########################################################
#function below with problem
##########################################################
my #point = ();
push #point,[$hi,$hi,$hi];
=pod
#do the following while there are points on the list
while ( #point ) {
foreach my $vector ( keys %offset ) { #for each offset vector
my #neighbor = (($point[0][0]+$offset{$vector}[0]),($point[0][1]+$offset{$vector}[1]),($point[0][2]+$offset{$vector}[2])); #compute neighbor point
if ( exists $pass{$neighbor[0]}{$neighbor[1]}{$neighbor[2]} ) { #see if neighbor is in the grid
if ( $pass{$neighbor[0]}{$neighbor[1]}{$neighbor[2]} == 1 ) { #if it is see if its further than the probe radius
push #point,[($point[0][0]+$offset{$vector}[0]),($point[0][1]+$offset{$vector}[1]),($point[0][2]+$offset{$vector}[2])]; #if it is push it onto the list of points
}
}
}
$pass{$point[0][0]}{$point[0][1]}{$point[0][2]} = 0; #eliminate the point just tested from the pass array
shift #point; #move to the next point in the list
}
=cut
##############################################################
# end of problem function
##############################################################
my $grid_ind = $atom_numbers[$#atom_numbers];
for ( my $i = $lo ; $i <= $hi ; $i = $i + $resolution ) {
for ( my $j = $lo ; $j <= $hi ; $j = $j + $resolution ) {
for ( my $k = $lo ; $k <= $hi ; $k = $k + $resolution ) {
if ( $pass{$i}{$j}{$k} == 1 ) {
$grid_ind = $grid_ind + 1;
my $n = sprintf("%5d",$grid_ind);
my $x = sprintf("%7.3f",$i);
my $y = sprintf("%7.3f",$j);
my $z = sprintf("%7.3f",$k);
my $w = sprintf("%6.3f",1);
my $p = sprintf("%6.3f",$p_rad);
print FH "ATOM $n MC CAV $n $x $y $z $w $p \n";
}
}
}
}
close(FH);
exit;
#do the following while there are points on the list
for ( my $i = $lo ; $i <= $hi ; $i = $i + $resolution ) {
for ( my $j = $lo ; $j <= $hi ; $j = $j + $resolution ) {
for ( my $k = $lo ; $k <= $hi ; $k = $k + $resolution ) {
if ( $pass{$i}{$j}{$k} == 1 ) {
push #point,[$i,$j,$k];
$num_cavities++;
while ( #point ) {
foreach my $vector ( keys %offset ) { #for each offset vector
my #neighbor = (($point[0][0]+$offset{$vector}[0]),($point[0][1]+$offset{$vector}[1]),($point[0][2]+$offset{$vector}[2])); #compute neighbor point
if ( exists $pass{$neighbor[0]}{$neighbor[1]}{$neighbor[2]} ) { #see if neighbor is in the grid
if ( $pass{$neighbor[0]}{$neighbor[1]}{$neighbor[2]} == 1 ) { #if it is see if its further than the probe radius
push #point,[($point[0][0]+$offset{$vector}[0]),($point[0][1]+$offset{$vector}[1]),($point[0][2]+$offset{$vector}[2])]; #if it is push it onto the list of points
}
}
}
$pass{$point[0][0]}{$point[0][1]}{$point[0][2]} = 0; #eliminate the point just tested from the pass array
shift #point; #move to the next point in the list
}
}
}
}
}
#print the results
print "\nthe structure has " . $num_cavities . " cavities.\n\n";
#print the point that are left over (these correspond to the cavities)
#for ( my $i = -10 ; $i <= 10 ; $i = $i + $resolution ) {
# for ( my $j = -10 ; $j <= 10 ; $j = $j + $resolution ) {
# for ( my $k = -10 ; $k <= 10 ; $k = $k + $resolution ) {
# print $i . "\t" . $j . "\t" . $k . "\t" . $pass{$i}{$j}{$k} . "\n";
# }
# }
#}
###################################################
# function
###################################################
sub parseATOM {
my($atomrecord) = #_;
use strict;
use warnings;
my %results = ( );
# Turn the scalar into an array of ATOM lines
my(#atomrecord) = split(/\n/, $atomrecord);
foreach my $record (#atomrecord) {
my $number = substr($record, 6, 5); # columns 7-11
my $x = substr($record, 30, 8); # columns 31-38
my $y = substr($record, 38, 8); # columns 39-46
my $z = substr($record, 46, 8); # columns 47-54
my $r = substr($record, 60, 6); # columns 47-54
#my $element = substr($record, 76, 2); # columns 77-78
# $number and $element may have leading spaces: strip them
$number =~ s/\s*//g;
#$element =~ s/\s*//g;
$x =~ s/\s*//g;
$y =~ s/\s*//g;
$z =~ s/\s*//g;
$r =~ s/\s*//g;
# Store information in hash
#$results{$number} = [$x,$y,$z,$element];
$results{$number} = [$x,$y,$z,$r];
}
# Return the hash
return %results;
}
Here's one thing that is almost certainly slowing things down:
$x =~ s/\s*//g;
$y =~ s/\s*//g;
$z =~ s/\s*//g;
$r =~ s/\s*//g;
It is possible for \s* to match an empty string, so you are replacing empty strings with empty strings, for each empty string in the target string.
Change to:
$x =~ s/\s+//g;
$y =~ s/\s+//g;
$z =~ s/\s+//g;
$r =~ s/\s+//g;
You have the following definitions:
my $lo = 1000;
my $hi = -1000;
So when you get to your first for loop, you will set $i to 1000, and then fail the check to see if it is less than -1000.
I am working on something for learning purposes where I have tackled Collatz using recursion. If you see below I make use of #_ and $_ to keep the for alive.
#!/usr/bin/env perl
sub collatz {
my ($num, $count) = #_;
$count++;
if ($num == 1) {
return $count;
} elsif ($num % 2 == 0) {
return collatz($num/2, $count);
} else {
return collatz($num*3 + 1, $count);
}
}
my $max = 0;
my $saved = 0;
for (1..1000) {
my $length = collatz($_, 0);
print "Num: " . $_ . " Length: " . $length . "\n";
if ($length > $max) {
$max = $length;
$saved = $_;
}
}
print "The longest sequence starts with " . $saved . "\n";
I am trying to use iteration instead of recursion but I just can't think of how to tackle this. I am not after the code in the question, I just want some tips / hints on how to tackle this to get the same result.
I suspect I will need to use a while or an until field.
Any help would be appreciated, again I don't want the exact answer.
Update
Here is my second attempt, which is giving me an error of
Can't return outside a subroutine at answer2.pl line 38.
my $number = 0;
my $counter = 0;
while ($number != 1000) {
$counter++;
if ($number == 1) {
return $counter;
}
elsif ($number % 2 == 0) {
return ($number / 2, $counter);
}
else {
return ($number * 3 + 1, $counter);
}
$number++;
}
print "number" . $number . "counter" . $counter . "\n";
Basically you have tail recursion, which is nice and simple to eliminate.
Instead of collatz calling itself to generate the next step in the sequence, you simply change the variables in-place and loop back to the top.
In its crudest form this would be
sub collatz2 {
my ($num, $count) = #_;
NEXT:
$count++;
if ($num == 1) {
return $count;
}
elsif ($num % 2 == 0) {
$num = $num / 2;
}
else {
$num = $num * 3 + 1;
}
goto NEXT;
}
but it should be written much more nicely than that.
I ended up with this
sub collatz {
my ($num) = #_;
my $count = 1;
while ($num > 1) {
$num = $num % 2 ? $num * 3 + 1 : $num / 2;
++$count;
}
$count;
}
Consider adding the logic that returns when the condition is met in the while.
Spoiler:
my $iter = 0;
while($num != 1){ #do stuff; $iter++ }
Just use a for or while loop with the end condition that your number == 1.
Spoiler:
use strict;
use warnings;
my $max_num = 0;
my $max_steps = 0;
for my $num (1..1000) {
my $steps = 0;
for (my $i = $num; $i != 1; $steps++) {
$i = $i % 2 ? 3 * $i + 1 : $i / 2;
}
print "Num: " . $num . " Length: " . $steps . "\n";
if ($steps > $max_steps) {
$max_num = $num;
$max_steps = $steps;
}
}
print "The longest sequence starts with " . $max_num . "\n";
I read Stack Overflow question How do I convert a binary string to a number in Perl? on how to convert binary integers to decimal or vice versa in Perl. But how do I do this for float as well?
For example, conversion from 5.375 to 101.011 and vice versa.
sub number_to_binary_string {
my $in = shift;
my $sign = $in < 0 and $in = abs $in;
my $out = sprintf "%b.", int $in;
substr $out, 0, 0, '-' if $sign;
$in -= int $in;
do {
if ($in >= .5) {
$out .= '1';
$in -= .5;
}
else {
$out .= '0';
}
$in *= 2;
} while $in > 0;
return $out;
}
sub binary_string_to_number {
my $in = shift;
my ($int,$frac) = split /\./, $in;
my $sign = $int =~ s/^-//;
my $out = oct "0b$int";
my $mult = 1;
for my $digit (split //, $frac) {
$mult *= .5;
$out += $mult * $digit;
}
$out = -$out if $sign;
return $out;
}
Below is a machine- and build-specific implementation (NV = little-endian double).
It returns the number stored exactly, and it supports NaN, Infinity, -Infinity and -0 and subnormals. It trims leading zeros and trailing decimal zeroes.
sub double_to_bin {
my ($n) = #_;
my ($s, $e, $m) = unpack 'a a11 a52', unpack 'B64', "".reverse pack 'F', $n;
$s = $s ? '-' : '';
$e = oct("0b$e");
if ($e == 0x7ff) {
return ($m =~ /1/) ? 'NaN' : $s . 'Infinity'
} elsif ($e == 0x000) {
$m = "0$m"; $e -= 52;
} else {
$m = "1$m"; $e -= 1075;
}
if ($e >= 0) {
$m .= ('0' x $e);
} elsif ($e >= -52) {
substr($m, $e+53, 0, '.');
} else {
$m = '0.' . ('0' x (-$e-53)) . $m;
}
$m =~ s/^0+(?!\.)//;
$m =~ s/(?:\..*1\K|\.)0+\z//;
return $s . $m;
}
Here's a sketch of an interesting "portable" implementation. It doesn't handle any of the interesting edge-cases like integers, NaNs, infinities, or even negative numbers because I'm lazy, but extending it wouldn't be so hard.
(my $bin = sprintf "%b.%032b", int($num), 2**32 * ($num - int($num)))
=~ s/\.?0+$//;
The 2**32 seems like an architecture-specific magic number but in fact it's basically just how many bits of precision you want after the dot. Too small and you get harmless truncation; too large and there's potential for overflow (since %b probably casts to UV sometime before doing its formatting).
$TO_BIN = '-b';
$TO_DEC = '-d';
($op, $n ) = #ARGV;
die("USAGE: $0 -b <dec_to_convert> | -d <bin_to_convert>\n") unless ( $op =~ /^($TO_BIN|$TO_DEC)$/ && $n );
for (split(//,$n)) {
if ($_ eq ".") {
$f=".";
} else {
if (defined $f) { $f.=$_ } else { $i.=$_ }
}
}
$ci = sprintf("%b", $i) if $op eq $TO_BIN;
$ci = sprintf("%d", eval "0b$i") if $op eq $TO_DEC;
#f=split(//,$f) if $f;
if ($op eq $TO_BIN) {
while( $f && length($cf) < 16 ) {
($f *= 2) =~ s/(\d)(\.?.*)/$2/;
$cf .= $1 ? '1' : '0';
}
} else {
for ($i=1;$i<#f;$i++) {
$cf = ($cf + $f[#f-$i])/2;
}
}
$cf=~s/^.*\.|^/./ if $cf;
print("$ci$cf\n");