Perl substr error - perl

I have problem with Perl substr function. It is so simple, but anyway... Don't know what is the problem.
sub encode_date
{
$date = $_[0];
$day = substr($date, 0, 2);
$month = substr($date, 2, 2);
$year = substr($date, 6, 4);
return "$year-$month-$day";
}
Sub accept "DD.MM.YYYY" formatted string and have to convert it to "YYYY-MM-DD" format.
Input : 09.09.1993
Output : 0-09-93-19 ???
Can anybody explain what's wrong, please.
p.s.: I wrote another sub for decoding date (from 'yyyy-mm-dd' to 'dd.mm.yyyy') and all work properly.
sub decode_date
{
$date = $_[0];
$year = substr $date, 0, 4;
$month = substr $date, 5, 2;
$day = substr $date, 8, 2;
return $day.".".$month.".".$year;
}
Sure, I tried both substr $date, 0, 2; and substr($date, 0, 2); and different combinations of the return value.

Month starts at index 3, not 2.
sub encode_date
{
my ($date) = #_;
my $day = substr($date, 0, 2);
my $month = substr($date, 3, 2);
my $year = substr($date, 6, 4);
return "$year-$month-$day";
}
Perhaps better way would be splitting string by non-digit(.), reverse numbers, and join them with dash -
sub encode_date
{
my ($date) = #_;
return join "-", reverse split /\D/, $date;
}

Your problem is that your offsets are wrong, because they don't take into account the separators. (For example, the month starts at offset 3, not 2, because it comes after the two-character date plus a period.)
That said, I think the best way to write this function is:
sub encode_date($) {
if ($_[0] =~ /^(\d\d)\.(\d\d)\.(\d{4})$/) {
return "$3-$2-$1";
}
}

sub encode_date
{
$date = $_[0];
$day = substr($date, 0, 2);
$month = substr($date, 3, 2);
$year = substr($date, 6, 4);
return "$year-$month-$day";
}
There were 2 problems. I used encode_date(encode_date($date)); (2 times)Another was $month = substr($date, 2, 2); Correct is $month = substr($date, 3, 2);

Related

Perl : Getting last Friday of the month

