Iteration through a list - python

I'm very new to Python hence this question.
I have a list that represents dates i.e. Mondays in March and beginning of April
[2, 9, 16, 23, 30, 6]
The list, 'color_sack' is created from a scrape of our local council website.
Im using
next_rubbish_day = next(x for x in color_sack if x > todays_date.day)
todays_date.day returns just the number representing the day i.e. 30
This has worked well all month until today 30th when it now displays a error
next_rubbish_day = next(x for x in color_sack if x > todays_date.day)
StopIteration
Is it possible to step through the list a better way so as next_rubbish_day would populate the 6 after the 30 from the list above.
I can see why its not working but can't work out a better way.
When April starts the list will be updated with the new dates for Mondays in April through to the beginning of May

Consider, if your current month is march and corresponding list of dates is [2, 9, 16, 23, 30, 6] and today's date is 30, basically what we are doing is :
Checking if there is any date in color_sack that is greater than
today's date if it is then we yield that date. In our case no date in the list is greater than 30.
If the 1st condition fails we now find out the index of maximum date in the color_sack, in our case the max date is 30 and its index is 4, now we found out if there is a idx greater than the index of maximum date in the list, if it is then we return that date.
This algorithm will comply with any dates in the current month eg March. As soon as the new month starts eg. "April starts the list will be updated with the new dates for Mondays in April through to the beginning of May".
So this algorithm will always comply.
Try this:
def next_rubbish_day(color_sack, todays_date):
for idx, day in enumerate(color_sack):
if day > todays_date or idx > color_sack.index(max(color_sack)):
yield day
print(next(next_rubbish_day(color_sack, 6)))
print(next(next_rubbish_day(color_sack, 10)))
print(next(next_rubbish_day(color_sack, 21)))
print(next(next_rubbish_day(color_sack, 30)))
print(next(next_rubbish_day(color_sack, 31)))
OUTPUT:
9
16
23
6
6

next takes an optional default that is returned when the iterable is empty. If color_sack consistently has the first-of-next-month day in the last position, return it as a default:
next_rubbish_day = next(
(x for x in color_sack[:-1] if x > todays_date.day),
color_sack[-1],
)
Note that this scheme will not tell you whether you rolled over. It will only tell you the next date is 6th, not 6th of April versus 6th of March.
To avoid the magic indices, consider splitting your list explicitly and giving proper names to each part.
*this_month, fallback_day = color_sack
next_rubbish_day = next(
(day for day in this_month if day > todays_date.day),
fallback_day,
)
If you need to be month-aware, handle the StopIteration explicitly:
try:
day = next(x for x in color_sack[:-1] if x > todays_date.day)
except StopIteration:
day = color_sack[-1]
month = 'next'
else:
month = 'this'
print(f'Next date is {day} of {month} month')

Thank you for the help, Ive used MisterMiyagi snippet as that seems to work at the moment.
Here is the full code:
import datetime
import requests
import calendar
from bs4 import BeautifulSoup
from datetime import date
def ord(n): # returns st, nd, rd and th
return str(n) + (
"th" if 4 <= n % 100 <= 20 else {
1: "st", 2: "nd", 3: "rd"}.get(n % 10, "th")
)
# Scrapes rubbish collection dates
URL = "https://apps.castlepoint.gov.uk/cpapps/index.cfm?roadID=2767&fa=wastecalendar.displayDetails"
raw_html = requests.get(URL)
data = BeautifulSoup(raw_html.text, "html.parser")
pink = data.find_all('td', class_='pink', limit=3)
black = data.find_all('td', class_='normal', limit=3)
month = data.find('div', class_='calMonthCurrent')
# converts .text and strip [] to get month name
month = str((month.text).strip('[]'))
todays_date = datetime.date.today()
print()
# creats sack lists
pink_sack = []
for div in pink:
n = div.text
pink_sack.append(n)
pink_sack = list(map(int, pink_sack))
print(f"Pink list {pink_sack}")
black_sack = []
for div in black:
n = div.text
black_sack.append(n)
black_sack = list(map(int, black_sack))
print(f"Black list {black_sack}")
# creats pink/black list
color_sack = []
color_sack = [None]*(len(pink_sack)+len(black_sack))
color_sack[::2] = pink_sack
color_sack[1::2] = black_sack
print(f"Combined list {color_sack}")
print()
print()
# checks today for rubbish
if todays_date.day in color_sack:
print(f"Today {(ord(todays_date.day))}", end=" ")
if todays_date.day in pink_sack:
print("is pink")
elif todays_date.day in black_sack:
print("is black")
# Looks for the next rubbish day
next_rubbish_day = next(
(x for x in color_sack[:-1] if x > todays_date.day),
color_sack[-1],
)
# gets day number
day = calendar.weekday(
(todays_date.year), (todays_date.month), (next_rubbish_day))
# print(next_rubbish_day)
print(f"Next rubbish day is {(calendar.day_name[day])} the {(ord(next_rubbish_day))}" +
(" and is Pink" if next_rubbish_day in pink_sack else " and is Black"))
print()
Theres probable so many more efficient ways of doing this, so Im open to suggestions and always learning.

