Iteration through a list

116 Views Asked by At

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

3

There are 3 best solutions below

0
On BEST ANSWER

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 :

  1. 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.
  2. 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
1
On

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.

3
On

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')