I am trying to get the last Friday of the month. I found out the awesome awk script that could to do this job. I try to port it perl but facing some issues. Any insight would be a great help. I can't use any perl modules apart from the inbuilt one, thats why I have to go through building this stuff.
Thanks for your help.
AWK script :
BEGIN {
split("31,28,31,30,31,30,31,31,30,31,30,31",daynum_array,",") # days per month in non leap year
year = ARGV[1]
if (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)) {
daynum_array[2] = 29
}
y = year - 1
k = 44 + y + int(y/4) + int(6*(y/100)) + int(y/400)
for (m=1; m<=12; m++) {
k += daynum_array[m]
d = daynum_array[m] - (k%7)
printf("%04d-%02d-%02d\n",year,m,d)
}
exit(0)
}
My Perl script :
my #time = localtime;
my ($month, $year) = #time[4, 5];
$year += 1900;
#months = qw( 31 28 31 30 31 30 31 31 30 31 30 31 );
$months[1] = check_leap_year($year) ? 29 : 28;
$y = $year - 1;
$k = 44 + $y + int($y / 4) + int(6 * ($y / 100)) + int($y / 400);
$k += $months[$month];
$d = $months[$month] - ($k % 7);
$month += 1;
printf "%04d-%02d-%02d\n", $year, $month, $d;
sub check_leap_year {
my $year = shift;
return 0 if $year % 4;
return 1 if $year % 100;
return 0 if $year % 400;
return 1;
}
There's a few ways to do this. Using Time::Piece isn't the simplest, it isn't designed for date math, but you don't have to install additional software.
use v5.10;
use strict;
use warnings;
use Time::Piece;
sub get_last_dow_in_month {
my($year, $month, $dow) = #_;
# Get a Time::Piece object at the last day of the month.
my $first_of_the_month = Time::Piece->strptime("$year $month", "%Y %m");
my $last_day = $first_of_the_month->month_last_day;
my $last_of_the_month = Time::Piece->strptime("$year $month $last_day", "%Y %m %d");
# Figure out how many days you need to go back.
my $days_offset = -(($last_of_the_month->day_of_week + (7 - $dow)) % 7);
return $last_of_the_month->mday + $days_offset;
}
say get_last_dow_in_month(2014, 3, 5);
If you need to do more date processing, DateTime is the most comprehensive.
Modules are made to be used. Calc last friday of month on PerlMonks contains some examples.
E.g.
$ perl -MDate::Manip -E 'say UnixDate(ParseDate("last Friday in March 2015"),"Last Friday of the month is %B %E, %Y.")
Last Friday of the month is March 27th, 2015.
Rather than working around the technical limitation, you need to work around the social limitation that is hampering the technical side of your job.
If you are constrained by using core modules only, this is the way to compute it:
use strict;
use warnings;
use Time::Local qw[];
# Computes the last day in the given month which occurs on the given
# day of the week. Returns the day of the month [22, 31].
# 1 <= $month <= 12
# 0 <= $dow <= 6 (0=Sunday)
sub last_dow_in_month {
my ($year, $month, $dow) = #_;
$year += int($month / 12);
$month %= 12;
my $time = Time::Local::timegm(0, 0, 0, 1, $month, $year) - 86400;
my ($mday, $wday) = (gmtime($time))[3,6];
return $mday - ($wday - $dow) % 7;
}
my $year = 2015;
foreach my $month (1..12) {
printf "%.4d-%.2d-%.2d\n",
$year, $month, last_dow_in_month($year, $month, 5);
}
Output:
2015-01-30
2015-02-27
2015-03-27
2015-04-24
2015-05-29
2015-06-26
2015-07-31
2015-08-28
2015-09-25
2015-10-30
2015-11-27
2015-12-25
Using DateTime the code becomes more readable:
use DateTime;
# Computes the last day in the given month which occurs on the given
# day of the week. Returns the day of the month [22, 31].
# 1 <= $month <= 12
# 1 <= $dow <= 7 (1=Monday)
sub last_dow_in_month {
my ($year, $month, $dow) = #_;
my $dt = DateTime->last_day_of_month(year => $year, month => $month);
$dt->subtract(days => ($dt->day_of_week - $dow) % 7);
return $dt->day_of_month;
}
If performance is of essence, Time::Moment can be used to compute it:
use Time::Moment qw[];
# Computes the last day in the given month which occurs on the given
# day of the week. Returns the day of the month [22, 31].
# 1 <= $month <= 12
# 1 <= $dow <= 7 (1=Monday)
sub last_dow_in_month {
my ($year, $month, $dow) = #_;
my $tm = Time::Moment->new(year => $year, month => $month)
->plus_months(1)
->minus_days(1);
return $tm->minus_days(($tm->day_of_week - $dow) % 7)
->day_of_month;
}
A correct implementation of your intended algorithm:
# Computes the last friday in the given month. Returns the day of the
# month [22, 31].
# 1 <= $month <= 12
sub last_friday_in_month {
my ($year, $month) = #_;
my $days = (
[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365],
[0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366],
)[($year % 4) == 0 && ($year % 100 != 0 || $year % 400 == 0)];
my $y = $year - 1;
my $k = 44 + $y + int($y/4) + int(6 * ($y/100)) + int($y/400);
$k += $days->[$month];
return $days->[$month] - $days->[$month - 1] - ($k % 7);
}
This is just for variety. As #Schwern notes, "cal is a clever hack, but it's a crutch that lets the OP avoid learning to use a good date library. Calendaring is hard, use a library."
Assuming you have cal on your system
#!/usr/bin/env perl
use strict;
use warnings;
local $ENV{LC_ALL} = 'C';
my #cal = `cal`;
my $last_friday;
for (my $i = $#cal; $i >= 0; $i -= 1) {
my #dates = split ' ', $cal[$i];
next unless #dates > 5;
$last_friday = $dates[5];
last;
}
print "$last_friday\n";
Or, more succinctly, but somewhat less efficiently:
#!/usr/bin/env perl
use strict;
use warnings;
local $ENV{LC_ALL} = 'C';
my ($last_friday) = grep defined, map +(split)[5], reverse `cal`;
print "$last_friday\n";
Or, even,
#!/usr/bin/env perl
use strict;
use warnings;
use List::Util qw( first );
local $ENV{LC_ALL} = 'C';
my $last_friday = first { defined } map +(split)[5], reverse `cal`;
print "$last_friday\n";

