Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Complexity of len() with regard to sets and lists

The complexity of len() with regards to sets and lists is equally O(1). How come it takes more time to process sets?

~$ python -m timeit "a=[1,2,3,4,5,6,7,8,9,10];len(a)" 10000000 loops, best of 3: 0.168 usec per loop ~$ python -m timeit "a={1,2,3,4,5,6,7,8,9,10};len(a)" 1000000 loops, best of 3: 0.375 usec per loop 

Is it related to the particular benchmark, as in, it takes more time to build sets than lists and the benchmark takes that into account as well?

If the creation of a set object takes more time compared to creating a list, what would be the underlying reason?

like image 710
Omid Avatar asked Aug 27 '15 12:08

Omid


People also ask

What is the time complexity of LEN ()?

The len() function in Python has a very peculiar characteristic that one had often wondered about. It takes absolutely no time, and equal time, in calculating the lengths of iterable data structures(string, array, tuple, etc.), irrespective of the size or type of data. This obviously implies O(1) time complexity.

Is Len array O 1 or O N?

len is an O(1) because in your RAM, lists are stored as tables (series of contiguous addresses).

What is the time complexity of set () in Python?

Creating Set:- In Python, Sets are created through set() function. An Empty list is created. Note that empty Set cannot be created through {}, it creates dictionary. Checking if an item is in : Time complexity of this operation is O(1) on average.

Can LEN be used on set?

With len(..) we obtain the size of a collection. So the size of that set is the number of unique characters of the input. So if we write len(set(some_string)) we will first turn a string into a set. This is possible since a string is an iterable of the characters.


2 Answers

Firstly, you have not measured the speed of len(), you have measured the speed of creating a list/set together with the speed of len().

Use the --setup argument of timeit:

$ python -m timeit --setup "a=[1,2,3,4,5,6,7,8,9,10]" "len(a)" 10000000 loops, best of 3: 0.0369 usec per loop $ python -m timeit --setup "a={1,2,3,4,5,6,7,8,9,10}" "len(a)" 10000000 loops, best of 3: 0.0372 usec per loop 

The statements you pass to --setup are run before measuring the speed of len().

Secondly, you should note that len(a) is a pretty quick statement. The process of measuring its speed may be subject to "noise". Consider that the code executed (and measured) by timeit is equivalent to the following:

for i in itertools.repeat(None, number):     len(a) 

Because both len(a) and itertools.repeat(...).__next__() are fast operations and their speeds may be similar, the speed of itertools.repeat(...).__next__() may influence the timings.

For this reason, you'd better measure len(a); len(a); ...; len(a) (repeated 100 times or so) so that the body of the for loop takes a considerably higher amount of time than the iterator:

$ python -m timeit --setup "a=[1,2,3,4,5,6,7,8,9,10]" "$(for i in {0..1000}; do echo "len(a)"; done)" 10000 loops, best of 3: 29.2 usec per loop $ python -m timeit --setup "a={1,2,3,4,5,6,7,8,9,10}" "$(for i in {0..1000}; do echo "len(a)"; done)" 10000 loops, best of 3: 29.3 usec per loop 

(The results still says that len() has the same performances on lists and sets, but now you are sure that the result is correct.)

Thirdly, it's true that "complexity" and "speed" are related, but I believe you are making some confusion. The fact that len() has O(1) complexity for lists and sets does not imply that it must run with the same speed on lists and sets.

It means that, on average, no matter how long the list a is, len(a) performs the same asymptotic number of steps. And no matter how long the set b is, len(b) performs the same asymptotic number of steps. But the algorithm for computing the size of lists and sets may be different, resulting in different performances (timeit shows that this is not the case, however this may be a possibility).

Lastly,

If the creation of a set object takes more time compared to creating a list, what would be the underlying reason?

A set, as you know, does not allow repeated elements. Sets in CPython are implemented as hash tables (to ensure average O(1) insertion and lookup): constructing and maintaining a hash table is much more complex than adding elements to a list.

Specifically, when constructing a set, you have to compute hashes, build the hash table, look it up to avoid inserting duplicated events and so on. By contrast, lists in CPython are implemented as a simple array of pointers that is malloc()ed and realloc()ed as required.

like image 129
Andrea Corbellini Avatar answered Oct 13 '22 12:10

Andrea Corbellini


The relevant lines are http://svn.python.org/view/python/trunk/Objects/setobject.c?view=markup#l640

640     static Py_ssize_t 641     set_len(PyObject *so) 642     { 643         return ((PySetObject *)so)->used; 644     } 

and http://svn.python.org/view/python/trunk/Objects/listobject.c?view=markup#l431

431     static Py_ssize_t 432     list_length(PyListObject *a) 433     { 434         return Py_SIZE(a); 435     } 

Both are only a static lookup.

So what is the difference you may ask. You measure the creation of the objects, too. And it is a little more time consuming to create a set than a list.

like image 33
kay Avatar answered Oct 13 '22 14:10

kay