Reputation: 27
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
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:
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.input_day >= 31
, so May 1 to 30 are all invalid. It should be <=
.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
Reputation: 8962
def
or loopsYes, 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.
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.
Now, using the two seasons
and transitions
dictionaries, implement this algorithm (I'll leave this part up to you):
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
.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.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
.day
to t_day
. If day
is less than t_day
, return the first item in the 2-tuple. Otherwise return the second.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"
.
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.
def
and loopsBy 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.
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
season
logicYou 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}")
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