Project Euler #19 - Counting Sundays Problem - python

I have been working on this problem for a while. It's a problem that says you have to find how many Sundays land on the First of the Month in the years 1901 to 2000. I've made it mostly work, but it still outputs 173, instead of 171 (the correct answer).
Does anyone why this is?
months = {1:31, 2:28, 3:31, 4:30, 5:31, 6:30, 7:31, 8:31, 9:30, 10:31, 11:30, 12:31}
start, end = 1900, 2001
years = range(start, end)
leap_years = range(1904, 2001, 4)
Sundays = 0
days = 1
for year in years:
if year in leap_years:
months[2] = 29
else:
months[2] = 28
for month in range(1, 13):
for day in range(1, months[month]+1):
if days == 7:
if day == 1:
print(year, month, day)
Sundays += 1
days = 1
else:
days += 1
print(Sundays)

Your algorithm is completely correct - just the inputs are wrong. start and end should be:
start, end = 1901, 2000
The range(a, b) function is not inclusive of b, but it is inclusive of a. Thus start should be 1901 instead of 1900.
The end is a bit more tricky, you have to think about the phrasing of the question:
... in the years 1901 to 2000
Think about it - this means every day up to, but not including, Jan 1 2000. Thus the last year that you actually want to iterate through and check is 1999. Thus, setting end to 2000 will cause the range() function to return all the years up to, but not including, 2000. Which is exactly what the question asks for.

Your days assumes day equal 1 is days equal 1, (Monday) starting from the first month (and then progresses through the months, years). I don't think this will accurately calculate the count of Sundays that fall on the first of each month over the years.
If you enter the following code at the beginning of your program (and delete the line before your loop that says days = 1), it will correctly give the number of Sundays == 171. This will start out the day of week for the first day of the first month of the first year accurately.
from calendar import weekday
start, end = 1901, 2001
days = 1 + weekday(start, 1, 1)
Note that the weekday function uses zero based counting, (Monday through Sunday is 0 to 6) so that's why 1 is added to the result in the initialization of days. (Because your Monday to Sunday is 1 to 7).
Just to illustrate how the solution could find Sundays without iterating through every day of all the years:
from calendar import weekday
sundays = 0
for yr in range(1901, 2001):
for month in range(1, 13):
if weekday(yr, month, 1) == 6:
sundays += 1
print(sundays) # prints 171

Related

Formula that gives workdays given starting day of the week as number and number of days?

I'm trying to come up with a formula (No ifs/else, only sum, subtraction, division, multiplication and mod) to com up with working days.
Workdays are days of the week 1-5
Given :
Day of the week as an integer between 1-7 (you can use 0-6 in your answer)
Number of days from day of the week between 1-n
Example input :
week_day = 1 //monday
days = 10
Should output :
workdays = 8
I believe the formula should be something around the mod operator, but not even sure where to start.
What I have so far only works if week_day < 5 :
week_day = 1
days = 16
saturday_day = 6
sunday_day = 7
saturdays = days/saturday_day
sundays = days/sunday_day
weekends = saturdays+sundays
workdays = days - weekends
I believe to make it work, saturday_day and sunday_day need to shift forward(or backward?) based on the week_day, but they both have to be between 1-7, that's where mod would come in I guess.
Here's my somewhat straightforward and rigorous solution. There could very well be an optimized way to do this:
# Determine the minimum of two integers without any branching (no `if`)
def min(x, y):
return y ^ ((x ^ y) & -(x < y))
# assuming that start is 1-7 with 1 being Monday
def compute_work_days(start, days):
# work in a 0-based scale (0 == Monday)
start -= 1
# remember our original start
orig_start = start
# adjust count so that we assume we start on the earlier Monday and
# end on the same day
days += start
# pull out full weeks, which provide 5 work days and otherwise leave the same problem
full_weeks = days // 7 # compute number of full weeks
days = days % 7 # take these even weeks out of the count
work_days = full_weeks * 5 # we get 5 work days for each full week
# what we have left is a value between 0 and 6, where the first 5 days
# are work days, so add at most 5 days
work_days += min(days, 5)
# now take off the extra days we added to the count at the beginning, the
# first 5 of which will be work days
work_days -= min(orig_start, 5)
return work_days
Let n denote the number of worked days and d a weekday in {1, 2, ..., 7}, then the number of workdays w maybe computed as following:
Hint: floor(x/d) is the number of multiples of d that are less than or equals to x.

Calculate first day of the week, given year and week number of the year

