Derek Pruitt
Derek Pruitt

Reputation: 27

Is there a better way to check months and days to print the seasons in python? I cant use def and class or loops

I am writing a program that takes a date as input and outputs the date's season. The input is a string to represent the month and an int to represent the day. This is for a school project, while this works it seems a little overkill, is there a better way to get the same results? without using def, class or loops. If not, if you could just let me know its not possible.

input_month = input()
input_day = int(input())

spring = ['March', 'April', 'May', 'June']
summer = ['June', 'July', 'August', 'September']
autumn = ['September', 'October', 'November', 'December']
winter = ['December', 'January', 'February', 'March']
if input_month in spring + summer + autumn + winter:
    if (input_day > 31) or (input_day <= 0):
        print('Invalid')
    elif (input_month == spring[0]) and (input_day >= 20):
        print('Spring')
    elif (input_month == spring[1]) and (input_day <= 30):
        print('Spring')
    elif (input_month == spring[2]) and (input_day >= 31):
        print('Spring')
    elif (input_month == spring[3]) and (input_day <= 20):
        print('Spring')

    elif (input_month == summer[0]) and (input_day >= 21):
        print('Summer')
    elif (input_month == summer[1]) and (input_day <= 31):
        print('Summer')
    elif (input_month == summer[2]) and (input_day <= 31):
        print('Summer')
    elif (input_month == summer[3]) and (input_day <= 21):
        print('Summer')

    elif (input_month == autumn[0]) and (input_day >= 22):
        if input_day >= 31:
            print('Invalid')
        else:
            print('Autumn')
    elif (input_month == autumn[1]) and (input_day <= 31):
        print('Autumn')
    elif (input_month == autumn[2]) and (input_day <= 30):
        print('Autumn')
    elif (input_month == autumn[3]) and (input_day <= 20):
        print('Autumn')

    elif (input_month == winter[0]) and (input_day >= 21):
        print('Winter')
    elif (input_month == winter[1]) and (input_day <= 31):
        print('Winter')
    elif (input_month == winter[2]) and (input_day <= 28):
        print('Winter')
    elif (input_month == winter[3]) and (input_day <= 19):
        print('Winter')
    else:
        print('Invalid')

Upvotes: 1

Views: 2634

Answers (2)

adamkwm
adamkwm

Reputation: 1173

To check if input day is inside valid range, a dictionary is helpful to map each month and days in that month. To check which season the input is belong to, you may try to compare days in year after converting season boundaries and input to that.

month_days = {
    "January": 31, "February": 28, "March": 31, "April": 30,
    "May": 31, "June": 30, "July": 31, "August": 31,
    "September": 30, "October": 31, "November": 30, "December": 31
}

# cummulative year days at the start of that month
cum_year_days = {}
summ = 0
for m, d in month_days.items():
    cum_year_days[m] = summ
    summ += d

# Or you can hard coded it like month_days
# cum_year_days = {
#     'January': 0, 'February': 31, 'March': 59, 'April': 90, 
#     'May': 120, 'June': 151, 'July': 181, 'August': 212, 
#     'September': 243, 'October': 273, 'November': 304, 'December': 334
# }

month = input("Month: ")
day = int(input("Day: "))

if month in month_days and 1 <= day <= month_days[month]:
    # convert input to year days
    year_days = cum_year_days[month] + day
    # convert season boundaries to year days in similar way
    if cum_year_days["March"] + 20 <= year_days <= cum_year_days["June"] + 20:
        print("Spring")
    elif cum_year_days["June"] + 21 <= year_days <= cum_year_days["September"] + 21:
        print("Summer")
    elif cum_year_days["September"] + 22 <= year_days <= cum_year_days["December"] + 20:
        print("Autumn")
    else:
        print("Winter")
else:
    print("Invalid")

Notes on your code and details on my comment:

  1. In your original code, you have a line input_month == spring or summer or autumn or winter. You have changed it but I want to explain why it is wrong. It is read as (input_month == spring) or summer or autumn or winter, the first one compares equality between string and a list so it will be False, and non-empty list is truthy so the following condition will return True. Therefore, it is True because you have non-empty list instead of valid input.
  2. For May, you have condition input_day >= 31, so May 1 to 30 are all invalid. It should be <=.
  3. For June, you have condition input_day >= 21 and restriction from the first if statement input_day > 31, so June 21 to June 31 are all valid, while June has no 31. You may check it like 21 <= input_day <= 30.

I found out that using tuple to represent date and performing tuple comparison may be more intuitive.

month_days = {
    "January": (1, 31), "February": (2, 28), "March": (3, 31), "April": (4, 30),
    "May": (5, 31), "June": (6, 30), "July": (7, 31), "August": (8, 31),
    "September": (9, 30), "October": (10, 31), "November": (11, 30), "December": (12, 31)
}

month = input("Month: ")
day = int(input("Day: "))

if month in month_days and 1 <= day <= month_days[month][1]:
    # convert input to date
    date = (month_days[month][0], day)
    # convert season boundaries to date
    if (3, 20) <= date <= (6, 20):
        print("Spring")
    elif (6, 21) <= date <= (9, 21):
        print("Summer")
    elif (9, 22) <= date <= (12, 20):
        print("Autumn")
    else:
        print("Winter")
else:
    print("Invalid")

Tuples compare lexicographically, that is compare element by element, return result immediately when they are not equal. In this case, it will check the month first, earlier month with lower value will be < later month. If months are equal, it will check the day, ealier day with lower value will be < later day. If both are equals, they are ==.

Upvotes: 2

ddejohn
ddejohn

