mirror of
https://github.com/python/cpython.git
synced 2026-10-28 10:25:05 +08:00
* Docs: split builtins to their own page from library * review feedback * addressed Hugo's feedback * update the What's Next page * one more wording tweak * move builtins to their own directory. fix the reference name * moved pages need to be noted in tools/removed-ids.txt * add Python to the builtins reference title * add sphinxext-rediraffe for the library->builtins split * update check-html-ids to handle rediraffe redirects * now we don't need (page missing) for the redirected pages * update other references to moved pages * A seealso from builtins to library * make a nice section for rediraffe settings * move the builtins note to the end, as a seealso * address merwok's comments * cache looking for ids in files * simplify the intro paragraphs
338 lines
11 KiB
ReStructuredText
338 lines
11 KiB
ReStructuredText
.. _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.
|