Related

How to find the most common day name in a month?

Is there a way to print the day of the week which appears most frequent in the specified month and year as a string. Like Monday and Tuesday and if there are any multiple days, the first one should be displayed.
First I tried to check with the datetime output but unable to find the calendar module to get this as day output.
import calendar
def usingcalendar(datetuple):
l = []
dt=list(datetuple)
obj = calendar.Calendar()
for day in obj.itermonthdates(dt[0], dt[1]):
l.append(day)
rev = l[:-8:-1]
rev.reverse()
print(rev)
I was trying to use calendar.itermonthdays to get the required iterator to use that to display days like 'Monday', Tuesday'. But only came up to this
[datetime.date(2020, 2, 24), datetime.date(2020, 2, 25), datetime.date(2020, 2, 26),
datetime.date(2020, 2, 27), datetime.date(2020, 2, 28), datetime.date(2020, 2, 29),
datetime.date(2020, 3, 1)]
You can use collections.Counter for this. You can call the calendar's itermonthdates for a given year and month, then create a Counter of the weekday names using the '%A' format specifier of datetime.
import calendar
from collections import Counter
cal = calendar.Calendar()
year = 2020
month = 1
count = Counter(d.strftime('%A') for d in cal.itermonthdates(year, month) if d.month==month)
Then for example
>>> count
Counter({'Wednesday': 5, 'Thursday': 5, 'Friday': 5, 'Saturday': 4, 'Sunday': 4, 'Monday': 4, 'Tuesday': 4})
>>> count.most_common(1)
[('Wednesday', 5)]
Noob coder:
I was trying to solve exactly the same problem. I came up with this solution and it works.
import calendar
import collections.defaultdict as dd
days_count = dd(int)
obj = calendar.Calendar()
days = [x for x in obj.itermonthdays2(year,month)]
for day in days:
if day[0] != 0: #these are empty days in a monthly calendar
days_count[day[1]] += 1
max_day = max(days_count, key=days_count.get)
This will give an integer value ( 0=Monday, 1=Tuesday,... 6= Sunday).
You can use a function or dictionary to convert the integer into a string representation of weekday.
FYI: This solution gives the first occurring weekday in case there are more than one most occurring weekday
I believe this should work.
whichever day come on 29th is coming 5 times in a month and coming first.
In case of Leap year, all days come 4 times, so Monday being 1st day of week should come.
import datetime
try:
    print(datetime.date(year, month,29).strftime('%A'))
except ValueError:
    print("Monday")
