Dummy Encoding for Date(time) in Python 3.7 - python

i wanted to create a function that encodes the month of a date(time) object into a season. So for example, if we have 2 seasons and the fourth month the function would return 0 as encoding. My question is now how to create such a function that it is most efficient but also pythonic (less lines of code). I came up with 3 different approaches, the first one looks like this:
def seasonal_encoding(month: int, seasons):
assert 12 % seasons == 0, "Seasons must be in [1, 2, 3, 4, 6, 12]"
if seasons == 1:
return _one_season()
elif seasons == 2:
return _two_seasons(month)
elif seasons == 3:
return _three_seasons(month)
elif seasons == 4:
return _four_seasons(month)
elif seasons == 6:
return _six_seasons(month)
elif seasons == 12:
return _twelve_seasons(month)
where for example three_seasons looks like this:
def _three_seasons(month):
if month in range(1, 5):
return 0
elif month in range(5, 9):
return 1
elif month in range(9, 13):
return 2
This is so far the fastest approach i came up with, but as you can imagine this may work for
a season encoding, but if we want to encode other date(time) attributes, for example hours, this becomes cumbersome real fast. So i came up with a second solution which is a little slower but has less lines of code:
def for_loop_seasonal_encoding(month: int, seasons: int):
assert 12 % seasons == 0, "Seasons must be in [1, 2, 3, 4, 6, 12]"
if seasons != 12:
stepsize = int(12 / seasons)
for i in range(1, seasons+1):
if month in range((i-1) * stepsize + 1, i * stepsize + 1):
return i-1
else:
return month
I also had another solution with numpy before coming up with the for loop solution which i will add for reasons of completeness, but this was four times slower then the other solutions.
def numpy_seasonal_encoding(month: int, seasons):
assert 12 % seasons == 0, "Seasons must be in [1, 2, 3, 4, 6, 12]"
arr = np.array(range(1, 13))
reshaped_arr = arr.reshape(seasons, int(12 / seasons))
return np.where(reshaped_arr == month)[0][0]
So my Question would be: Has anyone of you an idea how to make this as fast as the if representation but with way less lines of codes in "pure" python (so without resorting to Cython, numba etc., external packages like numpy are ok)?
This is the link to the .py file
Python File that includes the function used for timing and the code. Thank you in advance for any help.

Related

Using the result from a function in a new one (plus more)

