It will make Theta(n) next calls on the iterator, and Theta(n) additions, where n is the number of items you're summing.
That's as specific as you can be for the time complexity of an algorithm that calls unknown code. If the time taken for each addition depends on n (as for example it would when summing lists, like sum(list(range(i)) for i in range(n))), then that's going to affect the overall time complexity.
It will make Theta(n) next calls on the iterator, and Theta(n) additions, where n is the number of items you're summing.
That's as specific as you can be for the time complexity of an algorithm that calls unknown code. If the time taken for each addition depends on n (as for example it would when summing lists, like sum(list(range(i)) for i in range(n))), then that's going to affect the overall time complexity.
It's got to be O(n) for a large list of integers.
I had an interview the other day and the interviewer stumped me with this question, was wondering if anyone could share some insight?
Context: summing a linked list is usually O(n) since you have to go through the list once. However, Python 3 integers are not fixed in size, and so can be any number of bytes long. How would this affect time complexity? I assumed any overhead from re-allocating memory would be O(1) and therefore not relevant to time complexity but the interviewer was clearly looking for another answer... Any help is appreciated!
Python - Two Number Sum (time and space complexity) - Code Review Stack Exchange
arrays - time complexity ~~ python - Stack Overflow
python - time complexity of summing algorithms - Stack Overflow
Array sum speed
You're summing a list of N numbers of any size, in any order.
You aren't going to find a clever way to do that faster without some constraints.
It's Ω(N) always (lower bound is N addition operations - you won't get any better than that).
As a commenter below noted your algorithm may in fact be worse - it just can't be better.
Edited: corrections made based on comments regarding O(n) performance of [::].
TL;DR: It could be O(n), but your version is O(n²).
Remember that all of the big-O notations assume "times a constant". That is, O(n) really means O(k * n), and O(log n) really means O(k * log n).
Let's look at your first example:
def sum(numberSequence):
assert (len(numberSequence) > 0)
if (len(numberSequence) == 1):
return numberSequence[0]
else:
return sum(numberSequence[-1]) + numberSequence[:-1]
The first line is assert plus compare plus len. The len operation is a constant time for lists and tuples (But it might not be with some other data structure! Beware!), compare is a constant time, and the assert is effectively a constant time, because if it ever fails the whole thing blows up and we stop computing. So let's just call assert a function call plus a comparison plus a return.
Now, how many times does this function get called? Well, the termination condition obviously represents one time, and every other time it's recursing on a list that is one shorter than the previous list. So the function will be called len(numberSequence) times, which is n for our purposes.
So we have
1 * call (for the user calling us)
+ n * assert
+ n * len
+ n * compare
Next, we have the if statement that marks the termination condition for your recursion. Obviously, this statement will only be successful once (it's the termination condition, right? Only happens at the end...) so that's a comparison each time, and once per sum it's a return of a constant index.
n * compare
+ 1 * constant index
+ 1 * return
Finally, there is the else: branch. I'm pretty sure you have a bug, and it should really be this (note position of colon):
return sum(numberSequence[:-1]) + numberSequence[-1]
In that case you return the sum of a constant negative index lookup and a recursive function call of a slice. You only do this when it's NOT the end of the recursion, so n-1 times.
(n - 1) * constant negative index lookup
+ (n - 1) * slice
+ (n - 1) * recursive call
+ (n - 1) * return
But wait! If you look around for people asking about how to make a copy of a list, you'll find that one common Python idiom is copy = orig[:]. The reason for this is that a slice operation makes a copy of the subrange of the list it is slicing. So when you say numberSequence[:-1] what you're really saying is copy = [orig[i] for i in range(0, len(orig)-1)].
This means that the slice operation is O(n), but on the plus side it's written in C. So the constant is a much smaller one.
Let's add those up:
1 * call
+ n * assert
+ n * len
+ n * compare
+ n * compare
+ 1 * constant index
+ 1 * return
+ (n - 1) * constant negative index lookup
+ (n - 1) * (c * n) slice
+ (n - 1) * recursive call
+ (n - 1) * return
If we assume that constant index and constant negative index take the same time, we can merge them. We can obviously merge the returns and the calls. Which leaves us with:
n * call
+ n * assert
+ n * len
+ n * compare
+ n * compare
+ n * constant (maybe negative) index
+ n * return
+ (n - 1) * (c * n) slice
Now according to "the rules," this is O(n²). Which means that all the details of O(n) behavior fall by the wayside in favor of that big, fat O(n²).
However:
If the len operation were not O(1) - that is, constant time - then the function might well become O(n²) because of that.
If the index operations were not O(1), because of underlying implementation details, the function might become O(n²) or O(n log n) because of that.
So you have implemented an algorithm that could be O(n) using a Python operator that is inherently O(n) itself. Your implementation is "inherently" O(n²). But it can be fixed. Even if fixed, things outside of your control could make your code slower. (But, that's outside your control, so ... ignore it!)
How can we fix your code to make it O(n)? By getting rid of the slice! You don't need that anyway, right? You just need to track the range.
def sum(numberSequence, start=0, end=None):
assert (len(numberSequence) > 0)
if end is None:
end = len(numberSequence) - 1
if end == start:
return numberSequence[start]
else:
return sum(numberSequence, start, end-1) + numberSequence[end]
In this code, I'm doing pretty much the same thing that you did, with two differences. First, I've added a special case to handle being called by an end user with only the sequence as an argument. And second, of course, there is no slice. With that out of the way, the code is no longer inherently O(n²).
You can do the same math, and make the same changes, to your other example, but it's more complex. However, I will remind you that the sum of 2i for i = 0..n-1 is 2n - 1. As @lollercoaster points out, there ain't no such thing as a free lunch: you have to add up all the numbers.
I'm working on optimizing a python implementation of the Structural SIMilarity (SSIM) index by Zhou Wang, Alan C. Bovik, Hamid R. Sheikh and Eero P. Simoncelli (link). I'm not sure I can actually do anything at this point, but it's worth a shot because who doesn't want things to run faster if possible?
Essentially my problem boils down to the post title, how quickly I can sum arrays. I'm using a somewhat modified version of the algorithm which appears to give solid results, though I don't really get much of the math/methodology. The key difference between the original and the version I'm using is that this modified version divides both images into chunks and performs similarity analysis on each chunk, averaging the result over the size of the full image at the end.
After some experimentation I arrived at the conclusion that the fastest array sum function is numpy's einsum function. I don't know if a faster function exists, but I haven't found it yet if it does.
In order to do the actual image analysis, I give the main analysis function each image and then create a set of nested for loops, using the iterator from each for loop to slice the image arrays along the x and y axis and then pass the chunks to the function which actually calculates the similarity index. Since I do 3 summations per chunk, this means that the total number of summations is: ((imagesize/chunksize)2 ) x 3 x numimages
I'm wondering if there's anything I can do to speed things up, either in regards to how I'm summing the matrices or my approach to processing the chunks.
TL;DR gotta go fast
The clear answer in the general case (sorted, but otherwise arbitrary) is "no." You must visit each list value in order to compute the sum (the sorting does not help). Thus it is an ${\cal O}(n)$ computation.
("Binary search" is irrelevant to this question.)
True, you can place limits on the sum of ordered list in ${\cal O}(1)$ time, but that wasn't your question.
Oh gee... if the elements are contiguous then this is an ${\cal O}(1)$ computation, as Gauss proved when he was 10 years old.
To expand on the answer "obviously not", consider the array $i_1, i_2, \dots, i_N$ which is sorted. Then suppose we had a way to compute the sum in logarithmic time. Since we can only visit at most $\log(N)$ of the elements, we did not visit at least one element $i_k$. Now set $i_k := \frac{i_k + i_{k+1}}{2}$ (or $\frac{i_{k-1}+i_k}{2}$ if $k = N$) to change the sum in a way that our algorithm could not detect.
(… and as David now notes, you left crucial assumptions out of the question. Please don't do that.)