I'm Looking for ways to make this lexicographical code faster - python-3.x
I've been working on code to calculate the distance between 33 3D points and calculate the shortest route is between them. The initial code took in all 33 points and paired them consecutively and calculated the distances between the pairs using math.sqrt and sum them all up to get a final distance.
My problem is that with the sheer number of permutations of a list with 33 points (33 factorial!) the code is going to need to be at its absolute best to find the answer within a human lifetime (assuming I can use as many CPUs as I can get my hands on to increase the sheer computational power).
I've designed a simple web server to hand out an integer and convert it to a list and have the code perform a set number of lexicographical permutations from that point and send back the resulting shortest distance of that block. This part is fine but I have concerns over the code that does the distance calculations
I've put together a test version of my code so I could change things and see if it made the execution time faster or slower. This code starts at the beginning of the permutation list (0 to 32) in order and performs 50 million lexicographical iterations on it, checking the distance of the points at every iteration. the code is detailed below.
import json
import datetime
import math
def next_lexicographic_permutation(x):
i = len(x) - 2
while i >= 0:
if x[i] < x[i+1]:
break
else:
i -= 1
if i < 0:
return False
j = len(x) - 1
while j > i:
if x[j] > x[i]:
break
else:
j-= 1
x[i], x[j] = x[j], x[i]
reverse(x, i + 1)
return x
def reverse(arr, i):
if i > len(arr) - 1:
return
j = len(arr) - 1
while i < j:
arr[i], arr[j] = arr[j], arr[i]
i += 1
j -= 1
# ip for initial permutation
ip = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32]
lookup = '{"0":{"name":"van Maanen\'s Star","x":-6.3125,"y":-11.6875,"z":-4.125},\
"1":{"name":"Wolf 124","x":-7.25,"y":-27.1562,"z":-19.0938},\
"2":{"name":"Midgcut","x":-14.625,"y":10.3438,"z":13.1562},\
"3":{"name":"PSPF-LF 2","x":-4.40625,"y":-17.1562,"z":-15.3438},\
"4":{"name":"Wolf 629","x":-4.0625,"y":7.6875,"z":20.0938},\
"5":{"name":"LHS 3531","x":1.4375,"y":-11.1875,"z":16.7812},\
"6":{"name":"Stein 2051","x":-9.46875,"y":2.4375,"z":-15.375},\
"7":{"name":"Wolf 25","x":-11.0625,"y":-20.4688,"z":-7.125},\
"8":{"name":"Wolf 1481","x":5.1875,"y":13.375,"z":13.5625},\
"9":{"name":"Wolf 562","x":1.46875,"y":12.8438,"z":15.5625},\
"10":{"name":"LP 532-81","x":-1.5625,"y":-27.375,"z":-32.3125},\
"11":{"name":"LP 525-39","x":-19.7188,"y":-31.125,"z":-9.09375},\
"12":{"name":"LP 804-27","x":3.3125,"y":17.8438,"z":43.2812},\
"13":{"name":"Ross 671","x":-17.5312,"y":-13.8438,"z":0.625},\
"14":{"name":"LHS 340","x":20.4688,"y":8.25,"z":12.5},\
"15":{"name":"Haghole","x":-5.875,"y":0.90625,"z":23.8438},\
"16":{"name":"Trepin","x":26.375,"y":10.5625,"z":9.78125},\
"17":{"name":"Kokary","x":3.5,"y":-10.3125,"z":-11.4375},\
"18":{"name":"Akkadia","x":-1.75,"y":-33.9062,"z":-32.9688},\
"19":{"name":"Hill Pa Hsi","x":29.4688,"y":-1.6875,"z":25.375},\
"20":{"name":"Luyten 145-141","x":13.4375,"y":-0.8125,"z":6.65625},\
"21":{"name":"WISE 0855-0714","x":6.53125,"y":-2.15625,"z":2.03125},\
"22":{"name":"Alpha Centauri","x":3.03125,"y":-0.09375,"z":3.15625},\
"23":{"name":"LHS 450","x":-12.4062,"y":7.8125,"z":-1.875},\
"24":{"name":"LP 245-10","x":-18.9688,"y":-13.875,"z":-24.2812},\
"25":{"name":"Epsilon Indi","x":3.125,"y":-8.875,"z":7.125},\
"26":{"name":"Barnard\'s Star","x":-3.03125,"y":1.375,"z":4.9375},\
"27":{"name":"Epsilon Eridani","x":1.9375,"y":-7.75,"z":-6.84375},\
"28":{"name":"Narenses","x":-1.15625,"y":-11.0312,"z":21.875},\
"29":{"name":"Wolf 359","x":3.875,"y":6.46875,"z":-1.90625},\
"30":{"name":"LAWD 26","x":20.9062,"y":-7.5,"z":3.75},\
"31":{"name":"Avik","x":13.9688,"y":-4.59375,"z":-6.0},\
"32":{"name":"George Pantazis","x":-12.0938,"y":-16.0,"z":-14.2188}}'
lookup = json.loads(lookup)
lowest_total = 9999
# create 2D array for the distances and called it b to keep code looking clean.
b = [[0 for i in range(33)] for j in range(33)]
for x in range(33):
for y in range(33):
if x == y:
continue
else:
b[x][y] = math.sqrt(((lookup[str(x)]["x"] - lookup[str(y)]['x']) ** 2) + ((lookup[str(x)]['y'] - lookup[str(y)]['y']) ** 2) + ((lookup[str(x)]['z'] - lookup[str(y)]['z']) ** 2))
# begin timer
start_date = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
start = datetime.datetime.now()
print("[{}] Start".format(start_date))
# main iteration loop
for x in range(50_000_000):
distance = b[ip[0]][ip[1]] + b[ip[1]][ip[2]] + b[ip[2]][ip[3]] +\
b[ip[3]][ip[4]] + b[ip[4]][ip[5]] + b[ip[5]][ip[6]] +\
b[ip[6]][ip[7]] + b[ip[7]][ip[8]] + b[ip[8]][ip[9]] +\
b[ip[9]][ip[10]] + b[ip[10]][ip[11]] + b[ip[11]][ip[12]] +\
b[ip[12]][ip[13]] + b[ip[13]][ip[14]] + b[ip[14]][ip[15]] +\
b[ip[15]][ip[16]] + b[ip[16]][ip[17]] + b[ip[17]][ip[18]] +\
b[ip[18]][ip[19]] + b[ip[19]][ip[20]] + b[ip[20]][ip[21]] +\
b[ip[21]][ip[22]] + b[ip[22]][ip[23]] + b[ip[23]][ip[24]] +\
b[ip[24]][ip[25]] + b[ip[25]][ip[26]] + b[ip[26]][ip[27]] +\
b[ip[27]][ip[28]] + b[ip[28]][ip[29]] + b[ip[29]][ip[30]] +\
b[ip[30]][ip[31]] + b[ip[31]][ip[32]]
if distance < lowest_total:
lowest_total = distance
ip = next_lexicographic_permutation(ip)
# end timer
finish_date = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
finish = datetime.datetime.now()
print("[{}] Finish".format(finish_date))
diff = finish - start
print("Time taken => {}".format(diff))
print("Lowest distance => {}".format(lowest_total))
This is the result of a lot of work to make things faster. I was initially using string look-ups to find the distance to be calculated with a dict having keys like "1-2", but very quickly found out that it was very slow, I then moved onto hashed versions of the "1-2" key and the speed increased but the fastest way I have found so far is using a 2D array and looking up the values from there.
I have also found that manually constructing the distance calculation saved time over having a for x in ranges(32): loop adding the distances up and incrementing a variable to get the total.
Another great speed up was using pypy3 instead of python3 to execute it.
This usually takes 11 seconds to complete using pypy3
running 50 million of the distance calculation on its own takes 5.2 seconds
running 50 million of the next_lexicographic_permutation function on its own takes 6 seconds
I can't think of any way to make this faster and I believe there may be optimizations to be made in the next_lexicographic_permutation function. From what I've read about this the main bottleneck seems to be the switching of positions in the array:
x[i], x[j] = x[j], x[i]
Edit : added clarification of lifetime to represent human lifetime
The brute-force approach of calculating all the distances is going to be slower than a partitioning approach. Here is a similar question for the 3D case.
Related
"Time Limit Exceeded" error for python file
I have a question about how to improve my simple Python file so that it does not exceed the time limit. My code should run in less than 2 seconds, but it takes a long time. I will be glad to know any advice about it. Code receives (n) as an integer from the user, then in n lines, I have to do the tasks. If the input is "Add" I have to add the given number and then arrange them from smallest to largest. If the input is "Ask", I have to return the asked index of added numbers. This is an example for inputs and outputs. I guess the code works well for other examples, but the only problem is time ... n = int(input()) def arrange(x): for j in range(len(x)): for i in range(len(x) - 1): if x[i] > x[i + 1]: x[i], x[i + 1] = x[i + 1], x[i] tasks=[] for i in range(n): tasks.append(list(input().split())) ref = [] for i in range(n): if tasks[i][0] == 'Add': ref.append(int(tasks[i][1])) arrange(ref) elif tasks[i][0] == 'Ask': print(ref[int(tasks[i][1]) - 1]) For the given example, I get a "Time Limit Exceeded" Error.
First-off: Reimplementing list.sort will always be slower than just using it directly. If nothing else, getting rid of the arrange function and replacing the call to it with ref.sort() would improve performance (especially because Python's sorting algorithm is roughly O(n) when the input is largely sorted already, so you'll be reducing the work from the O(n**2) of your bubble-sorting arrange to roughly O(n), not just the O(n log n) of an optimized general purpose sort). If that's not enough, note that list.sort is still theoretically O(n log n); if the list is getting large enough, that may cost more than it should. If so, take a look at the bisect module, to let you do the insertions with O(log n) lookup time (plus O(n) insertion time, but with very low constant factors) which might improve performance further. Alternatively, if Ask operations are going to be infrequent, you might not sort at all when Adding, and only sort on demand when Ask occurs (possibly using a flag to indicate if it's already sorted so you don't call sort unnecessarily). That could make a meaningfully difference, especially if the inputs typically don't interleave Adds and Asks. Lastly, in the realm of microoptimizations, you're needlessly wasting time on list copying and indexing you don't need to do, so stop doing it: tasks=[] for i in range(n): tasks.append(input().split()) # Removed list() call; str.split already returns a list ref = [] for action, value in tasks: # Don't iterate by index, iterate the raw list and unpack to useful # names; it's meaningfully faster if action == 'Add': ref.append(int(value)) ref.sort() elif action == 'Ask': print(ref[int(value) - 1])
For me it runs in less than 0,005 seconds. Are you sure that you are measuring the right thing and you don't count in the time of giving the input for example? python3 timer.py Input: 7 Add 10 Add 2 Ask 1 Ask 2 Add 5 Ask 2 Ask 3 Output: 2 10 5 10 Elapsed time: 0.0033 seconds My code: import time n = int(input('Input:\n')) def arrange(x): for j in range(len(x)): for i in range(len(x) - 1): if x[i] > x[i + 1]: x[i], x[i + 1] = x[i + 1], x[i] tasks=[] for i in range(n): tasks.append(list(input().split())) tic = time.perf_counter() ref = [] print('Output:') for i in range(n): if tasks[i][0] == 'Add': ref.append(int(tasks[i][1])) arrange(ref) elif tasks[i][0] == 'Ask': print(ref[int(tasks[i][1]) - 1]) toc = time.perf_counter() print(f"Elapsed time: {toc - tic:0.4f} seconds")
How can we calculate the time complexity for the following piece of code in BIG-OH Notation?
def exercise2(N): count=0 i = N while ( i > 0 ): for j in range(0,i): count = count + 1 i = i//2 How do we know the time complexities for both the while and for loop? Edit:Many of the users are sending me link to understand the time complexity using Big-OH analysis. I appreciate it but the only languages in CS i understand is python and all those explanations are using java and C++ which makes it hard for me to understand. If anyone could explain time complexity using python it would be great!
The inner loop (for) is running in i, and i is going down from N to 1 in log(N) steps at most. Hence, the time complexity is N + N/2 + N/4 + ... + 1 = N(1 + 1/2 + 1/4 + ... + 1/2^k) = \Theta(N). For the latter equation, you can suppose N = 2^k as we are computing the asymptotic time complexity.
Deciding if all intervals are overlapping
I'm doing a problem that n people is standing on a line and each person knows their own position and speed. I'm asked to find the minimal time to have all people go to any spot. Basically what I'm doing is finding the minimal time using binary search and have every ith person's furthest distance to go in that time in intervals. If all intervals overlap, there is a spot that everyone can go to. I have a solution to this question but the time limit exceeded for it for my bad solution to find the intervals. My current solution runs too slow and I'm hoping to get a better solution. my code: people = int(input()) peoplel = [list(map(int, input().split())) for _ in range(people)] # first item in people[i] is the position of each person, the second item is the speed of each person def good(time): return checkoverlap([[i[0] - time *i[1], i[0] + time * i[1]] for i in peoplel]) # first item,second item = the range of distance a person can go to def checkoverlap(l): for i in range(len(l) - 1): seg1 = l[i] for i1 in range(i + 1, len(l)): seg2 = l[i1] if seg2[0] <= seg1[0] <= seg2[1] or seg1[0] <= seg2[0] <= seg1[1]: continue elif seg2[0] <= seg1[1] <= seg2[1] or seg1[0] <= seg2[1] <= seg1[1]: continue return False return True (this is my first time asking a question so please inform me about anything that is wrong)
One does simply go linear A while after I finished the answer I found a simplification that removes the need for sorting and thus allows us to further reduce the complexity of finding if all the intervals are overlapping to O(N). If we look at the steps that are being done after the initial sort we can see that we are basically checking if max(lower_bounds) < min(upper_bounds): return True else: return False And since both min and max are linear without the need for sorting, we can simplify the algorithm by: Creating an array of lower bounds - one pass. Creating an array of upper bounds - one pass. Doing the comparison I mentioned above - two passes over the new arrays. All this could be done together in one one pass to further optimize(and to prevent some unnecessary memory allocation), however this is clearer for the explanation's purpose. Since the reasoning about the correctness and timing was done in the previous iteration, I will skip it this time and keep the section below since it nicely shows the thought process behind the optimization. One sort to rule them all Disclaimer: This section was obsoleted time-wise by the one above. However since it in fact allowed me to figure out the linear solution, I'm keeping it here. As the title says, sorting is a rather straightforward way of going about this. It will require a little different data structure - instead of holding every interval as (min, max) I opted for holding every interval as (min, index), (max, index). This allows me to sort these by the min and max values. What follows is a single linear pass over the sorted array. We also create a helper array of False values. These represent the fact that at the beginning all the intervals are closed. Now comes the pass over the array: Since the array is sorted, we first encounter the min of each interval. In such case, we increase the openInterval counter and a True value of the interval itself. Interval is now open - until we close the interval, the person can arrive at the party - we are within his(or her) range. We go along the array. As long as we are opening the intervals, everything is ok and if we manage to open all the intervals, we have our party destination where all the social distancing collapses. If this happens, we return True. If we close any of the intervals, we have found our party breaker who can't make it anymore. (Or we can discuss that the party breakers are those who didn't bother to arrive yet when someone has to go already). We return False. The resulting complexity is O(Nlog(N)) caused by the initial sort since the pass itself is linear in nature. This is quite a bit better than the original O(n^2) caused by the "check all intervals pairwise" approach. The code: import numpy as np import cProfile, pstats, io #random data for a speed test. Not that useful for checking the correctness though. testSize = 10000 x = np.random.randint(0, 10000, testSize) y = np.random.randint(1, 100, testSize) peopleTest = [x for x in zip(x, y)] #Just a basic example to help with the reasoning about the correctness peoplel = [(1, 2), (3, 1), (8, 1)] # first item in people[i] is the position of each person, the second item is the speed of each person def checkIntervals(people, time): a = [(x[0] - x[1] * time, idx) for idx, x in enumerate(people)] b = [(x[0] + x[1] * time, idx) for idx, x in enumerate(people)] checks = [False for x in range(len(people))] openCount = 0 intervals = [x for x in sorted(a + b, key=lambda x: x[0])] for i in intervals: if not checks[i[1]]: checks[i[1]] = True openCount += 1 if openCount == len(people): return True else: return False print(intervals) def good(time, people): return checkoverlap([[i[0] - time * i[1], i[0] + time * i[1]] for i in people]) # first item,second item = the range of distance a person can go to def checkoverlap(l): for i in range(len(l) - 1): seg1 = l[i] for i1 in range(i + 1, len(l)): seg2 = l[i1] if seg2[0] <= seg1[0] <= seg2[1] or seg1[0] <= seg2[0] <= seg1[1]: continue elif seg2[0] <= seg1[1] <= seg2[1] or seg1[0] <= seg2[1] <= seg1[1]: continue return False return True pr = cProfile.Profile() pr.enable() print(checkIntervals(peopleTest, 10000)) print(good(10000, peopleTest)) pr.disable() s = io.StringIO() sortby = "cumulative" ps = pstats.Stats(pr, stream=s).sort_stats(sortby) ps.print_stats() print(s.getvalue()) The profiling stats for the pass over test array with 10K random values: ncalls tottime percall cumtime percall filename:lineno(function) 1 0.001 0.001 8.933 8.933 (good) 1 8.925 8.925 8.926 8.926 (checkoverlap) 1 0.003 0.003 0.023 0.023 (checkIntervals) 1 0.008 0.008 0.010 0.010 {built-in method builtins.sorted}
Faster way to simulate the crunch command behaviour on Python3.8
I'm trying to simulate what the crunch command does in Linux, with the difference of yield the words instead of writing them into a file and i came up with something like this: def wordlist(chars, min, max = None): if max is None: # Means that the user want only a singular length max = min length = len(chars) for n in range(min, max + 1): indexes = [0] * n for _ in range(length ** n): # The length of all the chars to the power of the places to fill return the number of words in the wordlist for m in range(1, len(indexes) + 1): # This is the reporting system, like if indexes instead of a list is a number if indexes[-m] == length: indexes[-m] = 0 indexes[-m - 1] += 1 yield ''.join(chars[i] for i in indexes) indexes[-1] += 1 It's a bit rude and not too much readable, maybe neither too much performing. Without using any external module like itertools, has someone got a better idea? EDIT: After a bit of struggling I have improved the math behind coming up to something like this: def wordlist(chars, min, max = None): if max is None: max = min if min <= 0 or max <= 0: return base = len(chars) for n in range(min, max + 1): for m in range(base ** n): yield ''.join(chars[m // base ** (n - v - 1) % base] for v in range(n)) Anyway I measured the time taken by each of the two function and, while this new one is much more readable and pretty, the first one still faster. I still waiting for better ideas from you
split a value into values in max, min range
I want to find an efficient algorithm to divide an integer number to some value in a max, min range. There should be as less values as possible. For example: max = 7, min = 3 then 8 = 4 + 4 9 = 4 + 5 16 = 5 + 5 + 6 (not 4 + 4 + 4 + 4) EDIT To make it more clear, let take an example. Assume that you have a bunch of apples and you want to pack them into baskets. Each basket can contain 3 to 7 apples, and you want the number of baskets to be used is as small as possible. ** I mentioned that the value should be evenly divided, but that's not so important. I am more concerned about less number of baskets.
This struck me as a fun problem so I had a go at hacking out a quick solution. I think this might be an interesting starting point, it'll either give you a valid solution with as few numbers as possible, or with numbers as similar to each other as possible, all within the bounds of the range defined by the min_bound and max_bound number = int(input("Number: ")) min_bound = 3 max_bound = 7 def improve(solution): solution = list(reversed(solution)) for i, num in enumerate(solution): if i >= 2: average = sum(solution[:i]) / i if average.is_integer(): for x in range(i): solution[x] = int(average) break return solution def find_numbers(number, division, common_number): extra_number = number - common_number * division numbers_in_solution = [common_number] * division if extra_number < min_bound and \ extra_number + common_number <= max_bound: numbers_in_solution[-1] += extra_number elif extra_number < min_bound or extra_number > max_bound: return None else: numbers_in_solution.append(extra_number) solution = improve(numbers_in_solution) return solution def tst(number): try: solution = None for division in range(number//max_bound, number//min_bound + 1): # Reverse the order of this for numbers as close in value to each other as possible. if round (number / division) in range(min_bound, max_bound + 1): solution = find_numbers(number, division, round(number / division)) elif (number // division) in range(min_bound, max_bound + 1): # Rarely required but catches edge cases solution = find_numbers(number, division, number // division) if solution: print(sum(solution), solution) break except ZeroDivisionError: print("Solution is 1, your input is less than the max_bound") tst(number) for x in range(1,100): tst(x) This code is just to demonstrate an idea, I'm sure it could be tweaked for better performance.