Performing a function on each combination of variables in two arrays

I am trying to take one set of data and subtract each value in that data by another set of data.
For example:
Data set one (1, 2, 3)
Data set two (1, 2, 3, 4, 5)
So I should get something like (1 - (1 .. 5)) then (2 - (1..5)) and so on.
I currently have:
#!/usr/bin/perl
use strict;
use warnings;
my $inputfile = $ARGV[0];
open( INPUTFILE, "<", $inputfile ) or die $!;
my #array = <INPUTFILE>;
my $protein = 'PROT';
my $chain = 'P';
my $protein_coords;
for ( my $line = 0; $line <= $#array; ++$line ) {
if ( $array[$line] =~ m/\s+$protein\s+/ ) {
chomp $array[$line];
my #splitline = ( split /\s+/, $array[$line] );
my %coordinates = (
x => $splitline[5],
y => $splitline[6],
z => $splitline[7],
);
push #{ $protein_coords->[0] }, \%coordinates;
}
}
print "$protein_coords->[0]->[0]->{'z'} \n";
my $lipid1 = 'MEM1';
my $lipid2 = 'MEM2';
my $lipid_coords;
for ( my $line = 0; $line <= $#array; ++$line ) {
if ( $array[$line] =~ m/\s+$lipid1\s+/ || $array[$line] =~ m/\s+$lipid2\s+/ ) {
chomp $array[$line];
my #splitline = ( split /\s+/, $array[$line] );
my %coordinates = (
x => $splitline[5],
y => $splitline[6],
z => $splitline[7],
);
push #{ $lipid_coords->[1] }, \%coordinates;
}
}
print "$lipid_coords->[1]->[0]->{'z'} \n";
I am trying to take every value in $protein_coords->[0]->[$ticker]->{'z'} minus each value in $lipid_coords->[1]->[$ticker]->{'z'}.
My overall objective is to find (z2-z1)^2 in the equation d = sqrt((x2-x1)^2+(y2-y1)^2-(z2-z1)^2). I think that if I can do this once then I can do it for X and Y also. Technically I am trying to find the distance between every atom in a PDB file against every lipid atom in the same PDB and print the ResID for distance less than 5A.
To iterate on all combinations of two arrays, just embed two for loops:
use strict;
use warnings;
my #dataone = (1, 2, 3);
my #datatwo = (1, 2, 3, 4, 5);
for my $one (#dataone) {
for my $two (#datatwo) {
print "$one - $two\n";
}
}
Outputs:
1 - 1
1 - 2
1 - 3
1 - 4
1 - 5
2 - 1
2 - 2
2 - 3
2 - 4
2 - 5
3 - 1
3 - 2
3 - 3
3 - 4
3 - 5
This will give you the result of subtracting each element of set 2 from each element of set 1 in what I believe is the manner you were asking.
#!/usr/bin/perl
use strict;
use warnings;
my #set1 = (1, 2, 3);
my #set2 = (1, 2, 3, 4, 5);
my #set3 = ();
for my $val (#set1) {
push #set3, map { $val - $_ } #set2;
}
local $" = ', ';
print "#set3\n";
system 'pause';
The result will be an array containing (1 - (1..5), 2 - (1..5), 3 - (1..5)).
Contents of #set3 after script runs:
0, -1, -2, -3, -4, 1, 0, -1, -2, -3, 2, 1, 0, -1, -2
All the other protein and lipid stuff is way over my head, but I hope this at least helps a little. You should now have an array containing the subtracted elements that you can work with to get the rest of your results!
Edit:
Can replace the loop with this one liner :)
my #set3 = map { my $v = $_; map { $v - $_ } #set2 } #set1;
map is a pretty nifty function!
The easiest way to do this is to do your calculations while you're going through file two:
for (my $line = 0; $line <= $#array; ++$line) {
if (($array[$line] =~ m/\s+$lipid1\s+/) | ($array[$line] =~ m/\s+$lipid2\s+/)) {
chomp $array[$line];
my #splitline = (split /\s+/, $array[$line]);
my %coordinates = (x => $splitline[5],
y => $splitline[6],
z => $splitline[7],
);
push #{$lipid_coords->[1]}, \%coordinates;
# go through each of the sets of protein coors in your array...
for my $p (#{$protein_coords->[0]}) {
# you can store this value however you want...
my $difference = $protein_coords->[0][$p]{z} - $coordinates{z};
}
}
}
If I were you, I would use some form of unique identifier to allow me to access the data on each combination -- e.g. build a hash of the form $difference->{<protein_id>}{<lipid_id>} = <difference>.