In python, How can we calculate the first day of the week when given a year and the particular week number of the year?
Note that date should be in format YYYY-MM-DD. Year and the week number is given in int format..
I am making the following assumptions about what your question means. If they are off, it should not be hard to adjust the code.
1) The first day of the week is Sunday. (so the answer is always a Sunday)
2) The week in which January 1 falls is week 1 (not 0).
Then the work breaks down into two parts.
a) Figure out the first day of the first week.
b) Add the right number of days onto that.
In Python, it looks as follows:
import datetime
def firstDayOfWeek1(y):
#takes a year and says the date of the first Sunday in the week in which January 1 falls
janDay = datetime.date(y,1,1)
while (janDay.weekday()!=6):#back up until Sunday, change if you hold Sunday is not the first day of the week
janDay=janDay-datetime.timedelta(days=1)
return janDay
def firstDayOfWeekN(y, n):#takes a year and a week number and gives the date of the first Sunday that week
return firstDayOfWeek1(y)+datetime.timedelta(weeks=(n-1))
def formattedFirstDayOfWeekN(y, n):#takes a year and a week number and gives the date of the first Sunday that week
return firstDayOfWeekN(y, n).isoformat()
#example
print formattedFirstDayOfWeekN(2018,2)#2018-01-07, first day of second week of January this year
I am using an algorithm which starts with a close-by date and then simply loops down till it finds the desired result. I am sacrificing some CPU cycles for ease of readability since the cost is not significant. I have done some limited testing but I hope the general idea is clear. Let me know your thoughts.
#This is the input in integer format
input_year = 2018
input_week = 29
#The general idea is that we will go down day by day from a reference date
#till we get the desired result.
#The loop is not computationally intensive since it will
#loop at max around 365 times.
#The program uses Python's ISO standard functions which considers Monday as
#the start of week.
ref_date = date(input_year+1,1,7) #approximation for starting point
#Reasoning behind arguments: Move to next year, January. Using 7 as day
#ensures that the calendar year has moved to the next year
#because as per ISO standard the first week starts in the week with Thursday
isoyear,isoweek,isoday = ref_date.isocalendar()
output_date = ref_date #initialize for loop
while True:
outisoyear,outisoweek,outisoday = output_date.isocalendar()
if outisoyear == input_year and outisoweek == input_week and outisoday == 1:
break
output_date = output_date + timedelta(days=-1)
print(output_date)

Finding Month from Day, Week and Year Python

