pycedar 0.2.2__tar.gz → 0.3.1__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.
Files changed (28) hide show
  1. {pycedar-0.2.2 → pycedar-0.3.1}/CHANGELOG.md +66 -1
  2. {pycedar-0.2.2/pycedar.egg-info → pycedar-0.3.1}/PKG-INFO +31 -9
  3. {pycedar-0.2.2 → pycedar-0.3.1}/README.ja.md +29 -8
  4. {pycedar-0.2.2 → pycedar-0.3.1}/README.md +30 -8
  5. pycedar-0.3.1/pycedar/VERSION +1 -0
  6. {pycedar-0.2.2 → pycedar-0.3.1}/pycedar/pycedar.cpp +6336 -4011
  7. {pycedar-0.2.2 → pycedar-0.3.1}/pycedar/pycedar.pyx +141 -42
  8. {pycedar-0.2.2 → pycedar-0.3.1/pycedar.egg-info}/PKG-INFO +31 -9
  9. {pycedar-0.2.2 → pycedar-0.3.1}/tests/test_pycedar.py +56 -29
  10. {pycedar-0.2.2 → pycedar-0.3.1}/tests/test_trie_api.py +58 -0
  11. pycedar-0.2.2/pycedar/VERSION +0 -1
  12. {pycedar-0.2.2 → pycedar-0.3.1}/MANIFEST.in +0 -0
  13. {pycedar-0.2.2 → pycedar-0.3.1}/pycedar/core/cedar/AUTHORS +0 -0
  14. {pycedar-0.2.2 → pycedar-0.3.1}/pycedar/core/cedar/BSD +0 -0
  15. {pycedar-0.2.2 → pycedar-0.3.1}/pycedar/core/cedar/COPYING +0 -0
  16. {pycedar-0.2.2 → pycedar-0.3.1}/pycedar/core/cedar/GPL +0 -0
  17. {pycedar-0.2.2 → pycedar-0.3.1}/pycedar/core/cedar/LGPL +0 -0
  18. {pycedar-0.2.2 → pycedar-0.3.1}/pycedar/core/cedar/README.md +0 -0
  19. {pycedar-0.2.2 → pycedar-0.3.1}/pycedar/core/cedar/THANKS +0 -0
  20. {pycedar-0.2.2 → pycedar-0.3.1}/pycedar/core/cedar/src/cedarpp.h +0 -0
  21. {pycedar-0.2.2 → pycedar-0.3.1}/pycedar/pycedar.pxd +0 -0
  22. {pycedar-0.2.2 → pycedar-0.3.1}/pycedar.egg-info/SOURCES.txt +0 -0
  23. {pycedar-0.2.2 → pycedar-0.3.1}/pycedar.egg-info/dependency_links.txt +0 -0
  24. {pycedar-0.2.2 → pycedar-0.3.1}/pycedar.egg-info/top_level.txt +0 -0
  25. {pycedar-0.2.2 → pycedar-0.3.1}/pyproject.toml +0 -0
  26. {pycedar-0.2.2 → pycedar-0.3.1}/setup.cfg +0 -0
  27. {pycedar-0.2.2 → pycedar-0.3.1}/setup.py +0 -0
  28. {pycedar-0.2.2 → pycedar-0.3.1}/tests/test_readme.py +0 -0
