Ordered Multivalue Dictionary
Project description
Better OrderedMultiDict
Overview
Better OrderedMultiDict provides two fast ordered multivalued dictionaries: OrderedMultiDict and DeOrderedMultiDict.
Multivalued means that there can be multiple items with the same key:
from better_orderedmultidict import OrderedMultiDict
dictionary = OrderedMultiDict()
dictionary.add("Schrödinger's cat", "dead")
dictionary.add("Schrödinger's cat", "alive")
print(dictionary.getall("Schrödinger's cat")) #["dead", "alive"]
Ordered means that the insertion order of all keys and values is retained:
from better_orderedmultidict import OrderedMultiDict
dictionary = OrderedMultiDict()
dictionary.add("Schrödinger's cat", "alive")
dictionary.add("Pavlov's dog", "conditioned")
dictionary.add("Schrödinger's cat", "dead")
print(list(dictionary.values())) # ["alive", "conditioned", "dead"]
print(list(dictionary.unique_keys())) # ["Schrödinger's cat", "Pavlov's dog"]
print(list(dictionary.keys())) # ["Schrödinger's cat", "Pavlov's dog", "Schrödinger's cat"]
dictionary.poplastitem()
print(list(dictionary.values())) # ["alive", "conditioned"]
print(list(dictionary.unique_keys())) # ["Schrödinger's cat", "Pavlov's dog"]
print(list(dictionary.keys())) # ["Schrödinger's cat", "Pavlov's dog"]
Better OrderedMultiDict requires Python 3.12+ and is fully type annotated.
Performance Comparison
Creating / iterating over dictionary with 500'000 entries with all keys being different:
| OrderedMultiDict | omdict | speedup | boltons OrderedMultiDict |
speedup | ||
|---|---|---|---|---|---|---|
| create | 117.6 ms | 285.8 ms | 2.4x | 136.2 ms | 1.2x | |
| addall / addlist | 67.4 ms | 158.2 ms | 2.3x | 66.6 ms | slower | |
| update / updateall1) | 133.1 ms | 281.3 ms | 2.1x | 175.1 ms | 1.3x | |
| extend / update_extend | 92.6 ms | -- | -- | 145.9 ms | 1.6x | |
| copy | 52.3 ms | 335.7 ms | 6.4x | 195.7 ms | 3.7x | |
| iterate over items | 2.2 ms | 45.2 ms | 20.3x | 46.2 ms | 20.8x | |
| iterate over values | 5.7 ms | 33.5 ms | 5.8x | 33.1 ms | 5.8x | |
| iterate over keys | 5.8 ms | 33.3 ms | 5.8x | 15.8 ms | 2.7x | |
| iterate over unique keys | 16.5 ms | 5.8 ms | slower | 32.0 ms | 1.9x | |
| pop last item until empty | 138.0 ms | 242.6 ms | 1.8x | 403.4 ms | 2.9x |
Creating / iterating over dictionary with 500'000 entries, but only 100 unique keys distributed randomly:
| OrderedMultiDict | omdict | speedup | boltons OrderedMultiDict |
speedup | ||
|---|---|---|---|---|---|---|
| create | 46.7 ms | 249.2 ms | 5.3x | 120.9 ms | 2.6x | |
| addall / addlist | 59.6 ms | 156.9 ms | 2.6x | 67.3 ms | 1.1x | |
| update / updateall1) | 53.0 ms | 250.5 ms | 4.7x | 116.0 ms | 2.2x | |
| extend / update_extend | 51.0 ms | -- | -- | 117.5 ms | 2.3x | |
| copy | 9.1 ms | 281.6 ms | 30.8x | 133.9 ms | 14.6x | |
| iterate over items | 2.5 ms | 44.0 ms | 17.7x | 29.7 ms | 11.9x | |
| iterate over values | 7.3 ms | 31.3 ms | 4.3x | 31.3 ms | 4.3x | |
| iterate over keys | 11.9 ms | 31.0 ms | 2.6x | 18.1 ms | 1.5x | |
| iterate over unique keys | 18.2 ms | < 0.1 ms | slower | 19.0 ms | 1.0x | |
| pop last item until empty | 110.0 ms | 215.1 ms | 2.0x | 163.3 ms | 1.5x |
1): omdict. updateall() has slightly different behavior: omdict keeps the positions for already existing keys, but OrderedMultiDict and bolton's OrderedMultiDict do not:
from better_orderedmultidict import OrderedMultiDict
from orderedmultidict import omdict
initial = [(1, 1), (2, 2), (1, 11), (2, 22), (3, 3)]
omd1 = OrderedMultiDict(initial)
omd2 = omdict(initial)
updates = [(1, '1'), (1, '11')]
omd1.update(updates)
omd2.updateall(updates)
print(list(omd1.items())) # [(2, 2), (2, 22), (3, '3'), (1, '1'), (1, '11')]
print(list(omd2.iterallitems())) # [(1, '1'), (2, 2), (1, '11'), (2, 22), (3, '3')]
Differences between OrderedMultiDict and DeOrderedMultiDict:
Both provide the same API, but have slightly different performance characteristics:
OrderedMultiDictis generally about 1.5x - 4x faster and uses a lot less memory per unique key. But method.popfirstitem()has linear performance characteristics O(n)DeOrderedMultiDictis generally slower and uses a lot more memory per unique key. But method.popfirstitem()has constant performance characteristics O(1)
Creating / iterating over dictionary with 500000 entries with all keys being different:
| OrderedMultiDict | DeOrderedMultiDict | OrderedMultiDict is faster: |
|
|---|---|---|---|
| create | 101.8 ms | 161.4 ms | 1.6x |
| copy | 51.9 ms | 146.8 ms | 2.8x |
| iterate over items | 2.2 ms | 10.3 ms | 4.6x |
| iterate over unique keys | 15.4 ms | 23.4 ms | 1.5x |
| pop first item until empty | 38.7 s (!) | 194.9 ms | >190x slower |
| pop last item until empty | 143.2 ms | 180.3 ms | 1.3x |
Examples
OrderedMultiDict can have multiple values per key:
from better_orderedmultidict import OrderedMultiDict
omd = OrderedMultiDict()
omd[1] = 1
print(omd[1]) # prints: 1
omd.add(1, 11)
print(omd[1]) # prints: 11
print(omd.get(1)) # prints: 11
print(omd.getlast(1)) # prints: 11
print(omd.getfirst(1)) # prints: 1
print(omd.getall(1)) # prints: [1, 11]
# adding multiple values at once:
omd.addall(2, [2, 22])
print(omd.getall(2)) # prints: [2, 22]
# __setitem__ overrides all existing entries for the given key:
omd[1] = 111
print(list(omd.values())) # prints: [2, 22, 111]
OrderedMultiDict retains insertion order:
from better_orderedmultidict import OrderedMultiDict
omd = OrderedMultiDict()
omd.addall(1, [1, 11])
omd.add(2, 2)
omd.add(3, 3)
omd.add(2, 22)
omd.add(3, 33)
omd.add(1, 111)
print(omd.getall(1)) # prints: [1, 11, 111]
print(list(omd.values())) # prints: [1, 11, 2, 3, 22, 33, 111]
del omd[2]
print(list(omd.values())) # prints: [1, 11, 3, 33, 111]
omd.popfirst(3)
print(list(omd.values())) # prints: [1, 11, 33, 111]
omd.poplast(1)
print(list(omd.values())) # prints: [1, 11, 33]
omd.poplast(1)
print(list(omd.values())) # prints: [1, 33]
Method parity with dict is only somewhat retained
from better_orderedmultidict import OrderedMultiDict
d, omd = dict(), OrderedMultiDict()
d.update([(1,1), (1,11), (2,2), (2,22)])
omd.update([(1,1), (1,11), (2,2), (2,22)])
print(d[1], omd[1]) # prints: (11, 11)
d[3] = omd[3] = 3
print(d[3], omd[3]) # prints: (3, 3)
d[3] = omd[3] = 33
print(d[3], omd[3]) # prints: (33, 33)
# But there are differences:
print(len(d), len(omd)) # prints: (3, 5)
d.pop(2)
omd.pop(2)
print(d.get(2), omd.get(2)) # prints: (None, 2)
Iterating over unique keys
from better_orderedmultidict import OrderedMultiDict
omd = OrderedMultiDict([(1,1), (2,2), (1,11), (2,22)])
print(len(omd.keys())) # prints: 4
print(len(omd.unique_keys())) # prints: 2
for key in reversed(omd.unique_keys()):
print(f"{key}: {omd.getall(key)}")
# prints:
# 1: [1, 11]
# 2: [2, 22]
Installation
Installation is simple:
$ pip install better_orderedmultidict
Changelog
See CHANGES.md.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file better_orderedmultidict-0.2.2.tar.gz.
File metadata
- Download URL: better_orderedmultidict-0.2.2.tar.gz
- Upload date:
- Size: 18.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3246f7312aafb8eaf6a47df793e1bd39cceee0cf9978c54217faf338f318f352
|
|
| MD5 |
a86b35db777c0333f45d84305b6f25a6
|
|
| BLAKE2b-256 |
e0441f1ebbe0eb20303eca60761a9a6fe9a7362cf0bd658454e7217dec545e91
|
File details
Details for the file better_orderedmultidict-0.2.2-py3-none-any.whl.
File metadata
- Download URL: better_orderedmultidict-0.2.2-py3-none-any.whl
- Upload date:
- Size: 15.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f2ba875b652fb7568f7b5e2ae60d25d1feed9d38365f9040b321de7facbb63d0
|
|
| MD5 |
4b085fe233561d165d86e73ed5f88f98
|
|
| BLAKE2b-256 |
149fe84c450a1f0b072afb7444a78932200ea43c4b3465b1b900171187c4ce9f
|