I can not figure out how to take the year, day and week to return the month. Right now I am just trying to develop a Python Script that will do this. The goal after finishing this script is to use it for a Spark SQL Query to find the month since in my data I am given a day, year and week in each row.
As of now my python code looks like so. This code only works for the statement I have into the print(getmonth(2, 30 ,2018) returning 7. I have tried other dates and the output is only "None". I have tried variables also, but no success there.
import datetime
def month(day, week, year):
for month in range(1,13):
try:
date = datetime.datetime(year, month, day)
except ValueError:
iso_year, iso_weeknum, iso_weekday = date.isocalendar()
if iso_weeknum == week:
return date.month
print(getmonth(2, 30, 2018))
#iso_(year,weeknum,weekday) are the classes for ISO. Year is 1-9999, weeknum is 0-52 or 53, and weekday is 0-6
#isocaldenar is a tuple (year, week#, weekday)
I don't really understand your questions, but i think datetime will work... sorce: Get date from ISO week number in Python:
>>> from datetime import datetime
>>> day = 28
>>> week = 30
>>> year = 2018
>>> t = datetime.strptime('{}_{}_{}{}'.format(day,week,year,-0), '%d_%W_%Y%w')
>>> t.strftime('%W')
'30'
>>> t.strftime('%m')
'07'
>>>
A simpler solution can be created using the pendulum library. As in your code, loop through month numbers, create dates, compare the weeks for these dates against the desired date. If found halt the loop; if the date is not seen then exit the loop with, say, a -1.
>>> import pendulum
>>> for month in range(1,13):
... date = pendulum.create(2018, month, 28)
... if date.week_of_year == 30:
... break
... else:
... month = -1
...
>>> month
7
>>> date
<Pendulum [2018-07-28T00:00:00+00:00]>
Here is a brute force method that loops through the days of the year (It expects the day as Monday being 0 and Sunday being 6, it also returns the Month 0 indexed, January being 0 and December being 11):
import datetime
def month(day, week, year):
#Generate list of No of days of the month
months = [31,28,31,30,31,30,31,31,30,31,30,31]
if((year % 4 == 0 and year % 100 != 0) or year % 400 == 0): months[1] += 1
#ISO wk1 of the yr is the first wk with a thursday, otherwise it's wk53 of the previous yr
currentWeek = 1 if day < 4 else 0
#The day that the chosen year started on
currentDay = datetime.datetime(year, 1, 1).weekday()
#Loop over every day of the year
for i in range(sum(months)):
#If the week is correct and day is correct you're done
if day == currentDay and week == currentWeek:
return months.index(next(filter(lambda x: x!=0, months)))
#Otherwise, go to next day of wk/next wk of yr
currentDay = (currentDay + 1) % 7
if currentDay == 0:
currentWeek += 1
#And decrement counter for current month
months[months.index(next(filter(lambda x: x!=0, months)))]-=1
print(month(2, 30, 2018)) # 6 i.e. July
months.index(next(filter(lambda x: x!=0, months))) is used to get the first month of that we haven't used all of the days of, i.e. the month you're currently in.

Algorithm for getting current week number after changing the starting day of the week in python?

I want to design an algorithm which will calculate the week number according to the start week day set. for eg : - If I set the start day as WEDNESDAY and currently its 40 week and its TUESDAY, it should print 40 as the week number. If it is WEDNESDAY or THURSDAY, I should get 41.
Think of it like a cycle. From Wednesday till tuesday, it should be assigned a week no + 1, then when next wednesday comes, week should be incremented again.
I tried using calendar.setfirstweekday(calendar.WEDNESDAY) and then altering my system machine time, all I get is 40 as the week number everytime.
How do I design such as algorithm in python?
I have a similar problem for month, but I have designed a solution for it. Here is it.
current_date = datetime.datetime.now()
if current_date.day < gv.month_start_date:
month = current_date.month -1
if month == 0:
month = 12
else:
month = current_date.month
How can I design it for week?
I finally designed a solution for this.
if current_day >= set_week_day:
week = current_week
else:
week = current_week - 1
Works for all cases.
datetime in python has a function called isocalender to get the ISO week number (starts on Monday)
import datetime
datetime.date(2013, 9, 30).isocalendar()[1]
You can use this with a little bit of logic (this script should have the week begin on Wednesdays)
import datetime
day = 30
month = 9
year = 2013
weekcount = datetime.date(year, month, day).isocalendar()[1]
if datetime.date(year, month, day).isocalendar()[2] <= 3: # start on wednesday
weekcount -= 1
print weekcount

Given a date range how to calculate the number of weekends partially or wholly within that range?

Given a date range how to calculate the number of weekends partially or wholly within that range?
(A few definitions as requested:
take 'weekend' to mean Saturday and Sunday.
The date range is inclusive i.e. the end date is part of the range
'wholly or partially' means that any part of the weekend falling within the date range means the whole weekend is counted.)
To simplify I imagine you only actually need to know the duration and what day of the week the initial day is...
I darn well now it's going to involve doing integer division by 7 and some logic to add 1 depending on the remainder but I can't quite work out what...
extra points for answers in Python ;-)
Edit
Here's my final code.
Weekends are Friday and Saturday (as we are counting nights stayed) and days are 0-indexed starting from Monday. I used onebyone's algorithm and Tom's code layout. Thanks a lot folks.
def calc_weekends(start_day, duration):
days_until_weekend = [5, 4, 3, 2, 1, 1, 6]
adjusted_duration = duration - days_until_weekend[start_day]
if adjusted_duration < 0:
weekends = 0
else:
weekends = (adjusted_duration/7)+1
if start_day == 5 and duration % 7 == 0: #Saturday to Saturday is an exception
weekends += 1
return weekends
if __name__ == "__main__":
days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
for start_day in range(0,7):
for duration in range(1,16):
print "%s to %s (%s days): %s weekends" % (days[start_day], days[(start_day+duration) % 7], duration, calc_weekends(start_day, duration))
print
General approach for this kind of thing:
For each day of the week, figure out how many days are required before a period starting on that day "contains a weekend". For instance, if "contains a weekend" means "contains both the Saturday and the Sunday", then we have the following table:
Sunday: 8
Monday: 7
Tuesday: 6
Wednesday: 5
Thursday: 4
Friday: 3
Saturday: 2
For "partially or wholly", we have:
Sunday: 1
Monday: 6
Tuesday: 5
Wednesday: 4
Thursday: 3
Friday: 2
Saturday: 1
Obviously this doesn't have to be coded as a table, now that it's obvious what it looks like.
Then, given the day-of-week of the start of your period, subtract[*] the magic value from the length of the period in days (probably start-end+1, to include both fenceposts). If the result is less than 0, it contains 0 weekends. If it is equal to or greater than 0, then it contains (at least) 1 weekend.
Then you have to deal with the remaining days. In the first case this is easy, one extra weekend per full 7 days. This is also true in the second case for every starting day except Sunday, which only requires 6 more days to include another weekend. So in the second case for periods starting on Sunday you could count 1 weekend at the start of the period, then subtract 1 from the length and recalculate from Monday.
More generally, what's happening here for "whole or part" weekends is that we're checking to see whether we start midway through the interesting bit (the "weekend"). If so, we can either:
1) Count one, move the start date to the end of the interesting bit, and recalculate.
2) Move the start date back to the beginning of the interesting bit, and recalculate.
In the case of weekends, there's only one special case which starts midway, so (1) looks good. But if you were getting the date as a date+time in seconds rather than day, or if you were interested in 5-day working weeks rather than 2-day weekends, then (2) might be simpler to understand.
[*] Unless you're using unsigned types, of course.
My general approach for this sort of thing: don't start messing around trying to reimplement your own date logic - it's hard, ie. you'll screw it up for the edge cases and look bad. Hint: if you have mod 7 arithmetic anywhere in your program, or are treating dates as integers anywhere in your program: you fail. If I saw the "accepted solution" anywhere in (or even near) my codebase, someone would need to start over. It beggars the imagination that anyone who considers themselves a programmer would vote that answer up.
Instead, use the built in date/time logic that comes with Python:
First, get a list of all of the days that you're interested in:
from datetime import date, timedelta
FRI = 5; SAT = 6
# a couple of random test dates
now = date.today()
start_date = now - timedelta(57)
end_date = now - timedelta(13)
print start_date, '...', end_date # debug
days = [date.fromordinal(d) for d in
range( start_date.toordinal(),
end_date.toordinal()+1 )]
Next, filter down to just the days which are weekends. In your case you're interested in Friday and Saturday nights, which are 5 and 6. (Notice how I'm not trying to roll this part into the previous list comprehension, since that'd be hard to verify as correct).
weekend_days = [d for d in days if d.weekday() in (FRI,SAT)]
for day in weekend_days: # debug
print day, day.weekday() # debug
Finally, you want to figure out how many weekends are in your list. This is the tricky part, but there are really only four cases to consider, one for each end for either Friday or Saturday. Concrete examples help make it clearer, plus this is really the sort of thing you want documented in your code:
num_weekends = len(weekend_days) // 2
# if we start on Friday and end on Saturday we're ok,
# otherwise add one weekend
#
# F,S|F,S|F,S ==3 and 3we, +0
# F,S|F,S|F ==2 but 3we, +1
# S|F,S|F,S ==2 but 3we, +1
# S|F,S|F ==2 but 3we, +1
ends = (weekend_days[0].weekday(), weekend_days[-1].weekday())
if ends != (FRI, SAT):
num_weekends += 1
print num_weekends # your answer
Shorter, clearer and easier to understand means that you can have more confidence in your code, and can get on with more interesting problems.
To count whole weekends, just adjust the number of days so that you start on a Monday, then divide by seven. (Note that if the start day is a weekday, add days to move to the previous Monday, and if it is on a weekend, subtract days to move to the next Monday since you already missed this weekend.)
days = {"Saturday":-2, "Sunday":-1, "Monday":0, "Tuesday":1, "Wednesday":2, "Thursday":3, "Friday":4}
def n_full_weekends(n_days, start_day):
n_days += days[start_day]
if n_days <= 0:
n_weekends = 0
else:
n_weekends = n_days//7
return n_weekends
if __name__ == "__main__":
tests = [("Tuesday", 10, 1), ("Monday", 7, 1), ("Wednesday", 21, 3), ("Saturday", 1, 0), ("Friday", 1, 0),
("Friday", 3, 1), ("Wednesday", 3, 0), ("Sunday", 8, 1), ("Sunday", 21, 2)]
for start_day, n_days, expected in tests:
print start_day, n_days, expected, n_full_weekends(n_days, start_day)
If you want to know partial weekends (or weeks), just look at the fractional part of the division by seven.
You would need external logic beside raw math. You need to have a calendar library (or if you have a decent amount of time implement it yourself) to define what a weekend, what day of the week you start on, end on, etc.
Take a look at Python's calendar class.
Without a logical definition of days in your code, a pure mathematical methods would fail on corner case, like a interval of 1 day or, I believe, anything lower then a full week (or lower then 6 days if you allowed partials).

Categories

Resources