Perl mergesort - array reference

I was trying to implement merge-sort in Perl, I am quite new to Perl and I know I am doing something wrong with the array references. The arrays end up holding the same value after the process is done. Please help cause I don't see where I am going wrong.
The Corrected Code:
use strict;
use warnings;
my ( #aref, #auxref ) = ();
my ( $hi, $lo, $i, $j, $k, $n ) = 0;
#aref = ( 5, 7, 6, 3, 4, 1, 8, 9, 4 );
$n = #aref;
mergeSort( \#aref, \#auxref, 0, $n - 1 );
print "#auxref\n";
print "#aref\n";
sub mergeSort {
my ($aref) = $_[0];
my ($auxref) = $_[1];
my $lo = $_[2];
my $hi = $_[3];
if ( $hi <= $lo ) { return; }
my $mid = 0;
$mid = int( $lo + ( $hi - $lo ) / 2 );
mergeSort( $aref, $auxref, $lo, $mid );
mergeSort( $aref, $auxref, $mid + 1, $hi );
merge( $aref, $auxref, $lo, $mid, $hi );
}
sub merge {
my ($aref) = $_[0];
my ($auxref) = $_[1];
my $lo = $_[2];
my $mid = $_[3];
my $hi = $_[4];
for ( $i = $lo ; $i <= $hi ; $i++ ) {
$auxref->[$i] = $aref->[$i];
}
$i = $lo;
$j = $mid + 1;
for ( $k = $lo ; $k <= $hi ; $k++ ) {
if ( $i > $mid ) {
$aref->[$k] = $auxref->[$j];
$j++;
}
elsif ( $j > $hi ) {
$aref->[$k] = $auxref->[$i];
$i++;
}
elsif ( $auxref->[$i] <= $auxref->[$j] ) {
$aref->[$k] = $auxref->[$i];
$i++;
}
else {
$aref->[$k] = $auxref->[$j];
$j++;
}
}
}
In sub merge, you have two array refs: $auxref and $aref.
And you're accessing the array elements as though they were ordinary arrays (i.e. $aref[0]) but as they are array references, you need to dereference with an arrow first: $aref->[0].
Adding use strict; and use warnings; to the top of your script should have weeded out these errors though?
Arrays
my #arr = (1, 2, 3, 4);
$arr[0] = 5;
push #arr, 6;
# #arr = (5, 2, 3, 4, 6)
Array References
my $arr = [1,2,3];
$arr->[0] = 5;
push #$arr, 6;
# $arr = [5, 2, 3, 4, 6];
2D arrays of array references
my #arr = ([1, 2], [3, 4]);
print $arr[0][1]; # identical to $arr[0]->[1];
push #{$arr[1]}, 5;
# #arr = ([1, 2], [3, 4, 5]);
2D arrayref of array references
my $arr = [[1, 2], [3, 4]];
print $arr->[0][1]; # identical to $arr->[0]->[1];
push #{$arr->[1]}, 5;
# $arr = [[1, 2], [3, 4, 5]];
2D Array of arrays
...can't exist because an array can only hold scalars
my #arr = ((1, 2), (3, 4));
# #arr = (1, 2, 3, 4);
The following is a version of merge sort that doesn't rely on references at all. It almost certainly isn't as memory efficient as some of the original merge sort algorithms were intended, but it gets the job done.
use strict;
use warnings;
my #array = ( 5, 7, 6, 3, 4, 1, 8, 9, 4 );
my #sorted = mergeSort(#array);
print "#sorted\n";
sub mergeSort {
my #array = #_;
if (#array > 1) {
my $mid = int(#array / 2);
my #lowArray = mergeSort(#array[0..$mid-1]);
my #highArray = mergeSort(#array[$mid..$#array]);
# Merge the two halves
my #newArray = ();
while (#lowArray && #highArray) {
if ($lowArray[0] < $highArray[0]) {
push #newArray, shift #lowArray;
} else {
push #newArray, shift #highArray;
}
}
# Either the low or high array will be empty at this point,
# so no need to compare for the remainder.
return (#newArray, #lowArray, #highArray);
} else {
return #array;
}
}

Perl: Loop through months in a specified range

I need to make a loop (foreach) for all the months specified in a range like:
01-2013 to 09-2015 (month-year) format.
The tricky part is that in every loop i need the month - year data as well to run an sql query, so i cannot use a simple +1 counter.
I looked as Date::Calc and Date::Simple but it did not offer me a solution.
Does anybody have a code snippet i could use or come up with an idea on how to tackle this challenge?
The DateTime module has a nice function add which allows you to add whatever amount of time you want to an object:
use strict;
use warnings;
use DateTime;
use feature 'say';
my $start = DateTime->new(year => 2013, month => 1);
my $end = DateTime->new(year => 2015, month => 9);
while ($start <= $end) {
$start->add(months => 1);
say $start->strftime("%m-%Y");
}
If you only need to loop through the dates, why not just use this:
for my $year (2013..2015) {
for my $month (1..12) {
my $date = sprintf "%02d-%d", $month, $year;
# do your $date processing here
...
last if ($date eq "09-2015");
}
}
Date::Calc is awesome. Check it again
use Date::Calc();
my ($month, $year, $end_month, $end_year) = (1, 2013, 9, 2015);
while (($year < $end_year) || ($year == $end_year && $month <= $end_month)) {
print "year: $year, month: $month\n";
($year, $month) = Date::Calc::Add_Delta_YMD($year,$month,1,0,1,0);
}
my $start_date = '01-2013';
my $end_date = '09-2015';
my ($sm, $sy) = split '-', $start_date;
my ($em, $ey) = split '-', $end_date;
for my $y ($sy..$ey) {
for my $m (1..12) {
next if ($y==$sy && $m<$sm);
last if ($y==$ey && $m>$em);
# use $m and $y for further processing sql query
# print "Month: $m\t Year: $y\n";
# ...
}
}

get timestamp in perl

my yesterday date is 201103116
I want to take starttime timestamp and endtime time stamp for yesterday in perl.
Using DateTime:
use DateTime;
my $start = DateTime->now(time_zone => 'local')->subtract(days => 1)
->set(hour => 0, minute => 0, second => 0);
my $end = $start->clone()->add(days => 1, seconds => -1);
print $start, " - ",$end,"\n"; # 2011-03-15T00:00:00 - 2011-03-15T23:59:59
print $start->epoch," - ",$end->epoch,"\n"; # 1300147200 - 1300233599
The Perl-core way would be:
my ( $y, $m, $d ) = unpack 'A4 A2 A2', $date;
my $start_ts = POSIX::mktime( 0, 0, 0, $d, $m - 1, $y - 1900 );
my $end_ts = POSIX::mktime( 0, 0, 0, $d + 1, $m - 1, $y - 1900 );
see POSIX
And with mktime it's perfectly okay to just add negatives to values. So if you need to have 23:59:59 as your end date as suggested in the comments, you can just fix it up with this:
my $end_ts = POSIX::mktime( -1, 0, 0, $d + 1, $m - 1, $y - 1900 );
(Although, I would just like to note that the excluded endpoint is not an unknown case in programming.)
I did not understand your question. However, have a look at DateTime.