usaco: friday the thirteen error - c++

I'm currently working on a problem (Friday the thirteenth) on the USACO site and I've coded a program which, given a number N,computes how often the 13th lands on each day of the week from Jan 1st,1900 to Dec 31st,1900+N-1.The algorithm is inspired from a mental calculation trick : http://www.jimloy.com/math/day-week.htm
Things to keep in mind:
January 1, 1900 was on a Monday.
Thirty days has September, April, June, and November, all the rest have 31 except for February which has 28 except in leap years when it has 29.
Every year evenly divisible by 4 is a leap year (1992 = 4*498 so 1992 will be a leap year, but the year 1990 is not a leap year)
The rule above does not hold for century years. Century years divisible by 400 are leap years, all other are not. Thus, the century years 1700, 1800, 1900 and 2100 are not leap years, but 2000 is a leap year.*
The program works well except when N=256 (1900 to 2156)
My program outputs :
440 438 439 439 437 439 440
while on USACO, there is:
440 439 438 438 439 439 439
The program takes a file (friday.in) containing N as input and outputs seven space separated integers representing the number of occurrences of each day (starting by Saturday) on one line in another file(friday.out).
I've put several cout for debugging purposes.
Here's the code:
/*
ID: freebie1
PROG:friday
LANG: C++
*/
#include <fstream>
#include <vector>
#include <iostream>
using namespace std;
class Date
{
public:
Date(int month=0,int year=0):m_month(month),m_year(year)
{
}
int monthDiff()
{
if(m_month<=3)
{
if(m_month==1)
{
return 0;
}
else if(m_month==2)
{
return 31;
}
}
if(m_month>=3)
{
if((m_year%4==0 && m_year%100!=0)||(m_year%100==0 && m_year%400==0))
{
if(m_month<9)
{
if(m_month%2==0)
return 60+(31*int(m_month/2.6)+30*(int(m_month/2.6)-1));
else
return 60+(61*int(m_month/3.5));
}
else if(m_month>=9)
{
if(m_month%2==0)
return 91+61*(m_month/3);
else
return 91+(31*int(m_month/2.75)+30*int(m_month/3.6));
}
}
else
{
if(m_month<9)
{
if(m_month%2==0)
return 59+(31*int(m_month/2.6)+30*(int(m_month/2.6)-1));
else
return 59+(61*int(m_month/3.5));
}
else if(m_month>=9)
{
if(m_month%2==0)
return 90+61*(m_month/3);
else
return 90+(31*int(m_month/2.75)+30*int(m_month/3.6));
}
}
}
}
void show()
{
cout<<m_month<<"/"<<m_year<<": ";
}
int tellDay()
{
int daysInYears=int((m_year-1900))*365;
int daysInMonths=this->monthDiff();
int leapDays;
if(m_year%4==0 && m_year!=1900)
leapDays=int((m_year-1900)/4)-1;
else if(m_year>2100)
leapDays=int((m_year-1900)/4)-1;
else if(m_year>2200)
leapDays=int((m_year-1900))/4-2;
else
leapDays=int((m_year-1900))/4;
int days=13+leapDays;
int res=daysInYears+daysInMonths+days;
cout<<"MonthDiff: "<<this->monthDiff()<<" In years: "<<daysInYears<<" days: "<<days<<" ";
return res%7;
}
private:
int m_month;
int m_year;
};
int main()
{
ifstream fin("friday.in");
ofstream fout("friday.out");
if(fin)
{
int n(0),day(0),sMonth(1),sYear(1900);
fin>>n;
vector<int> weekDays(7,0);
for(int i(0);i<n;i++)
{
for(int j(0);j<12;j++)
{
Date date(sMonth+j,sYear+i);
day=date.tellDay();
date.show();
cout<<day<<endl;
switch(day)
{
case 0:
weekDays[1]+=1;
break;
case 1:
weekDays[2]+=1;
break;
case 2:
weekDays[3]+=1;
break;
case 3:
weekDays[4]+=1;
break;
case 4:
weekDays[5]+=1;
break;
case 5:
weekDays[6]+=1;
break;
case 6:
weekDays[0]+=1;
break;
}
}
}
for(int i(0);i<6;i++)
{
fout<<weekDays[i]<<" ";
}
fout<<weekDays[6]<<endl;
}
return 0;
}