@@ -7,6 +7,69 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.1] - 2026-09-16
11
+
12
+ ### Changed
13
+
14
+ - Lookups, writes and traversals are faster, with no change in behaviour.
15
+
16
+ `base_trie` declared only the operations that do not depend on the key type.
17
+ Everything else — `exact_match_search()`, `suffix()`, `set()`, `update()`,
18
+ `traverse()`, `erase()` and the prefix queries — lived only on the
19
+ specialisations, while the shared helpers and `pycedar.dict` hold a
20
+ `base_trie` reference. Every one of those calls was therefore a Python method
21
+ lookup by name, with each C integer argument boxed into a Python `int` on the
22
+ way in. They are now declared on `base_trie`, so the calls dispatch through
23
+ the vtable instead. The key parameter is typed as `object` because the three
24
+ specialisations accept different types; each one still validates what it is
25
+ given.
26
+ - `common_prefix_search()` no longer walks the trie twice. cedar advances one
27
+ position per byte of the key, so the result can never exceed `len(key)`;
28
+ sizing the buffer to that removes the counting pass entirely.
29
+ `common_prefix_predict()` has no such bound, so it starts from a speculative
30
+ buffer and retries once, at the exact size cedar reports, only when the
31
+ results do not fit.
32
+
33
+ Measured on 20000 random keys, CPython 3.13, nanoseconds per operation:
34
+
35
+ | operation | 0.3.0 | 0.3.1 |
36
+ | --- | --- | --- |
37
+ | `key in d` | 98 | 52 |
38
+ | `d[key]` | 107 | 61 |
39
+ | `d.get(key)` | 114 | 67 |
40
+ | `d.setdefault(key)` | 103 | 67 |
41
+ | `d.get_node(key)` | 145 | 99 |
42
+ | `d[key] = value` | 78 | 61 |
43
+ | `d.set(key, value)` | 85 | 70 |
44
+ | `d.update(key, delta)` | 90 | 75 |
45
+ | `common_prefix_predict()` | 3568 | 2572 |
46
+ | `list(d.items())` per key | 188 | 163 |
47
+
48
+ `exact_match_search()` called directly on a specialisation stayed at 82 ns,
49
+ as it should: that call was never the dynamic one.
50
+
51
+ ## [0.3.0] - 2026-09-16
52
+
53
+ ### Changed
54
+
55
+ - **Breaking**: storing `-1` or `-2` now raises `ValueError`. They are cedar's
56
+ sentinels, `base_trie.NO_VALUE` and `base_trie.NO_PATH`, and accepting them
57
+ silently corrupted the trie: `-1` made a key invisible to `in`, `get()` and
58
+ `d[key]` while leaving it visible to iteration, and `-2` terminated every
59
+ traversal early, hiding each key stored after it. Anyone affected was already
60
+ unable to read those values back, so the exception replaces silent data loss
61
+ rather than working behaviour.
62
+
63
+ The check lives in the writers shared by both layers, so `d[key] = value`,
64
+ `set()`, `setdefault()` and the trie level `set()` all enforce it.
65
+ `update()` validates the resulting value instead of the delta, because an
66
+ ordinary delta can still land on a sentinel; when it does, the delta is
67
+ rolled back and the stored value is left untouched. A key created by such a
68
+ rejected `update()` is left registered with the value `0`.
69
+
70
+ As a consequence, `get()` returning `NO_VALUE` is now unambiguous: no stored
71
+ value can collide with it.
72
+
10
73
  ## [0.2.2] - 2026-09-16
11
74
 
12
75
  ### Added
@@ -166,7 +229,9 @@ records; the corresponding tags were added retroactively.
166
229
 
167
230
  - Build failure with clang on macOS.
168
231
 
169
- [Unreleased]: https://github.com/akivajp/pycedar/compare/v0.2.2...HEAD
232
+ [Unreleased]: https://github.com/akivajp/pycedar/compare/v0.3.1...HEAD
233
+ [0.3.1]: https://github.com/akivajp/pycedar/compare/v0.3.0...v0.3.1
234
+ [0.3.0]: https://github.com/akivajp/pycedar/compare/v0.2.2...v0.3.0
170
235
  [0.2.2]: https://github.com/akivajp/pycedar/compare/v0.2.1...v0.2.2
171
236
  [0.2.1]: https://github.com/akivajp/pycedar/compare/v0.2.0...v0.2.1
172
237
  [0.2.0]: https://github.com/akivajp/pycedar/compare/v0.1.3...v0.2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycedar
3
- Version: 0.2.2
3
+ Version: 0.3.1
4
4
  Summary: Python binding of cedar (implementation of efficiently-updatable double-array trie) using Cython
5
5
  Home-page: https://github.com/akivajp/pycedar
6
6
  Author: Akiva Miura
@@ -332,16 +332,38 @@ A ``dict``-like façade over a trie. ``pycedar.dict(key_type)`` accepts ``str``
332
332
  Values are stored as C ``int``, so they must fit in ``-2**31 .. 2**31-1``;
333
333
  anything larger raises ``OverflowError``.
334
334
 
335
- Two values collide with cedar's sentinels and must not be stored:
335
+ ``-1`` and ``-2`` are cedar's sentinels — ``base_trie.NO_VALUE`` and
336
+ ``base_trie.NO_PATH`` — and every writer rejects them:
336
337
 
337
- * **``-1`` (``NO_VALUE``)** — the key is stored and appears during iteration,
338
- but ``in``, ``get()`` and ``d[key]`` all report it as absent.
339
- * **``-2`` (``NO_PATH``)** — this value terminates traversal, so ``items()``,
340
- ``keys()``, ``values()``, ``find()`` and friends silently stop at that key and
341
- never reach the rest of the trie.
338
+ ```python
339
+ >>> limited = pycedar.dict()
340
+ >>> limited['key'] = -1
341
+ Traceback (most recent call last):
342
+ ...
343
+ ValueError: -1 is reserved and cannot be stored: ...
344
+ ```
345
+
346
+ ``update()`` checks the resulting value rather than the delta, because a
347
+ perfectly ordinary delta can still land on a sentinel. When it does, the delta
348
+ is rolled back and the stored value is left untouched:
349
+
350
+ ```python
351
+ >>> limited['counter'] = 1
352
+ >>> limited.update('counter', -3) # 1 + (-3) == -2
353
+ Traceback (most recent call last):
354
+ ...
355
+ ValueError: -2 is reserved and cannot be stored: ...
356
+ >>> limited['counter']
357
+ 1
358
+ ```
359
+
360
+ Every other value round trips, negative ones included.
342
361
 
343
- Storing non-negative values avoids both problems. Every other value, including
344
- other negative numbers, round trips normally.
362
+ Before 0.3.0 these two were accepted and silently corrupted the trie: ``-1``
363
+ made a key invisible to ``in``, ``get()`` and ``d[key]`` while leaving it
364
+ visible to iteration, and ``-2`` ended every traversal early, hiding each key
365
+ that came after it. If you are upgrading and were storing either, the values
366
+ were not being read back correctly in the first place.
345
367
 
346
368
  ### The serialization format is platform-dependent and unauthenticated
347
369
 
@@ -291,16 +291,37 @@ applet 2
291
291
  値は C の ``int`` として格納されるため、``-2**31 .. 2**31-1`` に収まる必要が
292
292
  あります。これを超えると ``OverflowError`` になります。
293
293
 
294
- 次の 2 つの値は cedar の番兵値と衝突するため、格納してはいけません。
294
+ ``-1`` と ``-2`` は cedar の番兵値(``base_trie.NO_VALUE`` と
295
+ ``base_trie.NO_PATH``)であり、すべての書き込み経路が拒否します。
295
296
 