I am trying to do the following:
1) calculate the amount of the same numbers in the data list. eg : there are three numbers between and including 10 and 20.
2) represent the value for each number range with the same number of '#'. eg: there are 3 numbers between 10 and 20 = ###.
Ideally ending in having the two values represented next to each other.
Unfortunately I really can't figure out step two and any help would really be appreciated.
My code is below:
def count_range_in_list(li, min, max):
ctr = 0
for x in li:
if min <= x <= max:
ctr += 1
return ctr
def amountOfHashes(count_range_in_list,ctr):
ctr = count_range_in_list()
if ctr == 1:
print ('#')
elif ctr == 2:
print ('##')
elif ctr == 3:
print ('###')
elif ctr == 4:
print ('####')
elif ctr == 5:
print ('#####')
elif ctr == 6:
print ('######')
elif ctr == 7:
print ('#######')
elif ctr == 8:
print ('########')
elif ctr == 9:
print ('#########')
elif ctr == 10:
print ('##########')
data = [90,30,13,67,85,87,50,45,51,72,64,69,59,17,22,23,44,25,16,67,85,87,50,45,51]
print(count_range_in_list(data, 0, 10),amountOfHashes)
print(count_range_in_list(data, 10, 20),amountOfHashes)
print(count_range_in_list(data, 20, 30),amountOfHashes)
print(count_range_in_list(data, 30, 40),amountOfHashes)
print(count_range_in_list(data, 40, 50),amountOfHashes)
print(count_range_in_list(data, 50, 60),amountOfHashes)
print(count_range_in_list(data, 60, 70),amountOfHashes)
print(count_range_in_list(data, 70, 80),amountOfHashes)
print(count_range_in_list(data, 80, 90),amountOfHashes)
print(count_range_in_list(data, 90, 100),amountOfHashes)
I'll start by clearing out some doubts you seem to have.
First, how to use the value of a function inside another one:
You don't need to pass the reference of a method to another here. What I mean is, in amountOfHashes(count_range_in_list,ctr) you can just drop count_range_in_list as a parameter, and just define it like amountOfHashes(ctr). Or better yet, use snake case in the method name instead of camel case, so you end up with amount_of_hashes(ctr). Even if you had to execute count_range_in_list inside amount_of_hashes, Python is smart enough to let you do that without having to pass the function reference, since both methods are inside the same file already.
And why do you only need ctr? Well, count_range_in_list already returns a counter, so that's all we need. One parameter, named ctr. In doing so, to "use the result from a function in a new one", we could:
def amount_of_hashes(ctr):
...
# now, passing the value of count_range_in_list in amount_of_hashes
amount_of_hashes(count_range_in_list(data, 10, 20))
You've figured out step 1) quite well already, so we can go to step 2) right away.
In Python it's good to think of iterative processes such as yours dynamically rather than in hard coded ways. That is, creating methods to check the same condition with a tiny difference between them, such as the ones in amountOfHashes, can be avoided in this fashion:
# Method name changed for preference. Use the name that best fits you
def counter_hashes(ctr):
# A '#' for each item in a range with the length of our counter
if ctr == 0:
return 'N/A'
return ''.join(['#' for each in range(ctr)])
But as noted by Roland Smith, you can take a string and multiply it by a number - that'll do exactly what you think: repeat the string multiple times.
>>> 3*'#'
###
So you don't even need my counter_hashes above, you can just ctr*'#' and that's it. But for consistency, I'll change counter_hashes with this new finding:
def counter_hashes(ctr):
# will still return 'N/A' when ctr = 0
return ctr*'#' or 'N/A'
For organization purposes, since you have a specific need (printing the hashes and the hash count) you may then want to format right what comes into print, you could make a specific method for the printing, that calls both counter_hashes and count_Range_in_list, and gives you a cleaner result afterwards:
def hash_range(data, min, max):
ctr = count_range_in_list(data, min, max)
hashes = counter_hashes(ctr)
print(f'{hashes} | {ctr} items in range')
The use and output of this would then become:
>>> data = [90,30,13,67,85,87,50,45,51,72,64,69,59,17,22,23,44,25,16,67,85,87,50,45,51]
>>> hash_range(data, 0, 10)
N/A | 0 items in range
>>> hash_range(data, 10, 20)
### | 3 items in range
>>> hash_range(data, 20, 30)
#### | 4 items in range
And so on. If you just want to print things right away, without the hash_range method above, it's simpler but more lengthy/repetitive if you want a oneliner:
>>> ctr = count_range_in_list(data, 10, 20)
>>> print(counter_hashes(ctr), ctr)
### 3
Why not just do it like this:
Python 3.x:
def amount_of_hashes(ctr):
while ctr > 0:
print('#', end = '')
ctr = ctr-1
Python 2.x:
def amount_of_hashes(ctr):
while ctr > 0:
print '#',
ctr = ctr-1
Counting the number in a list can be done like this:
def count_range_in_list(li, mini, maxi):
return len([i for i in li if mini <= i <= maxi])
Then making a number of hashes is even simpler. Just multiply a string containing the hash sign with a number.
print(ount_range_in_list(data, 0, 10)*'#')
Example in IPython:
In [1]: data = [90,30,13,67,85,87,50,45,51,72,64,69,59,17,22,23,44,25,16,67,85,87,50,45,51]
In [2]: def count_range_in_list(li, mini, maxi):
...: return len([i for i in li if mini <= i <= maxi])
...:
In [3]: print(count_range_in_list(data, 0, 10)*'#')
In [4]: print(count_range_in_list(data, 10, 20)*'#')
###
In [5]: print(count_range_in_list(data, 20, 30)*'#')
####
There are many ways to do this. One way is to use a for loop with range:
# Most basic
def count_range_in_list(li, min, max):
ctr = 0
hashes = ""
for x in li:
if min <= x <= max:
ctr += 1
hashes += "#"
print("There are {0} numbers = {1}".format(ctr, hashes))
# more declarative
def count_range_in_list(li, min, max):
nums = [x for x in li if min <= x <= max]
hashes = "".join(["#" for n in nums])
print("There are {0} numbers = {1}".format(len(nums), hashes))