You get wrong results from 205 years on, that is from the year 2104.
if(m_year%4==0 && m_year!=1900)
leapDays=int((m_year-1900)/4)-1;
For years divisible by 4, you don't subtract the non-leap years 2100, 2200, 2300, 2500, ... from the count.
Fix:
if(m_year%4==0 && m_year!=1900)
leapDays=int((m_year-1900)/4)-1 + (m_year-1604)/400 - (m_year-1904)/100;
subtract them. That leaves the wrong leap year count for years not divisible by 4 after 2300, you can correct that by adding a similar correction (and that doesn't need multiple branches then):
else
leapDays=int((m_year-1900)/4) + (m_year-1600)/400 - (m_year-1900)/100;
The formula shall determine the number of leap years that have passed between 1900 and m_year. If m_year is not a multiple of 4, the simple "multiples of 4 are leap years" gives that count as (m_year - 1900)/4. But that doesn't take into account that multiples of 100 are not leap years in general, so we subtract the number of such years that have passed in between, - (m_year - 1900)/100. However, now the multiples of 400 that are leap years have been subtracted too, so add their count back, + (m_year - 1600)/400 (base year here is 1600, the largest multiple of 400 not after 1900).
For multiples of 4, we have to correct for the fact that the current year is not a leap year before the current year, so I let the correction take place only after and shift the base years.
Better fix, making the leap year count uniform (no branches):
int leapDays = (m_year - 1901)/4 + (m_year-1601)/400 - (m_year-1901)/100;

Related

c++ adding years and days using date.h

Working on calendar duration arithmetic using date.h and std::chrono, but getting an unexpected result.
Sample code is:
#include "date.h"
#include <string>
#include <chrono>
#include <iostream>
int main() {
date::sys_seconds calendarDate = {};
calendarDate = std::chrono::years(30) + date::sys_seconds(std::chrono::days(10));
std::string stringDate = date::format("%Y-%m-%d %H:%M:%S", calendarDate);
std::cout << "{} + 30 years + 10 days = " << stringDate << "\n";
return 0;
}
Actual Output:
{} + 30 years + 10 days = 2000-01-11 06:36:00
Expected Output:
{} + 30 years + 10 days = 2000-01-11 00:00:00
Using Ubuntu 22.04; g++ 11.3.0
Compiled with: gcc -g -std=c++20 main.cpp -lstdc++
Using date.h fromm here: https://raw.githubusercontent.com/HowardHinnant/date/master/include/date/date.h
Any insight into what's adding in the extra 6hours and 36minutes?
It is because of leap time. For the Gregorian calendar, the average length of the calendar year (the mean year) across the complete leap cycle of 400 years is 365.2425 days (97 out of 400 years are leap years).
This is taken into account in the definition of std::chrono::years which is defined as a duration type with Period std::ratio<31556952> (60 * 60 * 24 * 365.2425 = 31556952).
However, if you compare this to the non-leap-aware duration of a year (60 * 60 * 24 * 365 = 31536000), then you get a difference of 20952 seconds or 5 hours 49 minutes and 12 seconds. If you multiply that difference by 30 it grows to:
7 days 6 hours 36 minutes and 0 seconds - and at least the latter part should look familiar to you.
But what about the 7 days? Well, it turns out that between 1970 and 2000: 1972, 1976, 1980, 1984, 1988, 1992, and 1996 were leap years - if you count that is 7, and that is where the days went.
You can check this by yourself by changing your code and looking at the output:
calendarDate += std::chrono::seconds(60 * 60 * 24 * 365 * 30); - "1999-12-25 00:00:00"
calendarDate += std::chrono::seconds(60 * 60 * 24 * 365 * 30) + day(7); - "2000-01-01 00:00:00"
By the way, the problem is that std::chrono::years gives you the length of an average year, while the date.h library knows (and cares about) the specific years: It knows that 1970 was not a leap year (so 60 * 60 * 24 * 365 = 31536000 seconds), but 1972 was a leap year (so 60 * 60 * 24 * 366 = 31622400 seconds).
If you had added 31 years instead, then you would have added 5:49:12 more skew from the average year, but then removed 1 more day since 2000 was also a leap year - so the time it would print would still be in 2000, but ~12 hours before new-years 2001.
In addition to Frodyne's good answer (which I've upvoted), I wanted to add that you can do either chronological arithmetic or calendrical arithmetic with date.h.
You've done chronological arithmetic, where years is considered to be a uniform unit, and is equal to the average civil year.
Calendrical arithmetic follows the irregularity of calendars, and is what you expected. This is how you would accomplish that:
#include "date/date.h"
#include <string>
#include <chrono>
#include <iostream>
int main() {
auto chronologicalDateTime = date::sys_seconds(date::days(10));
auto chronologicalDate = date::floor<date::days>( chronologicalDateTime);
date::year_month_day calendarDate = chronologicalDate;
auto timeOfDay = chronologicalDateTime - chronologicalDate;
calendarDate += date::years{30};
chronologicalDateTime = date::sys_days{calendarDate} + timeOfDay;
std::string stringDate = date::format("%Y-%m-%d %H:%M:%S", chronologicalDateTime);
std::cout << "{} + 30 years + 10 days = " << stringDate << "\n";
return 0;
}
You first convert the chronological date/time to a calendrical date, and add the years to the calendrical date. Then convert that back into a chronological date/time.
{} + 30 years + 10 days = 2000-01-11 00:00:00
Here's another SO answer that goes over the same principle, except using months: https://stackoverflow.com/a/43018120/576911
Linux stores dates as UTC. My guess is that your machine is located at Central Time zone (CT) which is +6 hours from UTC.

