-
-
Notifications
You must be signed in to change notification settings - Fork 35k
Add a page detailing the time complexity of operations on built-in types #154363
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
StanFromIreland
wants to merge
5
commits into
python:main
Choose a base branch
from
StanFromIreland:timecomplexity-doc
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
80088bf
Add a 'Time complexity of operations on built-in types' section
StanFromIreland 84b95f9
Ned's suggestion for blog post rec.
StanFromIreland 1982202
Revert that
StanFromIreland 64fb553
Little fixups
StanFromIreland 0392d7e
Apply review suggestions from Pieter and Bénédikt
StanFromIreland File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,308 @@ | ||
| .. _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 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 <https://nedbatchelder.com/text/bigo>`__ | ||
| talk and blog post. | ||
|
|
||
| .. |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 (``s.copy()``) | ||
| - *O*\ (*n*) | ||
| * - Append (``s.append(x)``) [1]_ | ||
| - *O*\ (1) | ||
| * - Pop (``s.pop(k)``) [1]_ [2]_ | ||
| - *O*\ (*n* - *k*) | ||
| * - Insert (``s.insert(k, x)``) [1]_ [2]_ | ||
| - *O*\ (*n* - *k*) | ||
| * - Get item (``s[k]``) | ||
| - *O*\ (1) | ||
| * - Set item (``s[k] = x``) | ||
| - *O*\ (1) | ||
| * - Delete item (``del s[k]``) [2]_ | ||
| - *O*\ (*n* - *k*) | ||
| * - Iteration | ||
| - *O*\ (*n*) | ||
| * - Get slice (``s[i:j]``) | ||
| - *O*\ (*k*) | ||
| * - Set slice (``s[i:j] = t``) | ||
| - *O*\ (*k* + *n*) | ||
| * - Delete slice (``del s[i:j]``) | ||
| - *O*\ (*n*) | ||
| * - Extend (``s.extend(t)``) [1]_ | ||
| - *O*\ (*k*) | ||
| * - Sort (``s.sort()``) [3]_ | ||
| - *O*\ (*n* log *n*) | ||
| * - Multiply (``s * k``) | ||
| - *O*\ (*nk*) | ||
| * - ``x in s`` | ||
| - *O*\ (*n*) | ||
| * - ``min(s)``, ``max(s)`` | ||
| - *O*\ (*n*) | ||
| * - Get length (``len(s)``) [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 (``tuple(s)``) | ||
| - *O*\ (1) | ||
| * - Get item (``s[k]``) | ||
| - *O*\ (1) | ||
| * - Get slice (``s[i:j]``) | ||
| - *O*\ (*k*) | ||
| * - Concatenate (``s + t``) | ||
| - *O*\ (*n* + *k*) | ||
| * - Multiply (``s * k``) | ||
| - *O*\ (*nk*) | ||
| * - Iteration | ||
| - *O*\ (*n*) | ||
| * - ``x in s`` | ||
| - *O*\ (*n*) | ||
| * - ``min(s)``, ``max(s)`` | ||
| - *O*\ (*n*) | ||
| * - Get length (``len(s)``) [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, 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 | ||
| * - ``key in d`` | ||
| - *O*\ (1) | ||
| * - Copy (``d.copy()``) [5]_ [6]_ | ||
| - *O*\ (*n*) | ||
| * - Get item (``d[key]``) | ||
| - *O*\ (1) | ||
| * - Set item (``d[key] = value``) [1]_ | ||
| - *O*\ (1) | ||
| * - Delete item (``del d[key]``) | ||
| - *O*\ (1) | ||
| * - Iteration [6]_ | ||
| - *O*\ (*n*) | ||
| * - Get length (``len(d)``) [4]_ | ||
| - *O*\ (1) | ||
|
|
||
|
|
||
| :class:`!set`, :class:`!frozenset` | ||
| ================================== | ||
|
|
||
| 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 :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) | ||
| * - 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 (``len(s)``) [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 (``s[k]``) | ||
| - *O*\ (1) | ||
| * - Get slice (``s[i:j]``) | ||
| - *O*\ (*k*) | ||
| * - Concatenate (``s + t``) | ||
| - *O*\ (*n* + *k*) | ||
| * - Multiply (``s * k``) | ||
| - *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 (``len(s)``) [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 <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) | ||
| * - Convert to bytes (``v.tobytes()``, ``bytes(v)``) | ||
| - *O*\ (*n*) | ||
| * - Get length (``len(v)``) [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 (``s[k]``) | ||
| - *O*\ (1) | ||
| * - Get slice (``s[i:j]``) | ||
| - *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 (``len(s)``) [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. | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.