| .. _time-complexity: |
| |
| =============================================== |
| Time complexity of operations on built-in types |
| =============================================== |
| |
| This page documents the time complexity of various operations on built-in types |
| in CPython. Other Python implementations may have different performance |
| characteristics. Additionally, the listed costs assume exact built-in types, as |
| instances of subclasses may have different costs. |
| |
| We use |big O notation|_ to describe how the running time of an operation grows |
| with the size of its inputs. Unless stated otherwise, *n* denotes the number of |
| elements currently in the container, and *k* is the value of a numeric |
| parameter, such as an index or a repeat count. |
| |
| .. |big O notation| replace:: Big *O* notation |
| .. _big O notation: https://en.wikipedia.org/wiki/Big_O_notation |
| |
| |
| :class:`!list` |
| ============== |
| |
| Lists are mutable sequences; for more detail on the implementation see |
| :ref:`how-are-lists-implemented`. The largest costs come from growing beyond the |
| current allocation size (because everything must move), or from inserting or |
| deleting somewhere near the beginning (because everything after that must move). |
| If you need to add or remove at both ends, consider using a |
| :class:`collections.deque` instead. |
| |
| .. list-table:: |
| :header-rows: 1 |
| |
| * - Operation |
| - Complexity |
| * - Copy (``l.copy()``) |
| - *O*\ (*n*) |
| * - Append (``l.append(x)``) [1]_ |
| - *O*\ (1) |
| * - Pop (``l.pop(k)``) [1]_ [2]_ |
| - *O*\ (*n* - *k*) |
| * - Insert (``l.insert(k, x)``) [1]_ [2]_ |
| - *O*\ (*n* - *k*) |
| * - Get item (``l[k]``) |
| - *O*\ (1) |
| * - Set item (``l[k] = x``) |
| - *O*\ (1) |
| * - Delete item (``del l[k]``) [2]_ |
| - *O*\ (*n* - *k*) |
| * - Iteration |
| - *O*\ (*n*) |
| * - Get slice (``l[i:j]``) |
| - *O*\ (*j* - *i*) |
| * - Set slice (``l[i:j] = t``) [1]_ |
| - *O*\ (*j* - *i*) if len(*t*) == *j* - *i*, |
| otherwise *O*\ (*n* - *i* + len(*t*)) |
| * - Delete slice (``del l[i:j]``) |
| - *O*\ (*n* - *i*) |
| * - Extend (``l.extend(t)``) [1]_ [3]_ |
| - *O*\ (len(*t*)) |
| * - Sort (``l.sort()``) [4]_ |
| - *O*\ (*n* log *n*) |
| * - Concatenate (``l1 + l2``) |
| - *O*\ (len(*l1*) + len(*l2*)) |
| * - Multiply (``l * k``) |
| - *O*\ (*nk*) |
| * - ``x in l`` |
| - *O*\ (*n*) |
| * - ``min(l)``, ``max(l)`` |
| - *O*\ (*n*) |
| * - Get length (``len(l)``) [5]_ |
| - *O*\ (1) |
| |
| |
| :class:`!tuple` |
| =============== |
| |
| A :class:`tuple` is an :term:`immutable` sequence. Because a tuple can never |
| change, there are no insertion or deletion costs, and making a copy simply |
| returns the same object, so is constant time (*O*\ (1)). |
| |
| .. list-table:: |
| :header-rows: 1 |
| |
| * - Operation |
| - Complexity |
| * - Copy (``tuple(t)``) |
| - *O*\ (1) |
| * - Get item (``t[k]``) |
| - *O*\ (1) |
| * - Get slice (``t[i:j]``) |
| - *O*\ (*j* - *i*) |
| * - Concatenate (``t1 + t2``) |
| - *O*\ (len(*t1*) + len(*t2*)) |
| * - Multiply (``t * k``) |
| - *O*\ (*nk*) |
| * - Iteration |
| - *O*\ (*n*) |
| * - ``x in t`` |
| - *O*\ (*n*) |
| * - ``min(t)``, ``max(t)`` |
| - *O*\ (*n*) |
| * - Get length (``len(t)``) [5]_ |
| - *O*\ (1) |
| |
| |
| :class:`!dict`, :class:`!frozendict` |
| ==================================== |
| |
| The times listed for dict objects are average-case times, as they assume the |
| hash function for the objects is sufficiently robust to make collisions |
| uncommon. They also assume the keys are well-distributed among the set of |
| possible keys. In the worst case, when every key hashes to the same value, |
| each of the *O*\ (1) operations below instead takes *O*\ (*n*) time. They also |
| assume that hashing and comparing a key is *O*\ (1). For more detail on the |
| implementation, see :ref:`how-are-dictionaries-implemented`. |
| |
| A :class:`frozendict` is immutable, so it does not support setting, deleting, |
| or updating items. The other operations below apply to it at the same costs. |
| |
| .. list-table:: |
| :header-rows: 1 |
| |
| * - Operation |
| - Complexity |
| * - ``key in d`` |
| - *O*\ (1) |
| * - Copy (``d.copy()``) [6]_ [7]_ |
| - *O*\ (*n*) |
| * - Get item (``d[key]``, ``d.get(key)``) |
| - *O*\ (1) |
| * - Set item (``d[key] = value``) [1]_ |
| - *O*\ (1) |
| * - Delete item (``del d[key]``, ``d.pop(key)``) |
| - *O*\ (1) |
| * - Update (``d.update(t)``, ``d |= t``) [1]_ [3]_ [7]_ |
| - *O*\ (len(*t*)) |
| * - Iteration [7]_ |
| - *O*\ (*n*) |
| * - Get length (``len(d)``) [5]_ |
| - *O*\ (1) |
| |
| |
| :class:`!set`, :class:`!frozenset` |
| ================================== |
| |
| See :class:`dict` as the :class:`set` and :class:`frozenset` implementations are |
| similar, and the same caveats apply. |
| In the worst case, *O*\ (1) operations instead take *O*\ (*n*) time, |
| and operations that look up every element degrade accordingly. |
| |
| A :class:`frozenset` is :term:`immutable`, so it does not support adding, |
| discarding, or the in-place update operations. The others below apply to it at |
| the same costs. |
| |
| .. list-table:: |
| :header-rows: 1 |
| |
| * - Operation |
| - Complexity |
| * - ``x in s`` |
| - *O*\ (1) |
| * - Copy (``s.copy()``) [6]_ [7]_ |
| - *O*\ (*n*) |
| * - Add (``s.add(x)``) [1]_ |
| - *O*\ (1) |
| * - Discard (``s.discard(x)``, ``s.remove(x)``) |
| - *O*\ (1) |
| * - Union (``s1 | s2``, ``s1.union(s2)``) [7]_ |
| - *O*\ (len(*s1*) + len(*s2*)) |
| * - Update (``s1 |= s2``, ``s1.update(s2)``) [1]_ [7]_ |
| - *O*\ (len(*s2*)) |
| * - Intersection (``s1 & s2``, ``s1.intersection(s2)``) [7]_ [8]_ |
| - *O*\ (min(len(*s1*), len(*s2*))) |
| * - Intersection update (``s1 &= s2``, ``s1.intersection_update(s2)``) [1]_ [7]_ [8]_ |
| - *O*\ (min(len(*s1*), len(*s2*))) |
| * - Difference (``s1 - s2``, ``s1.difference(s2)``) [7]_ [9]_ |
| - *O*\ (len(*s1*)) |
| * - Difference update (``s1 -= s2``, ``s1.difference_update(s2)``) [1]_ [7]_ [8]_ |
| - *O*\ (min(len(*s1*), len(*s2*))) |
| * - Symmetric difference (``s1 ^ s2``, ``s1.symmetric_difference(s2)``) [7]_ |
| - *O*\ (len(*s1*) + len(*s2*)) |
| * - Symmetric difference update (``s1 ^= s2``, ``s1.symmetric_difference_update(s2)``) [1]_ [7]_ |
| - *O*\ (len(*s2*)) |
| * - Get length (``len(s)``) [5]_ |
| - *O*\ (1) |
| |
| |
| :class:`!str`, :class:`!bytes`, :class:`!bytearray` |
| =================================================== |
| |
| :class:`str` and :class:`bytes` objects are immutable sequences of characters and |
| bytes, respectively. As with tuples, copying one returns the original object. |
| A :class:`bytearray` is mutable, and additionally supports the mutating operations |
| of :class:`list` (except :meth:`!sort`), at the same costs. However, deleting at |
| the front with ``del`` (``del b[0]``, ``del b[:k]``) only advances the start of |
| the buffer instead of moving the remaining bytes, and is amortized *O*\ (1). |
| |
| .. list-table:: |
| :header-rows: 1 |
| |
| * - Operation |
| - Complexity |
| * - Get item (``s[k]``) |
| - *O*\ (1) |
| * - Get slice (``s[i:j]``) |
| - *O*\ (*j* - *i*) |
| * - Concatenate (``s + t``) [10]_ |
| - *O*\ (len(*s*) + len(*t*)) |
| * - Multiply (``s * k``) |
| - *O*\ (*nk*) |
| * - Substring search (``x in s``, ``s.find(x)``, ``s.index(x)``) [11]_ |
| - *O*\ (*n*) |
| * - Reverse substring search (``s.rfind(x)``, ``s.rindex(x)``) [11]_ [12]_ |
| - *O*\ (*n* × len(*x*)) |
| * - Encode or decode [13]_ |
| - *O*\ (*n*) |
| * - Iteration |
| - *O*\ (*n*) |
| * - Get length (``len(s)``) [5]_ |
| - *O*\ (1) |
| |
| |
| :class:`!memoryview` |
| ==================== |
| |
| :class:`memoryview` objects allow Python code to access the internal data |
| of an object that supports the :ref:`buffer protocol <bufferobjects>` without |
| copying. In particular, slicing a memory view returns a new view onto the same |
| buffer. |
| |
| .. list-table:: |
| :header-rows: 1 |
| |
| * - Operation |
| - Complexity |
| * - Create (``memoryview(obj)``) |
| - *O*\ (1) |
| * - Get item (``v[k]``) |
| - *O*\ (1) |
| * - Get slice (``v[i:j]``) |
| - *O*\ (1) |
| * - Index (``v.index(x)``) [11]_ [14]_ |
| - *O*\ (*n*) |
| * - Count (``v.count(x)``) [14]_ |
| - *O*\ (*n*) |
| * - Convert to bytes (``v.tobytes()``, ``bytes(v)``) |
| - *O*\ (*n*) |
| * - Get length (``len(v)``) [5]_ |
| - *O*\ (1) |
| |
| |
| :class:`!range` |
| =============== |
| |
| A :class:`range` object computes its items on demand from its *start*, *stop* and |
| *step* values, so most operations do not depend on the length of the range. |
| |
| .. list-table:: |
| :header-rows: 1 |
| |
| * - Operation |
| - Complexity |
| * - Get item (``r[k]``) |
| - *O*\ (1) |
| * - Get slice (``r[i:j]``) |
| - *O*\ (1) |
| * - ``x in r`` [15]_ |
| - *O*\ (1) |
| * - Index and count (``r.index(x)``, ``r.count(x)``) [15]_ |
| - *O*\ (1) |
| * - Iteration |
| - *O*\ (*n*) |
| * - ``min(r)``, ``max(r)`` |
| - *O*\ (*n*) |
| * - Get length (``len(r)``) [5]_ |
| - *O*\ (1) |
| |
| |
| Notes |
| ===== |
| |
| .. [1] Amortized. An individual operation may occasionally be *O*\ (*n*) |
| when the underlying storage is resized, but this cost is spread over |
| many operations, depending on the history of the container. |
| |
| .. [2] Popping or deleting the element at index *k* of a list of size *n* |
| shifts all elements after *k* one slot to the left, moving *n* - *k* - 1 |
| elements; inserting at index *k* shifts the elements from *k* onwards one |
| slot to the right, moving *n* - *k* elements. The worst case is index 0, |
| where the whole rest of the list has to be moved; the average case, an |
| index in the middle of the list, takes *O*\ (*n*/2) = *O*\ (*n*) |
| operations; and operating at the end of the list moves nothing and is |
| *O*\ (1). |
| |
| .. [3] Plus the cost of iterating over *t*, which may be expensive for an |
| arbitrary iterable. |
| |
| .. [4] This is the worst case scenario. Sorting is adaptive and input that is |
| already sorted or reverse-sorted takes only *O*\ (*n*) comparisons. |
| See :source:`Objects/listsort.txt` for more information. |
| |
| .. [5] The number of elements is stored in the object, so ``len()`` does |
| not need to count them. |
| |
| .. [6] Copying a :class:`frozendict` or a :class:`frozenset` is *O*\ (1) as it |
| returns the original object. |
| |
| .. [7] These operations scan the container's internal hash table, which is |
| not shrunk when elements are removed. After removing most elements, they |
| still take time proportional to the container's former size, until a |
| later insertion triggers a resize. |
| |
| .. [8] *O*\ (len(*t*)) if *t* is not a set. |
| |
| .. [9] *O*\ (len(*s*) + len(*t*)) if *t* is not a set. |
| |
| .. [10] Each concatenation builds a new object, so building a string by |
| concatenating many pieces in a loop is quadratic in the total length. |
| See the :ref:`note on concatenating immutable sequences |
| <typesseq-repeated-concatenation>` for alternatives. |
| |
| .. [11] With *start* and *end* arguments, *n* is the length of the region |
| searched rather than of *s*, and unlike slicing nothing is copied. |
| |
| .. [12] This is the worst case. Reverse searches are *O*\ (*n*) on typical |
| input. Forward searches instead use a more elaborate algorithm with a |
| linear worst case, described in |
| :source:`Objects/stringlib/stringlib_find_two_way_notes.txt`. |
| |
| .. [13] This assumes a codec that does a constant amount of work per character. |
| |
| .. [14] These unpack and compare each element individually, so they are much |
| slower than the equivalent :class:`bytes` methods. |
| |
| .. [15] Assuming :class:`int` or :class:`bool` arguments. For other types, |
| the range is searched like any other sequence in *O*\ (*n*) time. |