Get item is getting an item in a specific index, while lookup means searching if some element exists in the list. To do so, unless the list is sorted, you will need to iterate all elements, and have O(n) Get Item operations, which leads to O(n) lookup.
A dictionary is maintaining a smart data structure (hash table) under the hood, so you will not need to query O(n) times to find if the element exists, but a constant number of times (average case), leading to O(1) lookup.
Get item is getting an item in a specific index, while lookup means searching if some element exists in the list. To do so, unless the list is sorted, you will need to iterate all elements, and have O(n) Get Item operations, which leads to O(n) lookup.
A dictionary is maintaining a smart data structure (hash table) under the hood, so you will not need to query O(n) times to find if the element exists, but a constant number of times (average case), leading to O(1) lookup.
Accessing a list l at index n l[n] is O(1) because it is not implemented as a Vanilla linked list where one needs to jump between pointers (value, next-->) n times to reach cell index n.
If the memory is continuous and the entry size would had been fixed, reaching a specific entry would be trivial as we know to jump n times an entry size (like classic arrays in C).
However, since list is variable in entries size, the python implementation uses a continuous memory list just for the pointers to the values. This makes indexing a list (l[n]) an operation whose cost is independent of the size of the list or the value of the index.
For more information see: FAQ, variable-length array (VLA) & Dynamic array
What is the time complexity of the โinโ operation
Internals of Python list, access and resizing runtimes - Stack Overflow
How are list elements accessed in python internally? - Software Engineering Stack Exchange
Time complexity of printing a list? The entire list object itself, not its individual elements (Python)
Accessing primes[i] is done in constant time, O(1). What that means is that the time needed to read primes[i] does not increase as the primes becomes bigger and that it does not increase when i becomes bigger.
In layman's terms: it's damn fast!
Then again, accessing a local variable p is still faster than accessing primes[i], because the latter has to look up and call the __getitem__ implementation of the primes object. Therefore caching a value in a local variable instead of looking up a list twice is marginally faster.
On the other hand, caring about marginal speed improvements is meaningless compared to reducing algorithm complexity. For the problem of finding prime numbers, you should focus on finding a smart algorithm rather than on improving built-in-list access times.
Try using a benchmark
import time
start = time.time()
while n != 1:
p = primes[i]
if n % p == 0:
n = n // p
factorization.append(p)
else:
i += 1
end = time.time()
print(end - start)
do the same for implementation 2 and compare.
And also, try doing it in google colab or any other external machine for better results.
Iโm not the biggest python user. But I was looking at a friends code yesterday and they had something like:
For x in (list of 40000)
For y in (list of 2.7 million)
If x = y
Append something This was obviously super slow so they changed it to something like:
For x in (list of 2.7 million)
If y in (list of 40000)
Append something
This moved much faster. I get the point of one for loop being faster than two, but what is that โinโ exists function doing that makes it so much faster. I always thought that to check if something exists is O(n) which shouldnโt be faster. Also this was for ML purposes so they were likely using numpy stuff.
Python's [] is implemented as an array, not a linked list. Although resizing is O(n), appending to it is amortized O(1), because resizes happen very rarely. If you're not familiar with how this works, read this Wikipedia entry on dynamic arrays. Python's list doesn't expand by a factor of 2 each time, it's a bit more complicated than that, but the expansions are still designed to make appending amortized O(1).
Inserting in the middle, however, is always an inefficient O(n), because n items may have to be moved.
Tuples aren't faster than lists - they're just immutable lists under the hood (*).
Regarding your dictionary test: depending on your exact implementation, caching in a list will be faster than with a dict. However, Python's dicts are highly optimized, and especially for small amounts of keys will perform great.
(*) Here's a list's "get item" C function in Python 2.6:
PyObject *
PyList_GetItem(PyObject *op, Py_ssize_t i)
{
if (!PyList_Check(op)) {
PyErr_BadInternalCall();
return NULL;
}
if (i < 0 || i >= Py_SIZE(op)) {
if (indexerr == NULL)
indexerr = PyString_FromString(
"list index out of range");
PyErr_SetObject(PyExc_IndexError, indexerr);
return NULL;
}
return ((PyListObject *)op) -> ob_item[i];
}
And this is a tuple's:
PyObject *
PyTuple_GetItem(register PyObject *op, register Py_ssize_t i)
{
if (!PyTuple_Check(op)) {
PyErr_BadInternalCall();
return NULL;
}
if (i < 0 || i >= Py_SIZE(op)) {
PyErr_SetString(PyExc_IndexError, "tuple index out of range");
return NULL;
}
return ((PyTupleObject *)op) -> ob_item[i];
}
As you can see, they're almost exactly the same. In the end, after some type and bound checking, it's a simple pointer access with an index.
[Reference: Python documentation on Time Complexity for data type operations]
There is a great list here outlining the time complexity of the python data types. In your case item retrieval should be O(1) time.
I've been trying to Google this for hours, but only found one that answers it. However the explanation was short, and because there was only one, I couldn't make sure if it was correct at all.
Let's say a function asks for an input n, and then it creates a list x with n elements. What would then be the time complexity of print(x) with respect to n? Would its run time scale with whatever the size n of the list x is and therefore it would be O(n)? Or is its runtime going to be constant regardless of the size n?
The reason I'm having a hard time finding an answer to this in Google is because the results I get is always talking about iterating through the list and printing each element individually, instead of printing the list object itself.
According to the one result I found, it is also O(n), because the print function in this case apparently also iterates over the list visiting each element once. It would be nice if anyone else could confirm if this is correct.
there is a very detailed table on python wiki which answers your question.
However, in your particular example you should use enumerate to get an index of an iterable within a loop. like so:
for i, item in enumerate(some_seq):
bar(item, i)
The answer is "undefined". The Python language doesn't define the underlying implementation. Here are some links to a mailing list thread you might be interested in.
It is true that Python's lists have been implemented as contiguous vectors in the C implementations of Python so far.
I'm not saying that the O() behaviours of these things should be kept a secret or anything. But you need to interpret them in the context of how Python works generally.
Also, the more Pythonic way of writing your loop would be this:
def foo(some_list):
for item in some_list:
bar(item)