Understanding code for first day of month function

I'm doing a practice exercise. It asks me to create a calendar of the month, year which is the current user time. I have looked up some code on the Internet, it works well, but I can't understand it clearly. Especially the line year -= month < 3. Can someone explain it, please?
//return the daycode of the first day of month.
int firstDayOfMonth(int month, int year) {
int dow = 0;
int day = 1;
int t[] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 };
year -= month < 3; // I cannot understand this.
cout<<"This is year "<<year<<endl;
dow = ( year + year/4 - year/100 + year/400 + t[month-1] + day) % 7;
return dow;
}
int main()
{
int a;
cout<<firstDayOfMonth(2,2018)<<endl;
return 0;
}
In C++, boolean values can be implicitly converted to integers, with false becoming 0 and true becoming 1. (See bool to int conversion.)
So year -= month < 3; is equivalent to:
if (month < 3) {
year -= 1; // true -> 1
} else {
year -= 0; // false -> 0
}
which can be simplified to:
if (month < 3) {
--year;
}
The motivation is that January and February (months 1 and 2) come before any leap day, while the other months come after any leap day, so it's convenient to treat January and February as being at the end of the previous year, and let the leap day be added to the calculation for the entire March-to-February year.
This code is obviously not optimized for readability.
What that means is:
if the condition (month < 3) is true, then decrement by 1. if the condition (month < 3) is false, then decrement by 0 (year stays the same)
The value of 1 & 0 represent false & true of the month & number comparison.

Getting the day of the week a year starts at