You just need the day name for any valid dates from the 29th to the 31st of the month. If any of these these are not valid dates then stop trying any more.
import datetime
def get_most_common_days(year, month):
days = []
for daynum in range(29, 32):
try:
days.append(datetime.date(year, month, daynum).strftime('%A'))
except ValueError:
break
return days
print(get_most_common_days(2020, 8))
This gives
['Saturday', 'Sunday', 'Monday']
the most common days in August 2020. These occur 5 times. The days that are not listed occur 4 times.
This is my answer and it seems to be working-
import calendar
from collections import Counter
obj = calendar.Calendar()
my_list1 = []
for i in obj.itermonthdays2(year, month):
if i[0] == 0:
pass
else:
my_list1.append(str(i[1]))
int_values = []
for i in my_list1:
int_values.append(int(i))
num = Counter(int_values)
num = num.most_common(1)[0][0]
if num == 0:
print("Monday")
if num == 1:
print("Tuesday")
if num == 2:
print("Wednesday")
if num == 3:
print("Thursday")
if num == 4:
print("Friday")
if num == 5:
print("Saturday")
if num == 6:
print("Sunday")
The end part is a bit long but I couldnt find any way to get past it.
#!/bin/python3
import math
import os
import random
import re
import sys
import calendar
import datetime
from collections import Counter
def usingcalendar(datetuple):
# Write your code here
year = datetuple[0]
temp = list(datetuple)
if (year%4==0):
temp[1] = 2
datetuple = tuple(temp)
print(calendar.month(datetuple[0],datetuple[1]))
obj = calendar.Calendar()
l = []
dt=list(datetuple)
obj = calendar.Calendar()
for day in obj.itermonthdates(dt[0], dt[1]):
l.append(day)
rev = l[:-8:-1]
rev.reverse()
print(rev)
try:
print(datetime.date(datetuple[0],datetuple[1],29).strftime('%A'))
except ValueError:
print("Monday")
if __name__ == '__main__':
qw1 = []
for _ in range(3):
qw1_item = int(input().strip())
qw1.append(qw1_item)
tup=tuple(qw1)
usingcalendar(tup)
Find the weekday of the first day of the month. Obviously, the weekday on which the first day of the month falls will be the most frequent one.
If there are multiple days it doesn't matter as they will come only after this and we need to find only the first one.
from datetime import datetime
month, year = 2, 2020
first_date = datetime(month=2, year=2020, day=1)
first_most_frequent_weekday = first_date.strftime("%A")
print(first_most_frequent_weekday) # Saturday
I guess you should consider using pandas in case of multiple recurring values
import pandas as pd
List = [2, 1, 2, 2, 1, 3, 1]
# Create a panda DataFrame using the list
df=pd.DataFrame({'Number': List})
# Creating a new dataframe to store the values with appropriate column name
# value_counts() returns the count based on the grouped column values
df1 = pd.DataFrame(data=df['Number'].value_counts(), columns=[['Number','Count']])
# The values in the List become the index of the new dataframe.Setting these index as a column
df1['Count']=df1['Number'].index
# Fetch the list of frequently repeated columns
list(df1[df1['Number']==df1.Number.max()]['Count'])
Output
[2,1]
Make use of this example in your scenario!

KeyError: 20, not sure what is wrong

I am new to python and am currently trying to create a program that will create a list of yearly percentage changes in revenue.
This is what I have so far:
revs = {}
for year in range(14, 20):
revs[year] = float(input("Revenue in {0}: ".format(year)))
revs_change = []
for year in range(14, 20):
next_year = year + 1
revs_change.append((revs[next_year] - revs[year])/revs[year])
print(revs_change[0])
The error comes on the 8th line and it has something to do with using the variable next_year.
Thanks!
If you are going to print out the values of year and next_year the problem is that there is no value for revs[next_year=20].
One way is to do like this-
start = 14
end = 21
for year in range(start, end):
revs[year] = float(input("Revenue in {0}: ".format(year)))
revs_change = []
for year in range(start, end-1):
next_year = year + 1
print(f"Year: {year}, Next Year: {next_year}\n")
revs_change.append( (revs[next_year] - revs[year])/revs[year] )
print(revs_change[0])
In Python, the end limit for any range you give will be taken excluding that upper limit i.e if you set the upper limit as 20, it evaluates up to 19. Give the upper limit as 21 so that you'll get the desired output

Deleting elements in a list by their value as index

I have a list which is days in a time series that doesn't start at zero:
days = [2,3,4,5,...]
I have another list which is minutes of cloud on all days starting at day 0:
weather = [0,120,150,60,120,30,300,...]
I want to iterate through days, and remove it if the corresponding index in weather is greater than some value.
I've tried
downtime = 100
days_new = [x for i, x in enumerate(days) if weather[i] < downtime]
Which should then result in:
days_new = [3,5,...]
as the indexes removed (2,4) have a value greater than 100 in the list weather.
But it's removing them based off the index of days not its value as an index. I.e. this only works if my list starts at 0, not any integer greater than 0. How can I fix this?
Your requirement assumes weather is subscriptable by all the items in days, in which case you wouldn't need enumerate. Index weather directly with the indices in days:
days_new = [x for x in days if weather[x] < downtime]
If weather is given for all days and day from days is an index in weather:
downtime = 100
new_days = [day for day in days if weather[day] <= downtime]
You can try this
[x for x in days if weather[x] <= 100]
Output, for your input:
[3, 5]
You can try this way:
days = [2,3,4,5,...]
weather = [0,120,150,60,120,30,300,...]
downtime = 100
days_new = []
for x in days:
if weather[x] < downtime:
days_new.append(x)
# output :
days_new = [3, 5,...]