296
- * **``-1``(``NO_VALUE``)** — キーは格納され走査にも現れますが、``in``、
297
- ``get()``、``d[key]`` のいずれからも「存在しない」と報告されます。
298
- * **``-2``(``NO_PATH``)** — この値は走査の終端条件そのものであるため、
299
- ``items()``、``keys()``、``values()``、``find()`` などが**そのキーで静かに停止し、
300
- それ以降のトライに到達しなくなります**。
297
+ ```python
298
+ >>> limited = pycedar.dict()
299
+ >>> limited['key'] = -1
300
+ Traceback (most recent call last):
301
+ ...
302
+ ValueError: -1 is reserved and cannot be stored: ...
303
+ ```
304
+
305
+ ``update()`` はデルタではなく**加算結果**を検査します。ごく普通のデルタでも
306
+ 結果が番兵値に着地しうるためです。着地した場合はデルタを巻き戻すので、
307
+ 格納されている値は変化しません。
308
+
309
+ ```python
310
+ >>> limited['counter'] = 1
311
+ >>> limited.update('counter', -3) # 1 + (-3) == -2
312
+ Traceback (most recent call last):
313
+ ...
314
+ ValueError: -2 is reserved and cannot be stored: ...
315
+ >>> limited['counter']
316
+ 1
317
+ ```
318
+
319
+ これ以外の値は、負数を含めて通常どおり往復します。
301
320
 
302
- 値を非負に限れば、いずれの問題も回避できます。それ以外の値は、負数を含めて
303
- 通常どおり往復します。
321
+ 0.3.0 より前はこの 2 つを受け付けてしまい、トライを静かに壊していました。
322
+ ``-1`` はキーを ``in`` / ``get()`` / ``d[key]`` から不可視にする一方で走査には
323
+ 現れ、``-2`` は走査をそこで打ち切って以降のキーをすべて隠していました。
324
+ いずれかを格納していた場合、そもそも正しく読み出せていなかったことになります。
304
325
 
305
326
  ### シリアライズ形式はプラットフォーム依存であり、検証機構を持たない
306
327
 
@@ -294,16 +294,38 @@ A ``dict``-like façade over a trie. ``pycedar.dict(key_type)`` accepts ``str``
294
294
  Values are stored as C ``int``, so they must fit in ``-2**31 .. 2**31-1``;
295
295
  anything larger raises ``OverflowError``.
296
296
 
297
- Two values collide with cedar's sentinels and must not be stored:
297
+ ``-1`` and ``-2`` are cedar's sentinels — ``base_trie.NO_VALUE`` and
298
+ ``base_trie.NO_PATH`` — and every writer rejects them:
298
299
 
299
- * **``-1`` (``NO_VALUE``)** — the key is stored and appears during iteration,
300
- but ``in``, ``get()`` and ``d[key]`` all report it as absent.
301
- * **``-2`` (``NO_PATH``)** — this value terminates traversal, so ``items()``,
302
- ``keys()``, ``values()``, ``find()`` and friends silently stop at that key and
303
- never reach the rest of the trie.
300
+ ```python
301
+ >>> limited = pycedar.dict()
302
+ >>> limited['key'] = -1
303
+ Traceback (most recent call last):
304
+ ...
305
+ ValueError: -1 is reserved and cannot be stored: ...
306
+ ```
307
+
308
+ ``update()`` checks the resulting value rather than the delta, because a
309
+ perfectly ordinary delta can still land on a sentinel. When it does, the delta
310
+ is rolled back and the stored value is left untouched:
311
+
312
+ ```python
313
+ >>> limited['counter'] = 1
314
+ >>> limited.update('counter', -3) # 1 + (-3) == -2
315
+ Traceback (most recent call last):
316
+ ...
317
+ ValueError: -2 is reserved and cannot be stored: ...
318
+ >>> limited['counter']
319
+ 1
320
+ ```
321
+
322
+ Every other value round trips, negative ones included.
304
323
 
305
- Storing non-negative values avoids both problems. Every other value, including
306
- other negative numbers, round trips normally.
324
+ Before 0.3.0 these two were accepted and silently corrupted the trie: ``-1``
325
+ made a key invisible to ``in``, ``get()`` and ``d[key]`` while leaving it
326
+ visible to iteration, and ``-2`` ended every traversal early, hiding each key
327
+ that came after it. If you are upgrading and were storing either, the values
328
+ were not being read back correctly in the first place.
307
329
 
308
330
  ### The serialization format is platform-dependent and unauthenticated
309
331
 
@@ -0,0 +1 @@
1
+ 0.3.1