I was making a function in C++ to get the day of the week given the day, month and year (from 1900 on). The way I have to do it (I'm following orders, it's an exercise) is with the modulus of 7 of the total of days passed.
For example, 21 November 2018 will be the 325th day of that year (Taking into account leap years). The day of the week will be 325 % 7, which will give a number between 0 and 6, 0 being Sunday, 1 being Monday and so on, until 6 which would be Saturday.
But this will only work in years that start on Monday. 2018 works, but 2019 will be off by 1 day, as it starts on Tuesday.
My idea of fixing this is by knowing on what day that year starts and adding it to the 0-6 number given (fixing it if it's higher than 6), but I'd have to use the function for the year before, which would do so until it reaches 1900, which would be set to Monday. It sounds terrible, and I can't figure out another way of doing it.
Thanks in advance
If you don't want to use any libraries and do it purely by calculation, here is a solution.
http://mathforum.org/dr.math/faq/faq.calendar.html (Web Archive page)
or a easy explanation video.
What you can do is convert this logic into your program and find out the day of the week.
int dayofweek(int d, int m, int y)
{
static int t[] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 };
y -= m < 3;
return ( y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
}
Code Source.
http://www.cplusplus.com/reference/ctime/tm/
http://www.cplusplus.com/reference/ctime/mktime/
int weakDayOfYearBegin(int year)
{
std::tm t {};
t.tm_year = year - 1900;
t.tm_mday = 1;
std::mktime(&t);
return t.tm_wday;
}
https://wandbox.org/permlink/1ZnByeurgMrEF3fA

Explain this leap year Function C++

I'm having an issue with this code,I do not understand how the function works. I need to validate the input from the user, to see if their date that they placed is valid. And if it isn't I set the error code. So in my read function I cin the date then validate the input and call mdays() however, for some reason I don't know how to check in my if statement in the read function if the date is validate or not.
int Date::mdays() const
{
int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, -1};
int mon = _mon >= 1 && _mon <= 12 ? _mon : 13;
mon--;
return days[mon] + int((mon == 1)*((_year % 4 == 0) &&
(_year % 100 != 0)) || (_year % 400 == 0));
}
The code is very clever, written by someone who wanted to demonstrate that they are smart. I hate clever code. (It's also quite slow, I hate code that tries to be clever and fails).
Remember the rules for leapyears:
Every fourth year is a leap year. Except that every 100th year is not a leap year. Except that every 400th year is a leap year.
Most months you can look up from a table, except that February has either 28 or 29 days. So to understand the code, what happens if the month is not February? And what happens if the month is February? mon will be equal to 1. What is the value of (mon == 1) in February? How would you express the rules for leap years?
And the function that you showed calculates the number of days in a month, it doesn't do any validation. Obviously you need to know that April has 30 days to know that April 31st is invalid.
You can change the signature of mdays(), return a boolean to indicate if the date is validate or not, and put an output argument to store the days if the date is validate
bool Date::mdays(int& month_days) const {
int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (_mon < 1 || _mon > 12) return false;
mon--;
month_days = days[mon] + int((mon == 1)*((_year % 4 == 0) && (_year % 100 != 0)) || (_year % 400 == 0));
return true;
}
If you can modify the Date class, you should be able to create new method utilizing return value of mdays() like this:
bool Date::validate_day_and_month() const {
int maxday = mdays();
if ( maxday < 0 ) { return false; } // mdays() = -1 if _month is bad
if ( _day <= 0 || _day > maxday ) { return false; }
return true;
}
Here, _day is the day part of the user date input.

Get the number of trading days in between two days

i'm trying to get the number of trading dates between two dates which will only exclude the weekends and won't consider any holidays. I'm using Boost and c++11 standards.
using namespace boost::gregorian;
long dateDifference( string start_date, string end_date ) {
date _start_date(from_simple_string(start_date));
date _end_date(from_simple_string(end_date));
long difference = ( _start_date - _end_date ).days();
return difference;
}
This just returns the number of days between two dates without considering weekends. Can someone point me in the right direction. I can't seem to figure out the solution.
Thanks,
Maxx
O(1) solution with no loops:
#include <boost/date_time.hpp>
using namespace std;
using namespace boost::gregorian;
long countWeekDays( string d0str, string d1str ) {
date d0(from_simple_string(d0str));
date d1(from_simple_string(d1str));
long ndays = (d1-d0).days() + 1; // +1 for inclusive
long nwkends = 2*( (ndays+d0.day_of_week())/7 ); // 2*Saturdays
if( d0.day_of_week() == boost::date_time::Sunday ) ++nwkends;
if( d1.day_of_week() == boost::date_time::Saturday ) --nwkends;
return ndays - nwkends;
}
The basic idea is to first count all Saturdays, which is conveniently given by the formula (ndays+d0.day_of_week())/7. Doubling this gives you all Saturdays and Sundays, except in the cases when the start and end dates may fall on a weekend, which is adjusted for by 2 simple tests.
To test it:
#include <iostream>
#include <cassert>
#include <string>
// January 2014
// Su Mo Tu We Th Fr Sa
// 1 2 3 4
// 5 6 7 8 9 10 11
// 12 13 14 15 16 17 18
// 19 20 21 22 23 24 25
// 26 27 28 29 30 31
int main()
{
assert(countWeekDays("2014-01-01","2014-01-01") == 1);
assert(countWeekDays("2014-01-01","2014-01-02") == 2);
assert(countWeekDays("2014-01-01","2014-01-03") == 3);
assert(countWeekDays("2014-01-01","2014-01-04") == 3);
assert(countWeekDays("2014-01-01","2014-01-05") == 3);
assert(countWeekDays("2014-01-01","2014-01-06") == 4);
assert(countWeekDays("2014-01-01","2014-01-10") == 8);
assert(countWeekDays("2014-01-01","2014-01-11") == 8);
assert(countWeekDays("2014-01-01","2014-01-12") == 8);
assert(countWeekDays("2014-01-01","2014-01-13") == 9);
assert(countWeekDays("2014-01-02","2014-01-13") == 8);
assert(countWeekDays("2014-01-03","2014-01-13") == 7);
assert(countWeekDays("2014-01-04","2014-01-13") == 6);
assert(countWeekDays("2014-01-05","2014-01-13") == 6);
assert(countWeekDays("2014-01-06","2014-01-13") == 6);
assert(countWeekDays("2014-01-07","2014-01-13") == 5);
cout << "All tests pass." << endl;
return 0;
}
This works for any date range in the Gregorian calendar, which boost currently supports for years 1400-10000. Note that different countries have adopted the Gregorian calendar at different times. For example the British switched from the Julian to the Gregorian calendar in September of 1752, so their calendar for that month looks like
September 1752
Su Mo Tu We Th Fr Sa
1 2 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Just run a day iterator and calculate the weekdays manually:
#include <boost/date_time.hpp>
using namespace boost::gregorian;
long dateDifference( string start_date, string end_date )
{
date _start_date(from_simple_string(start_date));
date _end_date(from_simple_string(end_date));
// counter for weekdays
int cnt=0;
for(day_iterator iter = _start_date; iter!=_end_date; ++iter)
{
// increment counter if it's no saturday and no sunday
if( iter->day_of_week() != boost::date_time::Saturday
&& iter->day_of_week() != boost::date_time::Sunday)
++cnt;
}
return cnt;
}
Answer ported from this answer: https://stackoverflow.com/a/7342989/3187827
The simplest approach is to use the boost::gregorian::day_of_week() function and iterate through every day between your start date and your end date, incrementing only when it's not a Saturday nor a Sunday.
A more efficient approach would be to iterate from start_date to the next Monday (say), iterate from end_date to the previous Monday, then with a simple division find out how many week-ends you've got in between.
Finally, a "real world" solution would involve finding proper calendar data with the holidays that apply to your case, and integrate it with your algorithm.
I didn't find any O(1) solutions which satisfied me so here is what I did:
int get_weekdays_count(const boost::gregorian::date& a,const boost::gregorian::date& b)
{
int na=(a<b) ? a.day_of_week().as_number() : b.day_of_week().as_number();
int diff=(a-b).days();
if(diff!=0){
if(diff<0) diff*=-1;
int rslt=diff/7; //number of saturdays
rslt*=2; // times 2 for sundays
rslt+= (diff%7) >=(boost::gregorian::Saturday-na)%7 ? 1 : 0; // handle special case for saturdays
rslt+= (diff%7) >=(boost::gregorian::Sunday-na)%7 ? 1 : 0; //special case for sundays
return 1+diff-rslt;
}
else return (na==boost::gregorian::Saturday || na==boost::gregorian::Sunday) ? 0 : 1;
};
This work even if a > b , just put two separate time and here you go.
Speed in a for loop: 25 nanosec/call on VS2012 Release mode // proc i5 4690K