How to find number of Mondays or any other weekday between two dates in Python?

I have two dates between which I need to find out how many Mon- Fri are coming(except for Sta, Sun), everyday should be counted
Currently I am thinking this:
import calendar
import datetime
start_date = datetime.datetime.strptime("01/01/2017",'%d/%m/%Y')
end_date = datetime.datetime.strptime("31/01/2017",'%d/%m/%Y')
week_arr = [0] * 7
calendar.day_name[start_date.weekday()] ## will give me name of day
"""
As I receive Monday I will increment week_arr[0] by 1, Tuesday
week_arr[1]+= 1,
"""
I am not getting how to do it effectively so that I dont use much line of code(less if -else and for loops), may be some tricks in pandas.
You can define a function and use it like this :
def num_days_between( start, end, week_day):
num_weeks, remainder = divmod( (end-start).days, 7)
if ( week_day - start.weekday() ) % 7 < remainder:
return num_weeks + 1
else:
return num_weeks
where week_day is day number you wan to calculate count.
This code still uses a for loop and an if/else.
import datetime
import calendar
def weekday_count(start, end):
start_date = datetime.datetime.strptime(start, '%d/%m/%Y')
end_date = datetime.datetime.strptime(end, '%d/%m/%Y')
week = {}
for i in range((end_date - start_date).days):
day = calendar.day_name[(start_date + datetime.timedelta(days=i+1)).weekday()]
week[day] = week[day] + 1 if day in week else 1
return week
print(weekday_count("01/01/2017", "31/01/2017"))
# prints result
# {'Monday': 5, 'Tuesday': 5, 'Friday': 4, 'Wednesday': 4, 'Thursday': 4, 'Sunday': 5, 'Saturday': 4}
Number of Mondays in 2020 can be got using numpy library
import numpy as np
np.busday_count('2020', '2021', weekmask='Mon')
This is efficient - even in the face of ten thousands of days between start and end - and still very flexible (it iterates at most 7 times inside the sum function):
def intervening_weekdays(start, end, inclusive=True, weekdays=[0, 1, 2, 3, 4]):
if isinstance(start, datetime.datetime):
start = start.date() # make a date from a datetime
if isinstance(end, datetime.datetime):
end = end.date() # make a date from a datetime
if end < start:
# you can opt to return 0 or swap the dates around instead
raise ValueError("start date must be before end date")
if inclusive:
end += datetime.timedelta(days=1) # correct for inclusivity
try:
# collapse duplicate weekdays
weekdays = {weekday % 7 for weekday in weekdays}
except TypeError:
weekdays = [weekdays % 7]
ref = datetime.date.today() # choose a reference date
ref -= datetime.timedelta(days=ref.weekday()) # and normalize its weekday
# sum up all selected weekdays (max 7 iterations)
return sum((ref_plus - start).days // 7 - (ref_plus - end).days // 7
for ref_plus in
(ref + datetime.timedelta(days=weekday) for weekday in weekdays))
This takes both datetime.date as well as datetime.datetime objects for start and end, respectively.
Also, you can choose between a closed (inclusive=True) and a half-open (inclusive=False) interval.
By default, it calculates the number of workdays between the dates, but you can choose any set of weekdays (weekend days: weekdays=[5, 6]) or single weekdays (Wednesdays: weekdays=2) as well.
If anyone need an even simpler answer,
from datetime import date
d1 = date(2017, 1, 4)
d2 = date(2017, 1, 31)
count = 0
for d_ord in range(d1.toordinal(), d2.toordinal()):
d = date.fromordinal(d_ord)
if (d.weekday() == 4):
count += 1
print(count)
I found a simple and easy to understand code using for loop.
Take first date identify its weekday with "%a" compare it with your intrested weekday if found increment a count value. and repeat the steps till your last day.
Code is as below for your refrence i took monday as my intrested weekdays.
import datetime
A1=datetime.datetime.strptime("1/23/2016", "%m/%d/%Y")
A2=datetime.datetime.strptime("11/10/2016", "%m/%d/%Y")
count=0
week="Mon"
for i in range ((A2-A1).days): #gives the no of days from A1 to A2
if A1.strftime("%a")==week:
count+=1
A1+=datetime.timedelta(days=1)
print(count)
https://www.w3schools.com/python/python_datetime.asp

