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!
Related
Background
We have a family tradition where my and my siblings' Christmas presents are identified by a code that can be solved using only numbers related to us. For example, the code could be birth month * age + graduation year (This is a simple one). If the numbers were 8 * 22 + 2020 = 2196, the number 2196 would be written on all my Christmas presents.
I've already created a Python class that solves the code with certain constraints, but I'm wondering if it's possible to do it recursively.
Current Code
The first function returns a result set for all possible combinations of numbers and operations that produce a value in target_values
#Master algorithm (Get the result set of all combinations of numbers and cartesian products of operations that reach a target_value, using only the number_of_numbers_in_solution)
#Example: sibling1.results[1] = [(3, 22, 4), (<built-in function add>, <built-in function add>), 29]. This means that 3 + 22 + 4 = 29, and 29 is in target_values
import operator
from itertools import product
from itertools import combinations
NUMBER_OF_OPERATIONS_IN_SOLUTION = 2 #Total numbers involved is this plus 1
NUMBER_OF_NUMBERS_IN_SOLUTION = NUMBER_OF_OPERATIONS_IN_SOLUTION + 1
TARGET_VALUES = {22,27,29,38,39}
def getresults( list ):
#Add the cartesian product of all possible operations to a variable ops
ops = []
opslist = [operator.add, operator.sub, operator.mul, operator.truediv]
for val in product(opslist, repeat=NUMBER_OF_OPERATIONS_IN_SOLUTION):
ops.append(val)
#Get the result set of all combinations of numbers and cartesian products of operations that reach a target_value
results = []
for x in combinations(list, NUMBER_OF_NUMBERS_IN_SOLUTION):
for y in ops:
result = 0
for z in range(len(y)):
#On the first iteration, do the operation on the first two numbers (x[z] and x[z+1])
if (z == 0):
#print(y[z], x[z], x[z+1])
result = y[z](x[z], x[z+1])
#For all other iterations, do the operation on the current result and x[z+1])
else:
#print(y[z], result, x[z+1])
result = y[z](result, x[z+1])
if result in TARGET_VALUES:
results.append([x, y, result])
#print (x, y)
print(len(results))
return results
Then a class that takes in personal parameters for each person and gets the result set
def getalpha( str, inverse ):
"Converts string to alphanumeric array of chars"
array = []
for i in range(0, len(str)):
alpha = ord(str[i]) - 96
if inverse:
array.append(27 - alpha)
else:
array.append(alpha)
return array;
class Person:
def __init__(self, name, middlename, birthmonth, birthday, birthyear, age, orderofbirth, gradyear, state, zip, workzip, cityfirst3):
#final list
self.listofnums = []
self.listofnums.extend((birthmonth, birthday, birthyear, birthyear - 1900, age, orderofbirth, gradyear, gradyear - 2000, zip, workzip))
self.listofnums.extend(getalpha(cityfirst3, False))
self.results = getresults(self.listofnums)
Finally, a "solve code" method that takes from the result sets and finds any possible combinations that produce the full list of target_values.
#Compares the values of two sets
def compare(l1, l2):
result = all(map(lambda x, y: x == y, l1, l2))
return result and len(l1) == len(l2)
#Check every result in sibling2 with a different result target_value and equal operation sets
def comparetwosiblings(current_values, sibling1, sibling2, a, b):
if sibling2.results[b][2] not in current_values and compare(sibling1.results[a][1], sibling2.results[b][1]):
okay = True
#If the indexes aren't alphanumeric, ensure they're the same before adding to new result set
for c in range(0, NUMBER_OF_NUMBERS_IN_SOLUTION):
indexintersection = set([index for index, value in enumerate(sibling1.listofnums) if value == sibling1.results[a][0][c]]) & set([index for index, value in enumerate(sibling2.listofnums) if value == sibling2.results[b][0][c]])
if len(indexintersection) > 0:
okay = True
else:
okay = False
break
else:
okay = False
return okay
#For every result, we start by adding the result number to the current_values list for sibling1, then cycle through each person and see if a matching operator list leads to a different result number. (Matching indices as well)
#If there's a result set for everyone that leads to five different numbers in the code, the values will be added to the newresult set
def solvecode( sibling1, sibling2, sibling3, sibling4, sibling5 ):
newresults = []
current_values = []
#For every result in sibling1
for a in range(len(sibling1.results)):
current_values = []
current_values.append(sibling1.results[a][2])
for b in range(len(sibling2.results)):
if comparetwosiblings(current_values, sibling1, sibling2, a, b):
current_values.append(sibling2.results[b][2])
for c in range(len(sibling3.results)):
if comparetwosiblings(current_values, sibling1, sibling3, a, c):
current_values.append(sibling3.results[c][2])
for d in range(len(sibling4.results)):
if comparetwosiblings(current_values, sibling1, sibling4, a, d):
current_values.append(sibling4.results[d][2])
for e in range(len(sibling5.results)):
if comparetwosiblings(current_values, sibling1, sibling5, a, e):
newresults.append([sibling1.results[a][0], sibling2.results[b][0], sibling3.results[c][0], sibling4.results[d][0], sibling5.results[e][0], sibling1.results[a][1]])
current_values.remove(sibling4.results[d][2])
current_values.remove(sibling3.results[c][2])
current_values.remove(sibling2.results[b][2])
print(len(newresults))
print(newresults)
It's the last "solvecode" method that I'm wondering if I can optimize and make into a recursive algorithm. In some cases it can be helpful to add or remove a sibling, which would look nice recursively (My mom sometimes makes a mistake with one sibling, or we get a new brother/sister-in-law)
Thank you for any and all help! I hope you at least get a laugh out of my weird family tradition.
Edit: In case you want to test the algorithm, here's an example group of siblings that result in exactly one correct solution
#ALL PERSONAL INFO CHANGED FOR STACKOVERFLOW
sibling1 = Person("sibling1", "horatio", 7, 8, 1998, 22, 5, 2020, "ma", 11111, 11111, "red")
sibling2 = Person("sibling2", "liem", 2, 21, 1995, 25, 4, 2018, "ma", 11111, 11111, "pho")
sibling3 = Person("sibling3", "kyle", 4, 21, 1993, 26, 3, 2016, "ma", 11111, 11111, "okl")
sibling4 = Person("sibling4", "jamal", 4, 7, 1991, 29, 2, 2014, "ma", 11111, 11111, "pla")
sibling5 = Person("sibling5", "roberto", 9, 23, 1990, 30, 1, 2012, "ma", 11111, 11111, "boe")
I just spent a while improving the code. Few things I need to mention:
It's not good practice to use python keywords(like list, str and zip) as variables, it will give you problems and it makes it harder to debug.
I feel like you should use the permutation function as combination gives unordered pairs while permutation gives ordered pairs which are more in number and will give more results. For example, for the sibling info you gave combination gives only 1 solution through solvecode() while permutation gives 12.
Because you are working with operators, there can be more cases with brackets. To solve that problem and to make the getresults() function a bit more optimized, I suggest you explore the reverse polish notation. Computerphile has an excellent video on it.
You don't need a compare function. list1==list2 works.
Here's the optimized code:
import operator
from itertools import product
from itertools import permutations
NUMBER_OF_OPERATIONS_IN_SOLUTION = 2 #Total numbers involved is this plus 1
NUMBER_OF_NUMBERS_IN_SOLUTION = NUMBER_OF_OPERATIONS_IN_SOLUTION + 1
TARGET_VALUES = {22,27,29,38,39}
def getresults(listofnums):
#Add the cartesian product of all possible operations to a variable ops
ops = []
opslist = [operator.add, operator.sub, operator.mul, operator.truediv]
for val in product(opslist, repeat=NUMBER_OF_OPERATIONS_IN_SOLUTION):
ops.append(val)
#Get the result set of all combinations of numbers and cartesian products of operations that reach a target_value
results = []
for x in permutations(listofnums, NUMBER_OF_NUMBERS_IN_SOLUTION):
for y in ops:
result = y[0](x[0], x[1])
if NUMBER_OF_OPERATIONS_IN_SOLUTION>1:
for z in range(1, len(y)):
result = y[z](result, x[z+1])
if result in TARGET_VALUES:
results.append([x, y, result])
return results
def getalpha(string, inverse):
"Converts string to alphanumeric array of chars"
array = []
for i in range(0, len(string)):
alpha = ord(string[i]) - 96
array.append(27-alpha if inverse else alpha)
return array
class Person:
def __init__(self, name, middlename, birthmonth, birthday, birthyear, age, orderofbirth, gradyear, state, zipcode, workzip, cityfirst3):
#final list
self.listofnums = [birthmonth, birthday, birthyear, birthyear - 1900, age, orderofbirth, gradyear, gradyear - 2000, zipcode, workzip]
self.listofnums.extend(getalpha(cityfirst3, False))
self.results = getresults(self.listofnums)
#Check every result in sibling2 with a different result target_value and equal operation sets
def comparetwosiblings(current_values, sibling1, sibling2, a, b):
if sibling2.results[b][2] not in current_values and sibling1.results[a][1]==sibling2.results[b][1]:
okay = True
#If the indexes aren't alphanumeric, ensure they're the same before adding to new result set
for c in range(0, NUMBER_OF_NUMBERS_IN_SOLUTION):
indexintersection = set([index for index, value in enumerate(sibling1.listofnums) if value == sibling1.results[a][0][c]]) & set([index for index, value in enumerate(sibling2.listofnums) if value == sibling2.results[b][0][c]])
if len(indexintersection) > 0:
okay = True
else:
okay = False
break
else:
okay = False
return okay
And now, the million dollar function or should i say two functions:
# var contains the loop variables a-e, depth keeps track of sibling number
def rec(arg, var, current_values, newresults, depth):
for i in range(len(arg[depth].results)):
if comparetwosiblings(current_values, arg[0], arg[depth], var[0], i):
if depth<len(arg)-1:
current_values.append(arg[depth].results[i][2])
rec(arg, var[:depth]+[i], current_values, newresults, depth+1)
current_values.remove(arg[depth].results[i][2])
else:
var.extend([i])
newresults.append([arg[0].results[var[0]][0], arg[1].results[var[1]][0], arg[2].results[var[2]][0], arg[3].results[var[3]][0], arg[4].results[var[4]][0], arg[0].results[var[0]][1]])
def solvecode(*arg):
newresults = []
for a in range(len(arg[0].results)):
current_values = [arg[0].results[a][2]]
rec(arg, var=[a], current_values=current_values, newresults=newresults, depth=1)
print(len(newresults))
print(newresults)
There is a need for two functions as the first one is the recursive one and the second one is like a packaging. I've also fulfilled your second wish, that was being able to have variable number of siblings' data that can be input into the new solvecode function. I've checked the new functions and they work together exactly like the original solvecode function. Something to be noted is that there is no significant difference in the version's runtimes although the second one has 8 less lines of code. Hope this helped. lmao took me 3 hours.
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.
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.
I am working on a project and I have a list of lists containing names, monetary values, etc. I am running into trouble trying to update the individual sub-lists within the primary list when a user enters a value.
For example, my list contains 4 rows (constant) and an in-determinant number of columns based on user entries. I am including the whole program just for reference in case there are questions about what it all looks like:
spacing = '- ' * 45 # formatting for DONOR header
data_list = [['NAMES', 'DONATION AMOUNT', 'Number of Gifts', 'Avg Gifts'],
['Rudolph S', 1500, 3, 0],
['Josef M', 250, 5, 0],
['Joye A', 5000, 2, None],
['Joni M', 2750, 1, None],
['Rachelle L', 750, 3, None],
['Vena U', 1000, 7, None],
['Efrain L', 10000, 1, None],
['Mee H', 15000, 2, None],
['Tanya E', 50000, 1, None],
['Garrett H', 800, 2, None]]
def addtolist():
"""Method for sending 'Thank You' messages to Donors, using names *"""
while True:
print("Enter the name of the person you are writing to (or enter 'list' to see a list of names or Q to quit) ")
fname_prompt = input("First Name: ").strip().capitalize()
if fname_prompt.upper() == "Q":
break
elif fname_prompt.lower() == "list":
if len(data_list) - 1 % 2 != 0:
for i in range(0, int(len(data_list) - 1 / 2)):
cut_off = int((len(data_list)) / 2)
if i == 0:
print(spacing)
print('{:>44s}'.format(str(data_list[i][0])))
print(spacing)
elif cut_off + i >= len(data_list):
continue
else:
print('{:>30s}'.format(data_list[i][0]), '{:>35s}'.format(data_list[cut_off + i][0]))
else:
if i == 0:
print(spacing)
print('{:>20s}'.format(str(data_list[i])))
print(spacing)
else:
print('{:>15s}'.format(data_list[i][0]), '{:>30s}'.format(data_list[cut_off + i][0]))
else:
lname_prompt = input("Last Name: ").strip().capitalize()
if lname_prompt.upper() == "Q":
break
elif lname_prompt.lower() == "list":
if len(data_list) - 1 % 2 != 0:
for i in range(0, int(len(data_list) - 1 / 2)):
cut_off = int((len(data_list)) / 2)
if i == 0:
print(spacing)
print('{:>44s}'.format(str(data_list[i][0])))
print(spacing)
elif cut_off + i >= len(data_list):
continue
else:
print('{:>30s}'.format(data_list[i][0]), '{:>35s}'.format(data_list[cut_off + i][0]))
else:
if i == 0: # for each item in list / 2 (5 x)
print(spacing)
print('{:>20s}'.format(str(data_list[i][0])))
print(spacing)
else:
print('{:>15s}'.format(data_list[i][0]), '{:>30s}'.format(data_list[cut_off + i][0]))
else:
full_name = fname_prompt + " " + lname_prompt
if full_name != "List List" or full_name != "list ":
name_found = False
for vals in data_list:
if full_name in vals:
name_found = True
else:
name_found = False
if name_found is False:
add_name = input("That name is not in the Donor list. Do you want to add it to the list? ").upper()
if add_name == "Y":
data_list.append([full_name])
if len(data_list) - 1 % 2 != 0:
for i in range(0, int(len(data_list) - (len(data_list) - 2) / 2)):
cut_off = int((len(data_list)) / 2)
if i == 0:
print(spacing)
print('{:>44s}'.format(str(data_list[i][0])))
print(spacing)
elif cut_off + i >= len(data_list):
print('{:>30s}'.format(data_list[i][0]))
continue
else:
print('{:>30s}'.format(data_list[i][0]), '{:>35s}'.format(data_list[cut_off + i][0]))
else:
if i == 0: # for each item in list / 2 (5 x)
print(spacing)
print('{:>20s}'.format(str(data_list[i][0])))
print(spacing)
else:
print('{:>15s}'.format(data_list[i][0]), '{:>30s}'.format(data_list[cut_off + i][0]))
donation_amt = int(input("Enter in the donation amount from Donor {0}: $".format(full_name)))
print('{0} has donated ${1}'.format(full_name, donation_amt))
data_list.append(donation_amt) # difficulty HERE
print(data_list)
The main line(s) I am having difficult with are at the very end with a comment "difficult HERE".
data_list.append(donation_amt) # difficulty HERE
I am trying to work this so that when the user enters a new name and new donation amount (or if they simply select an existing name and attach a donation amount to it), that the program can either append/insert the monetary value to the associated sublist (the name it is attached to). the way I have it set up now it is just appending the numerical amount onto the end of the larger list but I have been unsuccessful in attaching the value to the sublist... Has anyone done anything like this before?
Two-dimensional lists in Python are merely lists of lists. Thus, each element of data_list is, itself, a list. Here is an example of accessing an element, the first element below your row of headers (thus, index 1):
>>> first_entry = data_list[1]
>>> first_entry
['Rudolph S', 1500, 3, 0]
Since data_list[1] (which we have stored as a variable called first_entry) is also a list we can access, we could access the fourth element (at index 3, since lists begin indexing at 0) of the first entry as follows:
>>> first_entry = data_list[1]
>>> fourth_element = first_entry[3]
>>> fourth_element
0
Or, more succinctly:
>>> data_list[1][3]
0
So, to begin to answer your question, if your goal was to update the donation amount of "Joye A", you would use data_list[3][1] = donation_amt. This is because Joye's entry is at index 3 of the main list and donations are recorded at index 1 of her sub list.
Unfortunately, this doesn't really solve your problem, since you want to take an arbitrary name for which to either create a new entry or update an existing entry. The real answer here is that you are using the wrong data structure. For the sake of educational value, though, I'll go ahead and describe how you could do this with your existing structure.
Using your matrix
First you would need to determine if the name already exists. As a result, it would be best to create an extra list which contains only the first column, a 1-d list. You could do this in any number of ways. I'll show it as a list comprehension:
>>> names_only = [e[0] for e in data_list]
>>> names_only
['NAMES', 'Rudolph S', 'Josef M', ...]
I won't explain this here, but there are plenty of threads explaining how list comprehensions work for any readers who aren't aware.
First, you check if the name already exists in the matrix:
>>> 'Josef M' in names_only
True
If so, you now need to find the index of the name you're looking for. Lists in Python have an index function:
>>> idx = names_only.index('Joesf M')
>>> idx
2
You now update his donation amount as described above:
>>> data_list[idx][1] = donation_amt
Now for if he wasn't in the matrix, we want to make a whole new row. Lets imagine we're processing a user named 'bob'. He's not yet in the matrix. Here you use your append function:
>>> data_list.append([bob, donation_amt, 1, None])
Where 1 and None can be replaced with whatever your default values are. Putting it all together as a function:
>>> def update_or_create(name, amt):
... names = [e[0] for e in data_list]
... if name in names:
... idx = names.index(name)
... data_list[idx][1] = amt
... else:
... data_list.append([name, amt, 1, None])
Should do what your asking for.
Finally
It would be better to use a different structure for this. I would propose a dict structure like:
new_structure = {NAME: {'donation': DONATION_AMT, 'num_gifts': NUM_GIFTS, 'avg_amt': AVERAGE_DONATION},...}
Without going into too much detail, following this format would allow the following function to perform the same task:
>>> def update_or_create(name, amt):
... if name in new_structure:
... new_structure[name]['donation'] = amt
... else:
... new_structure.update({name: {'donation': amt, 'num_gifts': 1, 'avg_amt': None}})
Much nicer.
3 dimensional lists have the ability to add on to either the main list or one of the sub-lists.
data_list.append(donation_amt)
would append to the main list, which would mean that if you had a list like yours, it would add to the end.
[['NAMES', 'DONATION AMOUNT', 'Number of Gifts', 'Avg Gifts'],
['Rudolph S', 1500, 3, 0],
['Josef M', 250, 5, 0],
['Joye A', 5000, 2, None],
['Joni M', 2750, 1, None],
['Rachelle L', 750, 3, None],
['Vena U', 1000, 7, None],
['Efrain L', 10000, 1, None],
['Mee H', 15000, 2, None],
['Tanya E', 50000, 1, None],
['Garrett H', 800, 2, None],
[donation_amt]]
If you wanted to add a donation amount to a specific index, use
data_list[index].append(donation_amt).
Please let me know if this doesn't work or if you want a better explanation, then it may be a different issue.
It seems you have 4 static columns and an indeterminate number of rows.
Have you considered using a list of dictionaries, something like a json doc?
data_list = [{
'NAME':'Rudolph S',
'DONATION AMOUNT' : 1500,
'Number of Gifts' : 3,
'Avg Gifts' : 0
},{
'NAME':'Josef M',
'DONATION AMOUNT' : 250,
'Number of Gifts' : 5,
'Avg Gifts' : None
}]
And so on. I think you might have an easier time working with the data if you can reference the individual keys and update their values, instead of working with lists and index values.
In order to append donation_amt properly to the correct sublist, you need to first determine the index in the list where the donor belongs. Once you find the index, you can then append the donation amount to the sublist at that index. To achieve this, replace:
data_list.append(donation_amt) # difficulty HERE
with:
# Determine index where the donor belongs
idx = -1
for item in range(0, len(data_list)):
if data_list[item][0] == full_name:
idx = item
break
# Append to the sublist
data_list[idx].append(donation_amt)
I tried this out and it works for me.
I built a Date Class that has a tomorrow method, add_n_days method, equals method, before and after methods, and a difference method. What I'm trying to figure out is how to use my difference method to create a new method where I figure out the day of the week the Date object falls on.
# A class to represent calendar dates
#
class Date:
""" A class that stores and manipulates dates,
represented by a day, month, and year.
"""
# The constructor for the Date class.
def __init__(self, new_month, new_day, new_year):
""" The constructor for objects of type Date. """
self.month = new_month
self.day = new_day
self.year = new_year
# The function for the Date class that returns a Date
# object in a string representation.
def __repr__(self):
""" This method returns a string representation for the
object of type Date that it is called on (named self).
** Note that this _can_ be called explicitly, but
it more often is used implicitly via printing or evaluating.
"""
s = '%02d/%02d/%04d' % (self.month, self.day, self.year)
return s
def is_leap_year(self):
""" Returns True if the called object is
in a leap year. Otherwise, returns False.
"""
if self.year % 400 == 0:
return True
elif self.year % 100 == 0:
return False
elif self.year % 4 == 0:
return True
return False
def copy(self):
""" Returns a new object with the same month, day, year
as the called object (self).
"""
new_date = Date(self.month, self.day, self.year)
return new_date
def is_before(self,d2):
if self.year<d2.year:
return true
if self.month<d1.month and self.year==d2.year:
return True
if self.day < d2.day and d2.month == self.month and self.year ==d2.year:
return true
return False
def is_after(self,d2):
return d2.isbefore(self)
def diff(self,d2):
dcopy=self.copy()
difference=0
if dcopy.isbefore(d2) == True:
while dcopy.isBefore(d)==true:
dcopy.tomorrow()
difference-=1
else:
while dcopy.isafter(d2):
dcopy.yesterday()
difference +=1
return difference
def diff(self,d2):
dcopy=self.copy()
difference=0
while dcopy.isbefore(d2):
dcopy.tomorrow()
difference-=1
while dcopy.isafter(d2):
dcopy.yesterday()
difference+=1
The way I am thinking of implementing it, but don't really understand how to establish it concretely is having a known date, a list with the Weekdays, using a modulus operator when trying to figure out the day of the weeks - %=7, and the diff (difference) method from above.
What is my best way of going about this problem, I would appreciate any help, explanation - I'm new to python and OOP.
Thanks.
day_of_week_names = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
'Friday', 'Saturday', 'Sunday']
output in shell, I think should look like this
>>> d = Date(4, 4, 2016)
>>> d.day_of_week()
'Monday'
>>> Date(1, 1, 2100).day_of_week()
'Friday'
>>> Date(7, 4, 1776).day_of_week()
'Thursday'
If I told you that 1/1/2000 was a Monday and asked you what day of the week was 50 days later, it would be easy to figure out.
It's simply 50 % 7, which is 1. Therefore, we know that the day of the week we are looking at is one day past the day we know.
So 50 days later would be a Tuesday, or 1 day past Monday.
Since you can find out how many days are between any given date and a so-called anchor date (like the 1/1/2000 example I gave above), you can calculate the day of the week for any date.
The following pseudo code, cited from wikipedia should help you get started. It depends on integer division, so remember to use // in python to get 'c-like' integer division.
def day_of_week(self):
d, m, y = self.day, self.month, self.year
day_of_week_names = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
t = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]
if m < 3:
y -= 1
index = (y + y//4 - y//100 + y//400 + t[m-1] + d) % 7
return day_of_week_names[index]