dequedict 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dequedict-0.1.0/PKG-INFO +104 -0
- dequedict-0.1.0/README.md +82 -0
- dequedict-0.1.0/dequedict/__init__.py +387 -0
- dequedict-0.1.0/dequedict/_dequedict.c +1394 -0
- dequedict-0.1.0/dequedict/_dequedict.pyi +128 -0
- dequedict-0.1.0/dequedict/py.typed +0 -0
- dequedict-0.1.0/dequedict.egg-info/PKG-INFO +104 -0
- dequedict-0.1.0/dequedict.egg-info/SOURCES.txt +13 -0
- dequedict-0.1.0/dequedict.egg-info/dependency_links.txt +1 -0
- dequedict-0.1.0/dequedict.egg-info/requires.txt +5 -0
- dequedict-0.1.0/dequedict.egg-info/top_level.txt +1 -0
- dequedict-0.1.0/pyproject.toml +60 -0
- dequedict-0.1.0/setup.cfg +4 -0
- dequedict-0.1.0/setup.py +12 -0
- dequedict-0.1.0/tests/test_dequedict.py +766 -0
dequedict-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dequedict
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fast ordered dict with deque-like operations at both ends
|
|
5
|
+
Author: sebastian
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: C
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: typing_extensions>=4.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-benchmark; extra == "dev"
|
|
22
|
+
|
|
23
|
+
# DequeDict
|
|
24
|
+
|
|
25
|
+
Ordered dictionary with O(1) deque operations at both ends.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- **O(1) key lookup** — like `dict`
|
|
30
|
+
- **O(1) pop/peek from both ends** — like `deque`
|
|
31
|
+
- **O(1) appendleft, move_to_end, delete by key**
|
|
32
|
+
- **Full typing support** with `.pyi` stubs
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install dequedict
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from dequedict import DequeDict, DefaultDequeDict
|
|
44
|
+
|
|
45
|
+
dd = DequeDict([("a", 1), ("b", 2), ("c", 3)])
|
|
46
|
+
|
|
47
|
+
dd["d"] = 4 # Set
|
|
48
|
+
value = dd["a"] # Get
|
|
49
|
+
del dd["b"] # Delete
|
|
50
|
+
|
|
51
|
+
dd.peekleft() # First value
|
|
52
|
+
dd.popleft() # Remove first
|
|
53
|
+
dd.appendleft("z", 0) # Insert at front
|
|
54
|
+
dd.move_to_end("a") # Move to back
|
|
55
|
+
|
|
56
|
+
# DefaultDequeDict: auto-create values like defaultdict
|
|
57
|
+
groups = DefaultDequeDict(list)
|
|
58
|
+
groups["a"].append(1)
|
|
59
|
+
groups["a"].append(2)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## API
|
|
63
|
+
|
|
64
|
+
| Method | Description |
|
|
65
|
+
|--------|-------------|
|
|
66
|
+
| `peekleft()` / `peek()` | First/last value |
|
|
67
|
+
| `peekleftitem()` / `peekitem()` | First/last (key, value) |
|
|
68
|
+
| `popleft()` / `pop()` | Remove and return first/last |
|
|
69
|
+
| `popleftitem()` / `popitem()` | Remove and return first/last pair |
|
|
70
|
+
| `appendleft(key, value)` | Insert at front |
|
|
71
|
+
| `move_to_end(key, last=True)` | Move to front or back |
|
|
72
|
+
| `get`, `keys`, `values`, `items`, `clear`, `copy`, `update`, `setdefault` | Standard dict ops |
|
|
73
|
+
|
|
74
|
+
## Performance
|
|
75
|
+
|
|
76
|
+
Mac M1, Python 3.11, C extension:
|
|
77
|
+
|
|
78
|
+
| Operation | DequeDict | dict | deque | OrderedDict |
|
|
79
|
+
|-----------|-----------|------|-------|-------------|
|
|
80
|
+
| Key lookup | 32 ns | **23 ns** | O(n) | 24 ns |
|
|
81
|
+
| Peek left | **22 ns** | N/A | 23 ns | 59 ns |
|
|
82
|
+
| Peek right | **22 ns** | N/A | 23 ns | 70 ns |
|
|
83
|
+
| Pop left ×100 | 3.7 µs | N/A | **2.1 µs** | 7.7 µs |
|
|
84
|
+
| Delete by key ×100 | 4.3 µs | **2.8 µs** | O(n) | 6.0 µs |
|
|
85
|
+
| Insert ×100 | **6.6 µs** | 9.5 µs | N/A | 6.8 µs |
|
|
86
|
+
|
|
87
|
+
Pop is ~1.8x slower than deque due to dict maintenance, but provides O(1) key lookup.
|
|
88
|
+
|
|
89
|
+
## Benchmarks
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
python benchmarks/benchmark.py
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Tests
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pip install pytest
|
|
99
|
+
python -m pytest tests/ -v
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# DequeDict
|
|
2
|
+
|
|
3
|
+
Ordered dictionary with O(1) deque operations at both ends.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **O(1) key lookup** — like `dict`
|
|
8
|
+
- **O(1) pop/peek from both ends** — like `deque`
|
|
9
|
+
- **O(1) appendleft, move_to_end, delete by key**
|
|
10
|
+
- **Full typing support** with `.pyi` stubs
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install dequedict
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from dequedict import DequeDict, DefaultDequeDict
|
|
22
|
+
|
|
23
|
+
dd = DequeDict([("a", 1), ("b", 2), ("c", 3)])
|
|
24
|
+
|
|
25
|
+
dd["d"] = 4 # Set
|
|
26
|
+
value = dd["a"] # Get
|
|
27
|
+
del dd["b"] # Delete
|
|
28
|
+
|
|
29
|
+
dd.peekleft() # First value
|
|
30
|
+
dd.popleft() # Remove first
|
|
31
|
+
dd.appendleft("z", 0) # Insert at front
|
|
32
|
+
dd.move_to_end("a") # Move to back
|
|
33
|
+
|
|
34
|
+
# DefaultDequeDict: auto-create values like defaultdict
|
|
35
|
+
groups = DefaultDequeDict(list)
|
|
36
|
+
groups["a"].append(1)
|
|
37
|
+
groups["a"].append(2)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## API
|
|
41
|
+
|
|
42
|
+
| Method | Description |
|
|
43
|
+
|--------|-------------|
|
|
44
|
+
| `peekleft()` / `peek()` | First/last value |
|
|
45
|
+
| `peekleftitem()` / `peekitem()` | First/last (key, value) |
|
|
46
|
+
| `popleft()` / `pop()` | Remove and return first/last |
|
|
47
|
+
| `popleftitem()` / `popitem()` | Remove and return first/last pair |
|
|
48
|
+
| `appendleft(key, value)` | Insert at front |
|
|
49
|
+
| `move_to_end(key, last=True)` | Move to front or back |
|
|
50
|
+
| `get`, `keys`, `values`, `items`, `clear`, `copy`, `update`, `setdefault` | Standard dict ops |
|
|
51
|
+
|
|
52
|
+
## Performance
|
|
53
|
+
|
|
54
|
+
Mac M1, Python 3.11, C extension:
|
|
55
|
+
|
|
56
|
+
| Operation | DequeDict | dict | deque | OrderedDict |
|
|
57
|
+
|-----------|-----------|------|-------|-------------|
|
|
58
|
+
| Key lookup | 32 ns | **23 ns** | O(n) | 24 ns |
|
|
59
|
+
| Peek left | **22 ns** | N/A | 23 ns | 59 ns |
|
|
60
|
+
| Peek right | **22 ns** | N/A | 23 ns | 70 ns |
|
|
61
|
+
| Pop left ×100 | 3.7 µs | N/A | **2.1 µs** | 7.7 µs |
|
|
62
|
+
| Delete by key ×100 | 4.3 µs | **2.8 µs** | O(n) | 6.0 µs |
|
|
63
|
+
| Insert ×100 | **6.6 µs** | 9.5 µs | N/A | 6.8 µs |
|
|
64
|
+
|
|
65
|
+
Pop is ~1.8x slower than deque due to dict maintenance, but provides O(1) key lookup.
|
|
66
|
+
|
|
67
|
+
## Benchmarks
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
python benchmarks/benchmark.py
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Tests
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install pytest
|
|
77
|
+
python -m pytest tests/ -v
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
"""DequeDict - Ordered dictionary with O(1) deque operations at both ends."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
from collections.abc import Iterable, Mapping
|
|
6
|
+
from contextlib import suppress
|
|
7
|
+
from typing import TYPE_CHECKING, Callable, Generic, ItemsView, Iterator, KeysView, TypeVar, ValuesView, overload
|
|
8
|
+
|
|
9
|
+
from typing_extensions import TypeIs
|
|
10
|
+
|
|
11
|
+
__all__ = ["DequeDict", "DefaultDequeDict"]
|
|
12
|
+
|
|
13
|
+
K = TypeVar("K")
|
|
14
|
+
V = TypeVar("V")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _is_iterable_of_pairs(items: object) -> TypeIs[Iterable[tuple[K, V]]]:
|
|
18
|
+
return not isinstance(items, Mapping) and getattr(items, "__iter__", None) is not None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DequeDict(Mapping[K, V]):
|
|
22
|
+
"""Ordered dictionary with O(1) deque operations at both ends.
|
|
23
|
+
|
|
24
|
+
Combines dict interface with deque-like operations:
|
|
25
|
+
- O(1) key lookup, contains, delete
|
|
26
|
+
- O(1) popleft/popleftitem/peekleft/peekleftitem
|
|
27
|
+
- O(1) pop/popitem/peek/peekitem
|
|
28
|
+
- O(1) appendleft
|
|
29
|
+
- O(1) move_to_end
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
__slots__ = ("_dict", "_head", "_tail")
|
|
33
|
+
__hash__ = None # type: ignore[assignment]
|
|
34
|
+
|
|
35
|
+
class _Node:
|
|
36
|
+
__slots__ = ("key", "value", "prev", "next")
|
|
37
|
+
|
|
38
|
+
def __init__(self, key: K, value: V) -> None:
|
|
39
|
+
self.key = key
|
|
40
|
+
self.value = value
|
|
41
|
+
self.prev: DequeDict._Node | None = None
|
|
42
|
+
self.next: DequeDict._Node | None = None
|
|
43
|
+
|
|
44
|
+
def __init__(self, items: Mapping[K, V] | Iterable[tuple[K, V]] | None = None) -> None:
|
|
45
|
+
self._dict: dict[K, DequeDict._Node] = {}
|
|
46
|
+
self._head: DequeDict._Node | None = None
|
|
47
|
+
self._tail: DequeDict._Node | None = None
|
|
48
|
+
if items is not None:
|
|
49
|
+
if _is_iterable_of_pairs(items):
|
|
50
|
+
for k, v in items:
|
|
51
|
+
self[k] = v
|
|
52
|
+
else:
|
|
53
|
+
for k, v in items.items():
|
|
54
|
+
self[k] = v
|
|
55
|
+
|
|
56
|
+
def __len__(self) -> int:
|
|
57
|
+
return len(self._dict)
|
|
58
|
+
|
|
59
|
+
def __contains__(self, key: object) -> bool:
|
|
60
|
+
return key in self._dict
|
|
61
|
+
|
|
62
|
+
def __getitem__(self, key: K) -> V:
|
|
63
|
+
if key not in self._dict:
|
|
64
|
+
raise KeyError(key)
|
|
65
|
+
return self._dict[key].value
|
|
66
|
+
|
|
67
|
+
def __setitem__(self, key: K, value: V) -> None:
|
|
68
|
+
if key in self._dict:
|
|
69
|
+
self._dict[key].value = value
|
|
70
|
+
return
|
|
71
|
+
node = self._Node(key, value)
|
|
72
|
+
self._dict[key] = node
|
|
73
|
+
if self._tail is None:
|
|
74
|
+
self._head = self._tail = node
|
|
75
|
+
else:
|
|
76
|
+
node.prev = self._tail
|
|
77
|
+
self._tail.next = node
|
|
78
|
+
self._tail = node
|
|
79
|
+
|
|
80
|
+
def __delitem__(self, key: K) -> None:
|
|
81
|
+
if key not in self._dict:
|
|
82
|
+
raise KeyError(key)
|
|
83
|
+
node = self._dict.pop(key)
|
|
84
|
+
self._unlink(node)
|
|
85
|
+
|
|
86
|
+
def _unlink(self, node: _Node) -> None:
|
|
87
|
+
if node.prev:
|
|
88
|
+
node.prev.next = node.next
|
|
89
|
+
else:
|
|
90
|
+
self._head = node.next
|
|
91
|
+
if node.next:
|
|
92
|
+
node.next.prev = node.prev
|
|
93
|
+
else:
|
|
94
|
+
self._tail = node.prev
|
|
95
|
+
|
|
96
|
+
def __iter__(self) -> Iterator[K]:
|
|
97
|
+
node = self._head
|
|
98
|
+
while node:
|
|
99
|
+
yield node.key
|
|
100
|
+
node = node.next
|
|
101
|
+
|
|
102
|
+
def __reversed__(self) -> Iterator[K]:
|
|
103
|
+
node = self._tail
|
|
104
|
+
while node:
|
|
105
|
+
yield node.key
|
|
106
|
+
node = node.prev
|
|
107
|
+
|
|
108
|
+
def __repr__(self) -> str:
|
|
109
|
+
if not self._dict:
|
|
110
|
+
return "DequeDict()"
|
|
111
|
+
items = [(n.key, n.value) for n in self._iter_nodes()]
|
|
112
|
+
return f"DequeDict({items!r})"
|
|
113
|
+
|
|
114
|
+
def __eq__(self, other: object) -> bool:
|
|
115
|
+
if not isinstance(other, (dict, DequeDict)):
|
|
116
|
+
return NotImplemented
|
|
117
|
+
if len(self) != len(other):
|
|
118
|
+
return False
|
|
119
|
+
return all(not (k not in other or other[k] != v) for k, v in self.items())
|
|
120
|
+
|
|
121
|
+
def _iter_nodes(self) -> Iterator[_Node]:
|
|
122
|
+
node = self._head
|
|
123
|
+
while node:
|
|
124
|
+
yield node
|
|
125
|
+
node = node.next
|
|
126
|
+
|
|
127
|
+
def peekleft(self) -> V:
|
|
128
|
+
"""Return first value without removing."""
|
|
129
|
+
if self._head is None:
|
|
130
|
+
raise IndexError("peek from an empty DequeDict")
|
|
131
|
+
return self._head.value
|
|
132
|
+
|
|
133
|
+
def peekleftitem(self) -> tuple[K, V]:
|
|
134
|
+
"""Return first (key, value) without removing."""
|
|
135
|
+
if self._head is None:
|
|
136
|
+
raise IndexError("peek from an empty DequeDict")
|
|
137
|
+
return (self._head.key, self._head.value)
|
|
138
|
+
|
|
139
|
+
def peekleftkey(self) -> K:
|
|
140
|
+
"""Return first key without removing."""
|
|
141
|
+
if self._head is None:
|
|
142
|
+
raise IndexError("peek from an empty DequeDict")
|
|
143
|
+
return self._head.key
|
|
144
|
+
|
|
145
|
+
def peek(self) -> V:
|
|
146
|
+
"""Return last value without removing."""
|
|
147
|
+
if self._tail is None:
|
|
148
|
+
raise IndexError("peek from an empty DequeDict")
|
|
149
|
+
return self._tail.value
|
|
150
|
+
|
|
151
|
+
def peekitem(self) -> tuple[K, V]:
|
|
152
|
+
"""Return last (key, value) without removing."""
|
|
153
|
+
if self._tail is None:
|
|
154
|
+
raise IndexError("peek from an empty DequeDict")
|
|
155
|
+
return (self._tail.key, self._tail.value)
|
|
156
|
+
|
|
157
|
+
def popleft(self) -> V:
|
|
158
|
+
"""Remove and return first value."""
|
|
159
|
+
if self._head is None:
|
|
160
|
+
raise IndexError("pop from an empty DequeDict")
|
|
161
|
+
node = self._head
|
|
162
|
+
del self._dict[node.key]
|
|
163
|
+
self._unlink(node)
|
|
164
|
+
return node.value
|
|
165
|
+
|
|
166
|
+
def popleftitem(self) -> tuple[K, V]:
|
|
167
|
+
"""Remove and return first (key, value)."""
|
|
168
|
+
if self._head is None:
|
|
169
|
+
raise KeyError("popleftitem from an empty DequeDict")
|
|
170
|
+
node = self._head
|
|
171
|
+
del self._dict[node.key]
|
|
172
|
+
self._unlink(node)
|
|
173
|
+
return (node.key, node.value)
|
|
174
|
+
|
|
175
|
+
@overload
|
|
176
|
+
def pop(self) -> V: ...
|
|
177
|
+
@overload
|
|
178
|
+
def pop(self, key: K) -> V: ...
|
|
179
|
+
@overload
|
|
180
|
+
def pop(self, key: K, default: V) -> V: ...
|
|
181
|
+
|
|
182
|
+
def pop(self, key: K | None = None, default: V | None = None) -> V: # type: ignore[misc]
|
|
183
|
+
"""Remove and return value by key, or from end if no key given."""
|
|
184
|
+
if key is None:
|
|
185
|
+
if self._tail is None:
|
|
186
|
+
if default is not None:
|
|
187
|
+
return default
|
|
188
|
+
raise IndexError("pop from an empty DequeDict")
|
|
189
|
+
node = self._tail
|
|
190
|
+
del self._dict[node.key]
|
|
191
|
+
self._unlink(node)
|
|
192
|
+
return node.value
|
|
193
|
+
if key not in self._dict:
|
|
194
|
+
if default is not None:
|
|
195
|
+
return default
|
|
196
|
+
raise KeyError(key)
|
|
197
|
+
node = self._dict.pop(key)
|
|
198
|
+
self._unlink(node)
|
|
199
|
+
return node.value
|
|
200
|
+
|
|
201
|
+
def popitem(self) -> tuple[K, V]:
|
|
202
|
+
"""Remove and return last (key, value)."""
|
|
203
|
+
if self._tail is None:
|
|
204
|
+
raise KeyError("popitem from an empty DequeDict")
|
|
205
|
+
node = self._tail
|
|
206
|
+
del self._dict[node.key]
|
|
207
|
+
self._unlink(node)
|
|
208
|
+
return (node.key, node.value)
|
|
209
|
+
|
|
210
|
+
def appendleft(self, key: K, value: V) -> None:
|
|
211
|
+
"""Insert (key, value) at front."""
|
|
212
|
+
if key in self._dict:
|
|
213
|
+
raise KeyError("key already exists")
|
|
214
|
+
node = self._Node(key, value)
|
|
215
|
+
self._dict[key] = node
|
|
216
|
+
if self._head is None:
|
|
217
|
+
self._head = self._tail = node
|
|
218
|
+
else:
|
|
219
|
+
node.next = self._head
|
|
220
|
+
self._head.prev = node
|
|
221
|
+
self._head = node
|
|
222
|
+
|
|
223
|
+
def move_to_end(self, key: K, last: bool = True) -> None:
|
|
224
|
+
"""Move existing key to front (last=False) or back (last=True)."""
|
|
225
|
+
if key not in self._dict:
|
|
226
|
+
raise KeyError(key)
|
|
227
|
+
node = self._dict[key]
|
|
228
|
+
if (last and node is self._tail) or (not last and node is self._head):
|
|
229
|
+
return
|
|
230
|
+
self._unlink(node)
|
|
231
|
+
if last:
|
|
232
|
+
node.prev = self._tail
|
|
233
|
+
node.next = None
|
|
234
|
+
if self._tail:
|
|
235
|
+
self._tail.next = node
|
|
236
|
+
else:
|
|
237
|
+
self._head = node
|
|
238
|
+
self._tail = node
|
|
239
|
+
else:
|
|
240
|
+
node.prev = None
|
|
241
|
+
node.next = self._head
|
|
242
|
+
if self._head:
|
|
243
|
+
self._head.prev = node
|
|
244
|
+
else:
|
|
245
|
+
self._tail = node
|
|
246
|
+
self._head = node
|
|
247
|
+
|
|
248
|
+
def get(self, key: K, default: V | None = None) -> V | None:
|
|
249
|
+
"""Return value for key, or default if key not present."""
|
|
250
|
+
if key not in self._dict:
|
|
251
|
+
return default
|
|
252
|
+
return self._dict[key].value
|
|
253
|
+
|
|
254
|
+
def keys(self) -> KeysView[K]:
|
|
255
|
+
"""Return view of keys in insertion order."""
|
|
256
|
+
return _DequeDictKeysView(self)
|
|
257
|
+
|
|
258
|
+
def values(self) -> ValuesView[V]:
|
|
259
|
+
"""Return view of values in insertion order."""
|
|
260
|
+
return _DequeDictValuesView(self)
|
|
261
|
+
|
|
262
|
+
def items(self) -> ItemsView[K, V]:
|
|
263
|
+
"""Return view of (key, value) pairs in insertion order."""
|
|
264
|
+
return _DequeDictItemsView(self)
|
|
265
|
+
|
|
266
|
+
def clear(self) -> None:
|
|
267
|
+
"""Remove all items."""
|
|
268
|
+
self._dict.clear()
|
|
269
|
+
self._head = None
|
|
270
|
+
self._tail = None
|
|
271
|
+
|
|
272
|
+
def copy(self) -> DequeDict[K, V]:
|
|
273
|
+
"""Return a shallow copy."""
|
|
274
|
+
return DequeDict(self.items())
|
|
275
|
+
|
|
276
|
+
def update(self, other: Mapping[K, V] | Iterable[tuple[K, V]] | None = None, **kwargs: V) -> None:
|
|
277
|
+
"""Update from dict, iterable of pairs, or keyword arguments."""
|
|
278
|
+
if other is not None:
|
|
279
|
+
if isinstance(other, Mapping):
|
|
280
|
+
for k, v in other.items():
|
|
281
|
+
self[k] = v
|
|
282
|
+
else:
|
|
283
|
+
for k, v in other:
|
|
284
|
+
self[k] = v
|
|
285
|
+
for k, v in kwargs.items():
|
|
286
|
+
self[k] = v # type: ignore[index]
|
|
287
|
+
|
|
288
|
+
def setdefault(self, key: K, default: V | None = None) -> V | None:
|
|
289
|
+
"""Return value for key, setting default if not present."""
|
|
290
|
+
if key in self._dict:
|
|
291
|
+
return self._dict[key].value
|
|
292
|
+
self[key] = default # type: ignore[assignment]
|
|
293
|
+
return default
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class _DequeDictKeysView(KeysView[K]):
|
|
297
|
+
__slots__ = ("_dd",)
|
|
298
|
+
|
|
299
|
+
def __init__(self, dd: DequeDict[K, V]) -> None:
|
|
300
|
+
self._dd = dd
|
|
301
|
+
|
|
302
|
+
def __len__(self) -> int:
|
|
303
|
+
return len(self._dd)
|
|
304
|
+
|
|
305
|
+
def __iter__(self) -> Iterator[K]:
|
|
306
|
+
return iter(self._dd)
|
|
307
|
+
|
|
308
|
+
def __contains__(self, key: object) -> bool:
|
|
309
|
+
return key in self._dd
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class _DequeDictValuesView(ValuesView[V]):
|
|
313
|
+
__slots__ = ("_dd",)
|
|
314
|
+
|
|
315
|
+
def __init__(self, dd: DequeDict[K, V]) -> None:
|
|
316
|
+
self._dd = dd
|
|
317
|
+
|
|
318
|
+
def __len__(self) -> int:
|
|
319
|
+
return len(self._dd)
|
|
320
|
+
|
|
321
|
+
def __iter__(self) -> Iterator[V]:
|
|
322
|
+
for node in self._dd._iter_nodes():
|
|
323
|
+
yield node.value
|
|
324
|
+
|
|
325
|
+
def __contains__(self, value: object) -> bool:
|
|
326
|
+
return any(node.value == value for node in self._dd._iter_nodes())
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class _DequeDictItemsView(ItemsView[K, V]):
|
|
330
|
+
__slots__ = ("_dd",)
|
|
331
|
+
|
|
332
|
+
def __init__(self, dd: DequeDict[K, V]) -> None:
|
|
333
|
+
self._dd = dd
|
|
334
|
+
|
|
335
|
+
def __len__(self) -> int:
|
|
336
|
+
return len(self._dd)
|
|
337
|
+
|
|
338
|
+
def __iter__(self) -> Iterator[tuple[K, V]]:
|
|
339
|
+
for node in self._dd._iter_nodes():
|
|
340
|
+
yield (node.key, node.value)
|
|
341
|
+
|
|
342
|
+
def __contains__(self, item: object) -> bool:
|
|
343
|
+
if not isinstance(item, tuple) or len(item) != 2:
|
|
344
|
+
return False
|
|
345
|
+
k, v = item
|
|
346
|
+
if k not in self._dd:
|
|
347
|
+
return False
|
|
348
|
+
return self._dd[k] == v
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class DefaultDequeDict(DequeDict[K, V]):
|
|
352
|
+
"""DequeDict with default_factory for missing keys, like collections.defaultdict."""
|
|
353
|
+
|
|
354
|
+
__slots__ = ("default_factory",)
|
|
355
|
+
|
|
356
|
+
def __init__(
|
|
357
|
+
self,
|
|
358
|
+
default_factory: Callable[[], V] | None = None,
|
|
359
|
+
items: Mapping[K, V] | Iterable[tuple[K, V]] | None = None,
|
|
360
|
+
) -> None:
|
|
361
|
+
self.default_factory = default_factory
|
|
362
|
+
super().__init__(items)
|
|
363
|
+
|
|
364
|
+
def __missing__(self, key: K) -> V:
|
|
365
|
+
if self.default_factory is None:
|
|
366
|
+
raise KeyError(key)
|
|
367
|
+
value = self.default_factory()
|
|
368
|
+
self[key] = value
|
|
369
|
+
return value
|
|
370
|
+
|
|
371
|
+
def __getitem__(self, key: K) -> V:
|
|
372
|
+
try:
|
|
373
|
+
return super().__getitem__(key)
|
|
374
|
+
except KeyError:
|
|
375
|
+
return self.__missing__(key)
|
|
376
|
+
|
|
377
|
+
def __repr__(self) -> str:
|
|
378
|
+
return f"DefaultDequeDict({self.default_factory}, {list(self.items())!r})"
|
|
379
|
+
|
|
380
|
+
def copy(self) -> DefaultDequeDict[K, V]:
|
|
381
|
+
return DefaultDequeDict(self.default_factory, self.items())
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
# Use C extension if available (disable with NOC=1 environment variable)
|
|
385
|
+
if not TYPE_CHECKING and not os.getenv("NOC"):
|
|
386
|
+
with suppress(ImportError):
|
|
387
|
+
from dequedict._dequedict import DequeDict
|