From 80088bf411a9d86b09fec88b49bc01a323e03b70 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Wed, 8 Jul 2026 21:42:02 +0200 Subject: [PATCH 1/5] Add a 'Time complexity of operations on built-in types' section --- Doc/faq/design.rst | 8 + Doc/faq/programming.rst | 2 +- Doc/glossary.rst | 2 +- Doc/library/collections.rst | 3 +- Doc/library/index.rst | 1 + Doc/library/stdtypes.rst | 6 + Doc/library/time-complexity.rst | 304 ++++++++++++++++++++++++++++++++ Doc/tutorial/datastructures.rst | 3 +- 8 files changed, 325 insertions(+), 4 deletions(-) create mode 100644 Doc/library/time-complexity.rst diff --git a/Doc/faq/design.rst b/Doc/faq/design.rst index c914089e9806ec6..02a3a3a22070dae 100644 --- a/Doc/faq/design.rst +++ b/Doc/faq/design.rst @@ -430,6 +430,8 @@ tuples, but not lists, can be used as keys. Note, however, that a tuple is only hashable if all of its elements are hashable. +.. _how-are-lists-implemented: + How are lists implemented in CPython? ------------------------------------- @@ -445,6 +447,10 @@ cleverness is applied to improve the performance of appending items repeatedly; when the array must be grown, some extra space is allocated so the next few times don't require an actual resize. +See :ref:`time-complexity` for the costs of the various list operations. + + +.. _how-are-dictionaries-implemented: How are dictionaries implemented in CPython? -------------------------------------------- @@ -462,6 +468,8 @@ internal array where the value will be stored. Assuming that you're storing keys that all have different hash values, this means that dictionaries take constant time -- *O*\ (1), in Big-O notation -- to retrieve a key. +See :ref:`time-complexity` for the costs of the various dictionary operations. + Why must dictionary keys be immutable? -------------------------------------- diff --git a/Doc/faq/programming.rst b/Doc/faq/programming.rst index c2f8f72ee1f2c4b..4e1157e6ebe7296 100644 --- a/Doc/faq/programming.rst +++ b/Doc/faq/programming.rst @@ -1136,7 +1136,7 @@ What is the most efficient way to concatenate many strings together? :class:`str` and :class:`bytes` objects are immutable, therefore concatenating many strings together is inefficient as each concatenation creates a new object. In the general case, the total runtime cost is quadratic in the -total string length. +total string length. See :ref:`time-complexity` for more information. To accumulate many :class:`str` objects, the recommended idiom is to place them into a list and call :meth:`str.join` at the end:: diff --git a/Doc/glossary.rst b/Doc/glossary.rst index bb00a4f02f0efd5..02f89b419d9fc2d 100644 --- a/Doc/glossary.rst +++ b/Doc/glossary.rst @@ -942,7 +942,7 @@ Glossary list A built-in Python :term:`sequence`. Despite its name it is more akin to an array in other languages than to a linked list since access to - elements is *O*\ (1). + elements is *O*\ (1). See :ref:`time-complexity`. list comprehension A compact way to process all or part of the elements in a sequence and diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index d09a6c92bbd37dc..65128149de33ec0 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -473,7 +473,8 @@ or subtracting from an empty counter. Though :class:`list` objects support similar operations, they are optimized for fast fixed-length operations and incur *O*\ (*n*) memory movement costs for ``pop(0)`` and ``insert(0, v)`` operations which change both the size and - position of the underlying data representation. + position of the underlying data representation. See :ref:`time-complexity` + for more information. If *maxlen* is not specified or is ``None``, deques may grow to an diff --git a/Doc/library/index.rst b/Doc/library/index.rst index 8fc77be520d4268..f28c03e2fae092f 100644 --- a/Doc/library/index.rst +++ b/Doc/library/index.rst @@ -44,6 +44,7 @@ the `Python Package Index `_. stdtypes.rst exceptions.rst threadsafety.rst + time-complexity.rst text.rst binary.rst diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 886648e820f071d..b2f0d934980353a 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -999,6 +999,9 @@ The ``in`` and ``not in`` operations have the same priorities as the comparison operations. The ``+`` (concatenation) and ``*`` (repetition) operations have the same priority as the corresponding numeric operations. [3]_ +See :ref:`time-complexity` for the costs of the various sequence +operations. + .. index:: triple: operations on; sequence; types pair: built-in function; len @@ -5112,6 +5115,7 @@ computing mathematical operations such as intersection, union, difference, and symmetric difference. (For other containers see the built-in :class:`dict`, :class:`list`, and :class:`tuple` classes, and the :mod:`collections` module.) +See :ref:`time-complexity` for the costs of the various set operations. Like other collections, sets support ``x in set``, ``len(set)``, and ``for x in set``. Being an unordered collection, sets do not record element position or @@ -5336,6 +5340,8 @@ There are currently two standard mapping types, the :dfn:`dictionary` and (For other containers see the built-in :class:`list`, :class:`set`, and :class:`tuple` classes, and the :mod:`collections` module.) +See :ref:`time-complexity` for the costs of the various dictionary +operations. A dictionary's keys are *almost* arbitrary values. Values that are not :term:`hashable`, that is, values containing lists, dictionaries or other diff --git a/Doc/library/time-complexity.rst b/Doc/library/time-complexity.rst new file mode 100644 index 000000000000000..ad9101f2c1524ac --- /dev/null +++ b/Doc/library/time-complexity.rst @@ -0,0 +1,304 @@ +.. _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 often miss CPython's internal fast paths. + +We use |big O notation|_ to describe how the running time of an operation grows +with the size of its input. Generally, *n* is the number of elements currently +in the container, and *k* is either the value of a parameter or the number of +elements in the parameter. See Ned Batchelder's `Big-O: How Code Slows as Data +Grows `__ talk and blog post for more information. + +.. |big O notation| replace:: Big *O* notation +.. _big O notation: https://en.wikipedia.org/wiki/Big_O_notation + + +:class:`!list` +============== + +Lists are mutable sequences. Internally, a :class:`list` is represented as an +array; for more detail 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 + - *O*\ (*n*) + * - Append [1]_ + - *O*\ (1) + * - Pop [1]_ [2]_ + - *O*\ (*n* - *k*) + * - Insert [1]_ [2]_ + - *O*\ (*n* - *k*) + * - Get item + - *O*\ (1) + * - Set item + - *O*\ (1) + * - Delete item [2]_ + - *O*\ (*n* - *k*) + * - Iteration + - *O*\ (*n*) + * - Get slice + - *O*\ (*k*) + * - Set slice + - *O*\ (*k* + *n*) + * - Delete slice + - *O*\ (*n*) + * - Extend [1]_ + - *O*\ (*k*) + * - Sort [3]_ + - *O*\ (*n* log *n*) + * - Multiply + - *O*\ (*nk*) + * - ``x in s`` + - *O*\ (*n*) + * - ``min(s)``, ``max(s)`` + - *O*\ (*n*) + * - Get length [4]_ + - *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 is constant +time as the same object is returned. + +.. list-table:: + :header-rows: 1 + + * - Operation + - Complexity + * - Copy + - *O*\ (1) + * - Get item + - *O*\ (1) + * - Get slice + - *O*\ (*k*) + * - Concatenate (``s + t``) + - *O*\ (*n* + *k*) + * - Multiply + - *O*\ (*nk*) + * - Iteration + - *O*\ (*n*) + * - ``x in s`` + - *O*\ (*n*) + * - ``min(s)``, ``max(s)`` + - *O*\ (*n*) + * - Get length [4]_ + - *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 used in parameters are selected uniformly +at random from the set of all 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. For more detail on the implementation, see +:ref:`how-are-dictionaries-implemented`. + +A :class:`frozendict` is immutable, but the non-mutating operations below +apply to it at the same costs. + +.. list-table:: + :header-rows: 1 + + * - Operation + - Complexity + * - ``k in d`` + - *O*\ (1) + * - Copy [5]_ [6]_ + - *O*\ (*n*) + * - Get item + - *O*\ (1) + * - Set item [1]_ + - *O*\ (1) + * - Delete item + - *O*\ (1) + * - Iteration [6]_ + - *O*\ (*n*) + * - Get length [4]_ + - *O*\ (1) + + +:class:`!set`, :class:`!frozenset` +================================== + +See :class:`dict` -- the :class:`set` and :class:`frozenset` implementation is +intentionally very similar, and the same hash collision caveat applies: +in the worst case, *O*\ (1) operations instead take *O*\ (*n*) time, +and operations that look up every element of an operand degrade accordingly. + +A frozenset is immutable, but the non-mutating operations below apply to it at +the same costs. + +.. list-table:: + :header-rows: 1 + + * - Operation + - Complexity + * - ``x in s`` + - *O*\ (1) + * - Add (``s.add(x)``) [1]_ + - *O*\ (1) + * - Discard (``s.discard(x)``) + - *O*\ (1) + * - Union (``s | t``) [6]_ + - *O*\ (len(*s*) + len(*t*)) + * - Intersection (``s & t``, ``s.intersection(t)``) [6]_ [7]_ + - *O*\ (min(len(*s*), len(*t*))) + * - Difference (``s - t``, ``s.difference(t)``) [6]_ [8]_ + - *O*\ (len(*s*)) + * - Difference update (``s.difference_update(t)``) [1]_ [6]_ [7]_ + - *O*\ (min(len(*s*), len(*t*))) + * - Symmetric difference (``s ^ t``) [6]_ + - *O*\ (len(*s*) + len(*t*)) + * - Symmetric difference update (``s.symmetric_difference_update(t)``) [1]_ [6]_ + - *O*\ (len(*t*)) + * - Get length [4]_ + - *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 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 + - *O*\ (1) + * - Get slice + - *O*\ (*k*) + * - Concatenate (``s + t``) + - *O*\ (*n* + *k*) + * - Multiply + - *O*\ (*nk*) + * - Substring search (``x in s``, ``s.find(x)``, ``s.index(x)``) [9]_ + - *O*\ (*n*) + * - Encode or decode + - *O*\ (*n*) + * - Iteration + - *O*\ (*n*) + * - Get length [4]_ + - *O*\ (1) + + +:class:`!memoryview` +==================== + +:class:`memoryview` objects allow Python code to access the internal data +of an object that supports the :ref:`buffer protocol ` without +copying. In particular, slicing a memory view returns a new view onto the same +buffer. + +.. list-table:: + :header-rows: 1 + + * - Operation + - Complexity + * - Create + - *O*\ (1) + * - Get item + - *O*\ (1) + * - Get slice + - *O*\ (1) + * - Convert to bytes (``v.tobytes()``, ``bytes(v)``) + - *O*\ (*n*) + * - Get length [4]_ + - *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 + - *O*\ (1) + * - Get slice + - *O*\ (1) + * - ``x in s`` [10]_ + - *O*\ (1) + * - Index and count (``s.index(x)``, ``s.count(x)``) [10]_ + - *O*\ (1) + * - Iteration + - *O*\ (*n*) + * - ``min(s)``, ``max(s)`` + - *O*\ (*n*) + * - Get length [4]_ + - *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] 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. + +.. [4] The number of elements is stored in the object, so ``len()`` does + not need to count them. + +.. [5] Copying a :class:`frozendict` is *O*\ (1) as it returns the original object. + +.. [6] 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. + +.. [7] *O*\ (len(*t*)) if *t* is not a set. + +.. [8] *O*\ (len(*s*) + len(*t*)) if *t* is not a set. + +.. [9] A naive substring search would need *O*\ (*nk*) comparisons in the + worst case, where *k* is the length of the substring searched for, but + CPython uses search algorithms with a linear worst case for forward + searches; see :source:`Objects/stringlib/stringlib_find_two_way_notes.txt` + for details. + +.. [10] Assuming :class:`int` or :class:`bool` arguments. For other types, + the range is searched like any other sequence in *O*\ (*n*) time. diff --git a/Doc/tutorial/datastructures.rst b/Doc/tutorial/datastructures.rst index 276e31a3056f0ee..77dd8a6e66d5cc4 100644 --- a/Doc/tutorial/datastructures.rst +++ b/Doc/tutorial/datastructures.rst @@ -167,7 +167,8 @@ It is also possible to use a list as a queue, where the first element added is the first element retrieved ("first-in, first-out"); however, lists are not efficient for this purpose. While appends and pops from the end of list are fast, doing inserts or pops from the beginning of a list is slow (because all -of the other elements have to be shifted by one). +of the other elements have to be shifted by one). See +:ref:`time-complexity` for more information. To implement a queue, use :class:`collections.deque` which was designed to have fast appends and pops from both ends. For example:: From 84b95f997c54664fd57ba7ddff57734bcff9f7b6 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Wed, 8 Jul 2026 22:06:37 +0200 Subject: [PATCH 2/5] Ned's suggestion for blog post rec. --- Doc/library/time-complexity.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/time-complexity.rst b/Doc/library/time-complexity.rst index ad9101f2c1524ac..538f10ffdee734a 100644 --- a/Doc/library/time-complexity.rst +++ b/Doc/library/time-complexity.rst @@ -12,8 +12,9 @@ instances of subclasses often miss CPython's internal fast paths. We use |big O notation|_ to describe how the running time of an operation grows with the size of its input. Generally, *n* is the number of elements currently in the container, and *k* is either the value of a parameter or the number of -elements in the parameter. See Ned Batchelder's `Big-O: How Code Slows as Data -Grows `__ talk and blog post for more information. +elements in the parameter. For a pragmatic approach to assessing time complexity, +see Ned Batchelder's `Big-O: How Code Slows as Data Grows +`__ talk and blog post. .. |big O notation| replace:: Big *O* notation .. _big O notation: https://en.wikipedia.org/wiki/Big_O_notation From 19822027cce3e6b660863781265e9b5f4bfc03d0 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Tue, 21 Jul 2026 15:36:46 +0200 Subject: [PATCH 3/5] Revert that --- Doc/library/collections.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doc/library/collections.rst b/Doc/library/collections.rst index 65128149de33ec0..d09a6c92bbd37dc 100644 --- a/Doc/library/collections.rst +++ b/Doc/library/collections.rst @@ -473,8 +473,7 @@ or subtracting from an empty counter. Though :class:`list` objects support similar operations, they are optimized for fast fixed-length operations and incur *O*\ (*n*) memory movement costs for ``pop(0)`` and ``insert(0, v)`` operations which change both the size and - position of the underlying data representation. See :ref:`time-complexity` - for more information. + position of the underlying data representation. If *maxlen* is not specified or is ``None``, deques may grow to an From 64fb553c9e51160a826d8cdce678732029ca1918 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Tue, 21 Jul 2026 15:40:10 +0200 Subject: [PATCH 4/5] Little fixups --- Doc/library/time-complexity.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Doc/library/time-complexity.rst b/Doc/library/time-complexity.rst index 538f10ffdee734a..1fef65d476363e8 100644 --- a/Doc/library/time-complexity.rst +++ b/Doc/library/time-complexity.rst @@ -141,13 +141,13 @@ apply to it at the same costs. :class:`!set`, :class:`!frozenset` ================================== -See :class:`dict` -- the :class:`set` and :class:`frozenset` implementation is -intentionally very similar, and the same hash collision caveat applies: -in the worst case, *O*\ (1) operations instead take *O*\ (*n*) time, +See :class:`dict` as the :class:`set` and :class:`frozenset` implementations are +intentionally very similar, and the same hash collision caveat applies. +In the worst case, *O*\ (1) operations instead take *O*\ (*n*) time, and operations that look up every element of an operand degrade accordingly. -A frozenset is immutable, but the non-mutating operations below apply to it at -the same costs. +A :class:`frozenset` is :term:`immutable`, but the non-mutating operations below +apply to it at the same costs. .. list-table:: :header-rows: 1 @@ -180,7 +180,7 @@ the same costs. =================================================== :class:`str` and :class:`bytes` objects are immutable sequences of characters and -bytes respectively; as with tuples, copying one returns the original object. +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 only advances the start of the buffer instead of moving the remaining @@ -278,8 +278,8 @@ Notes *O*\ (1). .. [3] 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. + already sorted or reverse-sorted takes only *O*\ (*n*) comparisons. + See :source:`Objects/listsort.txt` for more information. .. [4] The number of elements is stored in the object, so ``len()`` does not need to count them. @@ -298,7 +298,7 @@ Notes .. [9] A naive substring search would need *O*\ (*nk*) comparisons in the worst case, where *k* is the length of the substring searched for, but CPython uses search algorithms with a linear worst case for forward - searches; see :source:`Objects/stringlib/stringlib_find_two_way_notes.txt` + searches. See :source:`Objects/stringlib/stringlib_find_two_way_notes.txt` for details. .. [10] Assuming :class:`int` or :class:`bool` arguments. For other types, From 0392d7e57b74fcb1357c0fcf7315dbc549339fb2 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Tue, 21 Jul 2026 22:43:22 +0200 Subject: [PATCH 5/5] =?UTF-8?q?Apply=20review=20suggestions=20from=20Piete?= =?UTF-8?q?r=20and=20B=C3=A9n=C3=A9dikt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> Co-authored-by: Pieter Eendebak --- Doc/library/time-complexity.rst | 111 ++++++++++++++++---------------- 1 file changed, 57 insertions(+), 54 deletions(-) diff --git a/Doc/library/time-complexity.rst b/Doc/library/time-complexity.rst index 1fef65d476363e8..8a73c52d6e54980 100644 --- a/Doc/library/time-complexity.rst +++ b/Doc/library/time-complexity.rst @@ -6,15 +6,17 @@ 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 often miss CPython's internal fast paths. +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 input. Generally, *n* is the number of elements currently -in the container, and *k* is either the value of a parameter or the number of -elements in the parameter. For a pragmatic approach to assessing time complexity, -see Ned Batchelder's `Big-O: How Code Slows as Data Grows -`__ talk and blog post. +with the size of its input. Unless stated otherwise, *n* denotes the number of +elements currently in the container, and *k* is either the value of a parameter +or the number of elements in the parameter. + +For a pragmatic approach to assessing time complexity, see Ned Batchelder's +`Big-O: How Code Slows as Data Grows `__ +talk and blog post. .. |big O notation| replace:: Big *O* notation .. _big O notation: https://en.wikipedia.org/wiki/Big_O_notation @@ -23,51 +25,51 @@ see Ned Batchelder's `Big-O: How Code Slows as Data Grows :class:`!list` ============== -Lists are mutable sequences. Internally, a :class:`list` is represented as an -array; for more detail 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. +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 + * - Copy (``s.copy()``) - *O*\ (*n*) - * - Append [1]_ + * - Append (``s.append(x)``) [1]_ - *O*\ (1) - * - Pop [1]_ [2]_ + * - Pop (``s.pop(k)``) [1]_ [2]_ - *O*\ (*n* - *k*) - * - Insert [1]_ [2]_ + * - Insert (``s.insert(k, x)``) [1]_ [2]_ - *O*\ (*n* - *k*) - * - Get item + * - Get item (``s[k]``) - *O*\ (1) - * - Set item + * - Set item (``s[k] = x``) - *O*\ (1) - * - Delete item [2]_ + * - Delete item (``del s[k]``) [2]_ - *O*\ (*n* - *k*) * - Iteration - *O*\ (*n*) - * - Get slice + * - Get slice (``s[i:j]``) - *O*\ (*k*) - * - Set slice + * - Set slice (``s[i:j] = t``) - *O*\ (*k* + *n*) - * - Delete slice + * - Delete slice (``del s[i:j]``) - *O*\ (*n*) - * - Extend [1]_ + * - Extend (``s.extend(t)``) [1]_ - *O*\ (*k*) - * - Sort [3]_ + * - Sort (``s.sort()``) [3]_ - *O*\ (*n* log *n*) - * - Multiply + * - Multiply (``s * k``) - *O*\ (*nk*) * - ``x in s`` - *O*\ (*n*) * - ``min(s)``, ``max(s)`` - *O*\ (*n*) - * - Get length [4]_ + * - Get length (``len(s)``) [4]_ - *O*\ (1) @@ -83,15 +85,15 @@ time as the same object is returned. * - Operation - Complexity - * - Copy + * - Copy (``tuple(s)``) - *O*\ (1) - * - Get item + * - Get item (``s[k]``) - *O*\ (1) - * - Get slice + * - Get slice (``s[i:j]``) - *O*\ (*k*) * - Concatenate (``s + t``) - *O*\ (*n* + *k*) - * - Multiply + * - Multiply (``s * k``) - *O*\ (*nk*) * - Iteration - *O*\ (*n*) @@ -99,7 +101,7 @@ time as the same object is returned. - *O*\ (*n*) * - ``min(s)``, ``max(s)`` - *O*\ (*n*) - * - Get length [4]_ + * - Get length (``len(s)``) [4]_ - *O*\ (1) @@ -114,27 +116,27 @@ to the same value, each of the *O*\ (1) operations below instead takes *O*\ (*n*) time. For more detail on the implementation, see :ref:`how-are-dictionaries-implemented`. -A :class:`frozendict` is immutable, but the non-mutating operations below -apply to it at the same costs. +A :class:`frozendict` is immutable, so it does not support setting or deleting +items; the other operations below apply to it at the same costs. .. list-table:: :header-rows: 1 * - Operation - Complexity - * - ``k in d`` + * - ``key in d`` - *O*\ (1) - * - Copy [5]_ [6]_ + * - Copy (``d.copy()``) [5]_ [6]_ - *O*\ (*n*) - * - Get item + * - Get item (``d[key]``) - *O*\ (1) - * - Set item [1]_ + * - Set item (``d[key] = value``) [1]_ - *O*\ (1) - * - Delete item + * - Delete item (``del d[key]``) - *O*\ (1) * - Iteration [6]_ - *O*\ (*n*) - * - Get length [4]_ + * - Get length (``len(d)``) [4]_ - *O*\ (1) @@ -146,8 +148,9 @@ intentionally very similar, and the same hash collision caveat applies. In the worst case, *O*\ (1) operations instead take *O*\ (*n*) time, and operations that look up every element of an operand degrade accordingly. -A :class:`frozenset` is :term:`immutable`, but the non-mutating operations below -apply to it at the same costs. +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 @@ -172,7 +175,7 @@ apply to it at the same costs. - *O*\ (len(*s*) + len(*t*)) * - Symmetric difference update (``s.symmetric_difference_update(t)``) [1]_ [6]_ - *O*\ (len(*t*)) - * - Get length [4]_ + * - Get length (``len(s)``) [4]_ - *O*\ (1) @@ -191,13 +194,13 @@ bytes, and is amortized *O*\ (1). * - Operation - Complexity - * - Get item + * - Get item (``s[k]``) - *O*\ (1) - * - Get slice + * - Get slice (``s[i:j]``) - *O*\ (*k*) * - Concatenate (``s + t``) - *O*\ (*n* + *k*) - * - Multiply + * - Multiply (``s * k``) - *O*\ (*nk*) * - Substring search (``x in s``, ``s.find(x)``, ``s.index(x)``) [9]_ - *O*\ (*n*) @@ -205,7 +208,7 @@ bytes, and is amortized *O*\ (1). - *O*\ (*n*) * - Iteration - *O*\ (*n*) - * - Get length [4]_ + * - Get length (``len(s)``) [4]_ - *O*\ (1) @@ -222,15 +225,15 @@ buffer. * - Operation - Complexity - * - Create + * - Create (``memoryview(obj)``) - *O*\ (1) - * - Get item + * - Get item (``v[k]``) - *O*\ (1) - * - Get slice + * - Get slice (``v[i:j]``) - *O*\ (1) * - Convert to bytes (``v.tobytes()``, ``bytes(v)``) - *O*\ (*n*) - * - Get length [4]_ + * - Get length (``len(v)``) [4]_ - *O*\ (1) @@ -245,9 +248,9 @@ A :class:`range` object computes its items on demand from its *start*, *stop* an * - Operation - Complexity - * - Get item + * - Get item (``s[k]``) - *O*\ (1) - * - Get slice + * - Get slice (``s[i:j]``) - *O*\ (1) * - ``x in s`` [10]_ - *O*\ (1) @@ -257,7 +260,7 @@ A :class:`range` object computes its items on demand from its *start*, *stop* an - *O*\ (*n*) * - ``min(s)``, ``max(s)`` - *O*\ (*n*) - * - Get length [4]_ + * - Get length (``len(s)``) [4]_ - *O*\ (1)