Writing and using your own functions - basics

Your task is to write and test a function which takes two arguments (a year and a month) and returns the number of days for the given month/year pair (yes, we know that only February is sensitive to the year value, but we want our function to be universal). Now, convince the function to return None if its arguments don't make sense.
Use a list filled with the months' lengths. You can create it inside the function - this trick will significantly shorten the code.
I have got the code down but not the 'none' part. Can someone help me with this?
def IsYearLeap(year):
if (year%4==0):
return True
if (year%4!=0):
return False
def DaysInMonth(year,month):
if month in {1, 3, 5, 7, 8, 10, 12}:
return 31
elif month==2:
if IsYearLeap(year):
return 29
else:
return 28
elif month in {4,6,8,9,11}:
return 30
else:
return none
testyears = [1900, 2000, 2016, 1987,2019]
testmonths = [ 2, 2, 1, 11,4]
testresults = [28, 29, 31, 30,33]
for i in range(len(testyears)):
yr = testyears[i]
mo = testmonths[i]
print(yr,mo,"->",end="")
result = DaysInMonth(yr,mo)
if result == testresults[i]:
print("OK")
else:
print("Failed")
It seems that you have rather made a simple mistake. If you are not used the case-sensitive programming languages or have no experience in programming languages, this is understandable.
The keyword None is being misspelled as the undefined word none.
I think your testresults is wrong. February of 1900 should be 29 days also April of 2019 30 days. Also its None instead none. Another things also its better to using list on months list so you could using [1, 3, 5, 7, ...] instead {1, 3, 5, 7, ...}.
Also from your test cases you won't got None, in case you want check this case you could check with month = 13, and you will cover this case
As a further comment to the other good answers to this question, the correct rule for leap years should be something like:
def is_leap_year(year):
""" is it a leap year?
>>> is_leap_year(1984)
True
>>> is_leap_year(1985)
False
>>> is_leap_year(1900)
False
>>> is_leap_year(2000)
True
"""
return (year % 4 == 0 and
(year % 100 != 0 or year % 400 == 0))
Similarly, the test cases need to be clear that 1900 was not a leap year, 2000 was. I recommend writing a separate set of test cases for is_leap_year. Ultimately, in production code, you will be better off to use one of the many time/date libraries. The comments that I've provided make use of doctest to provide this unit test quickly.
A function which does not explicitly return anything implicitly returns None.
In addition to the spelling error (none vs None) you are using this by accident here:
def IsYearLeap(year):
if (year%4==0):
return True
if (year%4!=0):
return False
Can you see what will happen if neither of the conditions is true? It won't return either False or True, which presumably the caller expects. (Though if you check whether None == True you will get False, and not None is True, so you won't get a syntax error, just a result which might be different from what you expect - the worst kind of bug!)
def IsYearLeap(year):
return year % 4 == 0 & (year % 400 == 0 | year % 100 != 0)
def DaysInMonth(year,month):
if month in [1, 3, 5, 7, 8, 10, 12]:
return 31
elif month==2:
if IsYearLeap(year):
return 29
else:
return 28
elif month in [4,6,8,9,11]:
return 30
else:
return None
#
testYears = [1900, 2000, 2016, 1987]
testMonths = [2, 2, 1, 11]
testResults = [28, 29, 31, 30]
for i in range(len(testYears)):
yr = testYears[i]
mo = testMonths[i]
print(yr, mo, "->", end="")
result = DaysInMonth(yr, mo)
if result == testResults[i]:
print("OK")
else:
print("Failed")
def is_year_leap(year):
return year % 4 == 0 and year % 100 != 0 or year % 400 == 0
def days_in_month(year, month):
days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if type(year) != int or year < 1582 or\
type(month) != int or month < 1 or month > 12:
return None
elif is_year_leap(year):
del days[1]
days.insert(1, 29)
return days[month - 1]
test_years = [1900, 2000, 2016, 1987]
test_months = [2, 2, 1, 11]
test_results = [28, 29, 31, 30]
for i in range(len(test_years)):
yr = test_years[i]
mo = test_months[i]
print(yr, mo, "->", end="")
result = days_in_month(yr, mo)
if result == test_results[i]:
print("OK")
else:
print("Failed")
A few minor remarks:
You should remove the duplicate 8th month (August is listed for 30 and 31 days),
Better replace brackets {} with the list [],
Replace none with None (Python is case-sensitive, None is the keyword),
Add one more condition for a leap year:
(year % 400 == 0) and (year % 100 == 0) -> return True
(year % 4 == 0) and (year % 100 != 0) -> return True