Reputation: 8962

Without using def or loops

Yes, there's a much easier way to do this: take advantage of the built-in dict data type. You'll need to do some work, but here's one approach (out of many) that might help.

A nicer data structure

seasons = {"January": ("winter",),
           "February": ("winter",),
           "March": ("winter", "spring"),
           "April": ("spring",),
           "May": ("spring",),
           "June": ("spring", "summer"),
           "July": ("summer",),
           "August": ("summer",),
           "September": ("summer", "autumn"),
           "October": ("autumn",),
           "November": ("autumn",),
           "December": ("autumn", "winter")}


# Equinox and solstice dates are a little fuzzy, fiddle with these if you want
transitions = {("winter", "spring"): 21,
               ("spring", "summer"): 21,
               ("summer", "autumn"): 23,
               ("autumn", "winter"): 21}


days_in_month = {"January": 31,
                 "February": 28,  # Not accounting for leap years
                 "March": 31,
                 "April": 30,
                 "May": 31,
                 "June": 30,
                 "July": 31,
                 "August": 31,
                 "September": 30,
                 "October": 31,
                 "November": 30,
                 "December": 31}

Now you can get user input

# Get user input
month = input("enter month: ").lower().title()
day = int(input("enter day: "))


# Validate user input
if month not in seasons:
    print("invalid month")
elif not (1 <= day <= days_in_month[month]):
    print("invalid day")

One issue here is that if the month is invalid, the program doesn't do anything about it.

The algorithm

Now, using the two seasons and transitions dictionaries, implement this algorithm (I'll leave this part up to you):

  1. Get the "season" by accessing the season dictionary with a key whose value is month. This dict returns either a 1-tuple or a 2-tuple, depending on whether there's a seasonal transition that month.
  2. If the tuple returned by the season lookup is a 1-tuple (check its length), you can directly return the first value in that tuple, which will be the correct season for that month, and you're done. If not, proceed to step 3.
  3. If the tuple returned by the season lookup is a 2-tuple, use that tuple as the key in the transitions dictionary. This will return an integer, the day the season transition occurs, which you can call t_day.
  4. Compare the day to t_day. If day is less than t_day, return the first item in the 2-tuple. Otherwise return the second.
  5. Done!

Example:

If month = March, then season will be ("winter", "spring"). Since the length of the season tuple is 2, lookup the t_day, which will be 21 since the spring equinox is generally considered to occur on March 21. Suppose now that day = 13. Since 13 is not greater than or equal to 21, return season[0], which is "winter".

Solution

Since other people are just giving you answers straight up, I suppose I can provide my own (I was hoping to encourage you to figure the basic algorithm out on your own):

season = seasons.get(month)
transition_day = transitions.get(season, 99)
season = season[day >= transition_day]

This follows roughly the same logic that I laid out in the algorithm section, but takes advantage of the fact that bool is a subclass of int, meaning you can subscript sequences using boolean values, e.g.: "ab"[False] -> "a" (since False == 0).

By using .get() on the transition_day dictionary and providing a fallback value, we can simply subscript the season tuple (which is either 1 or 2 elements) using the boolean check day >= transition_day. If that boolean condition is True, we return season[1], otherwise we return season[0].

The reason this works even when season only has one element is because of that .get() which returns 99 if the key season isn't present in the transitions dictionary. This ensures that day >= transition_day will always be False when transition_day is 99, which means that you're guaranteed to always only ever call season[False] == season[0] in the case that season is only a 1-tuple.

Opening up to using def and loops

By using def, you can define functions which separate the logic of your code into isolated pieces which all perform a single task. This helps massively in keeping code clean, organized, and easy to reason about.

By using a while loop, you can ensure that the user input is always valid and that your code won't proceed until the user provides valid input.

A better way to deal with user input

Here's a freebie. There's a lot going on here, so take your time understanding what these two functions do:

def get_month():
    while True:
        month = input("enter month: ").lower().title()
        if month not in seasons:
            print("invalid month!")
            continue
        return month


def get_day(max_days):
    while True:
        try:
            day = int(input("enter day: "))
        except ValueError:
            print("invalid entry, day must be an integer!")
            continue
        if not (1 <= day <= max_days):
            print(f"invalid day, must be between 1 and {max_days}")
            continue
        return day

Encapsulating the season logic

You can do the same thing with the logic for determining the season:

def get_season(month, day):
    season = seasons.get(month)
    transition_day = transitions.get(season, 99)
    return season[day >= transition_day]

Then your main script is reduced to three function calls:

if __name__ == "__main__":
    # Get user input
    month = get_month()
    day = get_day(max_days=days_in_month[month])
    
    # Get season
    season = get_season(month, day)
    print(f"{month}, {day}: {season}")

Notes

With this solution, we've reduced the 40 lines of code it took to perform this task using if-elif-else down to a mere three lines of code (for the core logic of determining the season).

By wrapping your logic in a function,

def get_season(month, day):
    season = seasons.get(month)
    transition_day = transitions.get(season, 99)
    return season[day >= transition_day]

You now have three explicitly named functions: get_month(), get_day(), and get_season(). These functions make it abundantly clear what task each one of these functions performs. This is something you want to think about when writing scripts or designing software. It's also known as the single responsibility principle and is an extremely important part of programming.

Finally, to test this function and ensure that it is working properly, you can use this little snippet to enumerate all 365 days of the year:

for month, max_days in days_in_month.items():
    for day in range(1, max_days + 1):
        season = get_season(month, day)
        print(f"{month}, {day}: {season}")
    print()

Upvotes: 0

Related Questions