Count number of sundays in current month

How can I get the numberof Sundays of the current month in Python?
Anyone got any idea about this?
This gives you the number of sundays in a current month as you wanted:
import calendar
from datetime import datetime
In [367]: len([1 for i in calendar.monthcalendar(datetime.now().year,
datetime.now().month) if i[6] != 0])
Out[367]: 4
I happened to need a solution for this, but was unsatisfactory with the solutions here, so I came up with my own:
import calendar
year = 2016
month = 3
day_to_count = calendar.SUNDAY
matrix = calendar.monthcalendar(year,month)
num_days = sum(1 for x in matrix if x[day_to_count] != 0)
I'd do it like this:
import datetime
today = datetime.date.today()
day = datetime.date(today.year, today.month, 1)
single_day = datetime.timedelta(days=1)
sundays = 0
while day.month == today.month:
if day.weekday() == 6:
sundays += 1
day += single_day
print 'Sundays:', sundays
My take: (saves having to worry about being in the right month etc...)
from calendar import weekday, monthrange, SUNDAY
y, m = 2012, 10
days = [weekday(y, m, d+1) for d in range(*monthrange(y, m))]
print days.count(SUNDAY)
Or, as #mgilson has pointed out, you can do away with the list-comp, and wrap it all up as a generator:
sum(1 for d in range(*monthrange(y,m)) if weekday(y,m,d+1)==SUNDAY)
And I suppose, you could throw in a:
from collections import Counter
days = Counter(weekday(y, m, d + 1) for d in range(*monthrange(y, m)))
print days[SUNDAY]
Another example using calendar and datetime:
import datetime
import calendar
today = datetime.date.today()
m = today.month
y = today.year
sum(1 for week in calendar.monthcalendar(y,m) if week[-1])
Perhaps a slightly faster way to do it would be:
first_day,month_len = monthrange(y,m)
date_of_first_sun = 1+6-first_day
print sum(1 for x in range(date_of_first_sun,month_len+1,7))
You can do this using ISO week numbers:
from datetime import date
bom = date.today().replace(day=1) # first day of current month
eom = (date(bom.year, 12, 31) if bom.month == 12 else
(bom.replace(month=bom.month + 1) - 1)) # last day of current month
_, b_week, _ = bom.isocalendar()
_, e_week, e_weekday = eom.isocalendar()
num_sundays = (e_week - b_week) + (1 if e_weekday == 7 else 0)
In general for a particular day of the week (1 = Monday, 7 = Sunday) the calculation is:
num_days = ((e_week - b_week) +
(-1 if b_weekday > day else 0) +
( 1 if e_weekday >= day else 0))
import calendar
MONTH = 10
sundays = 0
cal = calendar.Calendar()
for day in cal.itermonthdates(2012, MONTH):
if day.weekday() == 6 and day.month == MONTH:
sundays += 1
PAY ATTENTION:
Here are the Calendar.itermonthdates's docs:
Return an iterator for one month. The iterator will yield datetime.date
values and will always iterate through complete weeks, so it will yield
dates outside the specified month.
That's why day.month == MONTH is needed
If you want the weekdays to be in range 0-6, use day.weekday(),
if you want them to be in range 1-7, use day.isoweekday()
My solution.
The following was inspired by #Lingson's answer, but I think it does lesser loops.
import calendar
def get_number_of_weekdays(year: int, month: int) -> list:
main_calendar = calendar.monthcalendar(year, month)
number_of_weeks = len(main_calendar)
number_of_weekdays = []
for i in range(7):
number_of_weekday = number_of_weeks
if main_calendar[0][i] == 0:
number_of_weekday -= 1
if main_calendar[-1][i] == 0:
number_of_weekday -= 1
number_of_weekdays.append(number_of_weekday)
return sum(number_of_weekdays) # In my application I needed the number of each weekday, so you could return just the list to do that.

Categories