How to Convert time to category in python?

How to convert time to some category?
For example, times between 17:30:13 and 19:30:13 should be categorized as "in the evening." Times between 12:00:12 and 13:00:12 should fall under "noon."
What is a good approach to do this in python?
If you want to use it for machine learning you normally want it to be a integer value, or transform it into a one-hot encoding.
To get the integer value you can integer divide on the hour:
from datetime import datetime,timedelta
values = [datetime.now() + timedelta(hours=i) for i in range(24)]
[value.hour // 6 for value in values]
yields:
[3, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3]
I didn't use any machine learning library. Wrote a function that returns a category
depending on what the lower bound hour of the day is.
Based on your example , I'd assume the lower bound hour does prioritize the category.
Maybe I'd like to understand the use case a bit better.
# hour can be some number from 0 to 23 which is also the lower bound hour
# in the mentioned interval
# 17:30:13- 19:30:13 should fall under "evening"
# 12:00:12-13:00:12 should fall under "noon"
# For 17:30:13- 19:30:13 , hour will be 17
# For 12:00:12-13:00:12 , hour will be 12
get_category(hour):
if hour < 12:
return "morning"
elif hour == 12:
return "noon"
elif hour > 12 and hour < 16:
return "evening"
else:
return "night"
You may need to name a function by yourself and customize the cutoff time.
import datetime
# test date
Testdate = datetime.datetime.now().time()
def GetDateCate (DateInput):
# name a cutoff date:
noon = datetime.time(12)
evening =datetime.time(18)
if Testdate <noon:
print('Morning')
elif Testdate <evening:
print('Afternoon')
else:
print('Night')
GetDateCate(Testdate)

Finding a path of a solved recursive function

In the code below, I got the optimal value of a recursive function that I set.
Now I want to get the values that helped build it - I mean, know which choice (or a "ride" (this is my decision)) was used in each step, and return that also as a string/array/somehow.
thestory : i have 6 rides, in every step i need to choose whether to go on the ride im on again, or switch. every ride has a fun rate for each time i go on it and i want to maximize the fun. right now i have the optimal value but not the rides that i went on that led me to it
The function F is the main focus here.
import math
import numpy as np
#fun rate for each ride,per loop
def funPerRide(rideNum,loopsNum):
if rideNum==1:
return 0
if rideNum==2:
return 100-np.power((4*loopsNum-10),2)
if rideNum==3:
return 50-(1000*np.power(np.e,(-loopsNum/2))/(5*loopsNum+20))
if rideNum==4:
return 4*loopsNum
if rideNum==5:
return 2.5*np.power(loopsNum,2)
if rideNum==6:
return 50*np.power(np.e,(-2*loopsNum+4))
def F(totalTime,timeLeft,rideNum,loopOnCurrRide):
#time of line+operation of ride
totalTimePerRide={1:0,2:40,3:15,4:20,5:23,6:11}
#time of operation of rides
operationTimePerRide={1:0,2:4,3:5,4:8,5:3,6:6}
#unfeasable conditions:
if timeLeft<0:
return -np.inf
if timeLeft+loopOnCurrRide*operationTimePerRide[rideNum]>totalTime:
return -np.inf
if loopOnCurrRide>3:
return -np.inf
#edge condition
if timeLeft == 0:
return 0
#fun if i stay on the ride im on right now
staying = funPerRide(rideNum,loopOnCurrRide+1)-funPerRide(rideNum,loopOnCurrRide)+F(totalTime,timeLeft-operationTimePerRide[rideNum],rideNum,loopOnCurrRide+1)
#calculating fun if i switch to the maximum-fun-ride, that is not the ride im currently at
switching = -1
whichRide=-1
for i in range(1,7):
if i>rideNum:
switchOption = funPerRide(i,loopOnCurrRide)+F(totalTime,timeLeft-4.5-totalTimePerRide[i],i,1)
if switchOption>switching:
switching, whichRide=switchOption,i
#calculating maximum fun between switching and staying
maxval,maxride=max((staying,rideNum),(switching,whichRide))
path.append(maxride)
maxval=float(maxval)
return float(maxval)
path = []
print(F(120,120,1,0),path)
Your function F can return a pair of two values: first - optimal answer, second - optimal path as a list of indexes.
def F(totalTime, timeLeft, rideNum, loopOnCurrRide):
# time of line+operation of ride
totalTimePerRide = {1: 0, 2: 40, 3: 15, 4: 20, 5: 23, 6: 11}
# time of operation of rides
operationTimePerRide = {1: 0, 2: 4, 3: 5, 4: 8, 5: 3, 6: 6}
if timeLeft + loopOnCurrRide * operationTimePerRide[rideNum] > totalTime:
return -10, []
if loopOnCurrRide > 3:
return -10, []
if timeLeft == 0:
return 0, []
staying, staying_path = F(totalTime, timeLeft - operationTimePerRide[rideNum], rideNum, loopOnCurrRide + 1)
staying += funPerRide(rideNum, loopOnCurrRide + 1) - funPerRide(rideNum, loopOnCurrRide)
staying_path = [-1] + staying_path
switching = -1
switching_path = []
for i in range(1, 7):
if i > rideNum:
switchOption, switchOption_path = F(totalTime, timeLeft - 4.5 - totalTimePerRide[i], i, 1)
switchOption += funPerRide(i, loopOnCurrRide)
if switchOption > switching:
switching = switchOption
switching_path = [i] + switchOption_path
return max((staying, staying_path), (switching, switching_path))
answer, path = F(120, 120, 1, 0)

Python: Converting ('Monday', 'Tuesday', 'Wednesday') to 'Monday to Wednesday'

I'm getting a sequence of day of the week. Python code of what I want to do:
def week_days_to_string(week_days):
"""
>>> week_days_to_string(('Sunday', 'Monday', 'Tuesday'))
'Sunday to Tuesday'
>>> week_days_to_string(('Monday', 'Wednesday'))
'Monday and Wednesday'
>>> week_days_to_string(('Sunday', 'Wednesday', 'Thursday'))
'Sunday, Wednesday, Thursday'
"""
if len(week_days) == 2:
return '%s and %s' % weekdays
elif week_days_consecutive(week_days):
return '%s to %s' % (week_days[0], week_days[-1])
return ', '.join(week_days)
I just need the week_days_consecutive function (the hard part heh).
Any ideas how I could make this happen?
Clarification:
My wording and examples caused some confusion. I do not only want to limit this function to the work week. I want to consider all days of the week (S, M, T, W, T, F). My apologies for not being clear about that last night. Edited the body of the question to make it clearer.
Edit: Throwing some wrenches into it
Wraparound sequence:
>>> week_days_to_string(('Sunday', 'Monday', 'Tuesday', 'Saturday'))
'Saturday to Tuesday'
And, per #user470379 and optional:
>>> week_days_to_string(('Monday, 'Wednesday', 'Thursday', 'Friday'))
'Monday, Wednesday to Friday'
I would approach this problem by:
Creating a dict mapping day names to their sequential index
Converting my input day names to their sequential indices
Looking at the resulting input indices and asking if they are sequential
Here's how you can do that, using calendar.day_name, range and some for comprehensions:
day_indexes = {name:i for i, name in enumerate(calendar.day_name)}
def weekdays_consecutive(days):
indexes = [day_indexes[d] for d in days]
expected = range(indexes[0], indexes[-1] + 1)
return indexes == expected
A few other options, depending on what you need:
If you need Python < 2.7, instead of the dict comprehension, you can use:
day_indexes = dict((name, i) for i, name in enumerate(calendar.day_name))
If you don't want to allow Saturday and Sunday, just trim off the last two days:
day_indexes = ... calendar.day_name[:-2] ...
If you need to wrap around after Sunday, it's probably easiest to just check that each item is one more than the previous item, but working in modulo 7:
def weekdays_consecutive(days):
indexes = [day_indexes[d] for d in days]
return all(indexes[i + 1] % 7 == (indexes[i] + 1) % 7
for i in range(len(indexes) - 1))
Update: For the extended problem, I would still stick with they day-to-index dict, but instead I would:
Find all the indexes where a run of sequential days stops
Wrap the days around if necessary to get the longest possible sequence of days
Group the days into their sequential spans
Here's code to do this:
def weekdays_to_string(days):
# convert days to indexes
day_indexes = {name:i for i, name in enumerate(calendar.day_name)}
indexes = [day_indexes[d] for d in days]
# find the places where sequential days end
ends = [i + 1
for i in range(len(indexes))
if (indexes[(i + 1) % len(indexes)]) % 7 !=
(indexes[(i) % len(indexes)] + 1) % 7]
# wrap the days if necessary to get longest possible sequences
split = ends[-1]
if split != len(days):
days = days[split:] + days[:split]
ends = [len(days) - split + end for end in ends]
# group the days in sequential spans
spans = [days[begin:end] for begin, end in zip([0] + ends, ends)]
# format as requested, with "to", "and", commas, etc.
words = []
for span in spans:
if len(span) < 3:
words.extend(span)
else:
words.append("%s to %s" % (span[0], span[-1]))
if len(days) == 1:
return words[0]
elif len(days) == 2:
return "%s and %s" % tuple(words)
else:
return ", ".join(words)
You might also try the following instead of that last if/elif/else block to get an "and" between the last two items and commas between everything else:
if len(words) == 1:
return words[0]
else:
return "%s and %s" % (", ".join(words[:-1]), words[-1])
That's a little different from the spec, but prettier in my eyes.
def weekdays_consecutive(inp):
days = { 'Monday': 0,
'Tuesday': 1,
'Wednesday': 2,
'Thursday': 3,
'Friday': 4 }
return [days[x] for x in inp] == range(days[inp[0]], days[inp[-1]] + 1)
As you have already checked for other cases, I think this will be good enough.
Here's my complete solution, you can use it however you want; (the code is being put into the public domain, but I won't accept any liability if anything happens to you or your computer as a consequence of using it and there's no warranty yadda yadda ya).
week_days = {
'monday':0,
'tuesday':1,
'wednesday':2,
'thursday':3,
'friday':4,
'saturday':5,
'sunday':6
}
week_days_reverse = dict(zip(week_days.values(), week_days.keys()))
def days_str_to_int(days):
'''
Converts a list of days into a list of day numbers.
It is case ignorant.
['Monday', 'tuesday'] -> [0, 1]
'''
return map(lambda day: week_days[day.lower()], days)
def day_int_to_str(day):
'''
Converts a day number into a string.
0 -> 'Monday' etc
'''
return week_days_reverse[day].capitalize()
def consecutive(days):
'''
Returns the number of consecutive days after the first given a sequence of
day numbers.
[0, 1, 2, 5] -> 2
[6, 0, 1] -> 2
'''
j = days[0]
n = 0
for i in days[1:]:
j = (j + 1) % 7
if j != i:
break
n += 1
return n
def days_to_ranges(days):
'''
Turns a sequence of day numbers into a list of ranges.
The days can be in any order
(n, m) means n to m
(n,) means just n
[0, 1, 2] -> [(0, 2)]
[0, 1, 2, 4, 6] -> [(0, 2), (4,), (6,)]
'''
days = sorted(set(days))
while days:
n = consecutive(days)
if n == 0:
yield (days[0],)
else:
assert n < 7
yield days[0], days[n]
days = days[n+1:]
def wrap_ranges(ranges):
'''
Given a list of ranges in sequential order, this function will modify it in
place if the first and last range can be put together.
[(0, 3), (4,), (6,)] -> [(6, 3), (4,)]
'''
if len(ranges) > 1:
if ranges[0][0] == 0 and ranges[-1][-1] == 6:
ranges[0] = ranges[-1][0], ranges[0][-1]
del ranges[-1]
def range_to_str(r):
'''
Converts a single range into a string.
(0, 2) -> "Monday to Wednesday"
'''
if len(r) == 1:
return day_int_to_str(r[0])
if r[1] == (r[0] + 1) % 7:
return day_int_to_str(r[0]) + ', ' + day_int_to_str(r[1])
return day_int_to_str(r[0]) + ' to ' + day_int_to_str(r[1])
def ranges_to_str(ranges):
'''
Converts a list of ranges into a string.
[(0, 2), (4, 5)] -> "Monday to Wednesday, Friday, Saturday"
'''
if len(ranges) == 1 and ranges[0] == (0, 6):
return 'all week'
return ', '.join(map(range_to_str, ranges))
def week_days_to_string(days):
'''
Converts a list of days in string form to a stringed list of ranges.
['saturday', 'monday', 'wednesday', 'friday', 'sunday'] ->
'Friday to Monday, Wednesday'
'''
ranges = list(days_to_ranges(days_str_to_int(days)))
wrap_ranges(ranges)
return ranges_to_str(ranges)
Features:
It supports more than one range,
You can enter in the days in any order,
It will wrap around,
Add comments if you find any problems and I'll do my best to fix them.
You would have to check the first date given, then have a list with all of the weekdays in it, check if the next given day is at the next index in the list, and repeat.
This can easily be done with a few loops, assuming the given days are in order.
I didn't test I must say.
def test(days):
days = list(days)
if len(days) == 1:
return days[0]
elif len(days) == 2:
return ' to '.join(days)
else:
return ''.join(days[:1] + [' to ' + days[-1]])
import itertools
#probably a better way to obtain this like with the datetime library
WEEKDAYS = (('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'))
def weekdays_consecutive(days):
#assumes that days only contains valid weekdays
if len(days) == 0:
return True #or False?
iter = itertools.cycle(WEEKDAYS)
while iter.next() != days[0]: pass
for day in days[1:]:
if day != iter.next(): return False
return True
#...
>>> weekdays_consecutive(('Friday', 'Monday'))
True
>>> weekdays_consecutive(('Friday', 'Monday', 'Tuesday'))
True
>>> weekdays_consecutive(('Friday', 'Monday', 'Tuesday', 'Thursday'))
False
This would either take some intricate case-by-case logic, or a hard-coded storage of all days sequentially. I'd prefer the latter.
def weekdays_consecutive(x):
allDays = { 'Monday':1, 'Tuesday':2, 'Wednesday':3, 'Thursday':4, 'Friday':5, 'Saturday' : 6, 'Sunday' : 7}
mostRecent = x[0]
for i in x[1:]:
if allDays[i] % 7 != allDays[mostRecent] % 7 + 1: return False
mostRecent = i
return True
And this can sort the input : x.sort(lambda x, y: allDays[x] - allDays[y]). I don't know which function you'd prefer to use it in
>>>x = ['Tuesday', 'Thursday', 'Monday', 'Friday']
>>>x.sort(lambda x, y: allDays[x] - allDays[y])
>>>x
['Monday', 'Tuesday', 'Thursday', 'Friday']
This relies on no non-days being present. I imagine you'd want to deal with this in the weekdays_to_string function rather than here in weekdays_consecutive.
i also think you want to change the first case of your other function to 'and' instead of 'to' and add case for single-day inputs.
EDIT: had a pretty dumb mistake i just fixed, should work now!

Categories