exonware-xwnode 0.0.1.12__py3-none-any.whl

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 (132) hide show
  1. exonware/__init__.py +14 -0
  2. exonware/xwnode/__init__.py +127 -0
  3. exonware/xwnode/base.py +676 -0
  4. exonware/xwnode/config.py +178 -0
  5. exonware/xwnode/contracts.py +730 -0
  6. exonware/xwnode/errors.py +503 -0
  7. exonware/xwnode/facade.py +460 -0
  8. exonware/xwnode/strategies/__init__.py +158 -0
  9. exonware/xwnode/strategies/advisor.py +463 -0
  10. exonware/xwnode/strategies/edges/__init__.py +32 -0
  11. exonware/xwnode/strategies/edges/adj_list.py +227 -0
  12. exonware/xwnode/strategies/edges/adj_matrix.py +391 -0
  13. exonware/xwnode/strategies/edges/base.py +169 -0
  14. exonware/xwnode/strategies/flyweight.py +328 -0
  15. exonware/xwnode/strategies/impls/__init__.py +13 -0
  16. exonware/xwnode/strategies/impls/_base_edge.py +403 -0
  17. exonware/xwnode/strategies/impls/_base_node.py +307 -0
  18. exonware/xwnode/strategies/impls/edge_adj_list.py +353 -0
  19. exonware/xwnode/strategies/impls/edge_adj_matrix.py +445 -0
  20. exonware/xwnode/strategies/impls/edge_bidir_wrapper.py +455 -0
  21. exonware/xwnode/strategies/impls/edge_block_adj_matrix.py +539 -0
  22. exonware/xwnode/strategies/impls/edge_coo.py +533 -0
  23. exonware/xwnode/strategies/impls/edge_csc.py +447 -0
  24. exonware/xwnode/strategies/impls/edge_csr.py +492 -0
  25. exonware/xwnode/strategies/impls/edge_dynamic_adj_list.py +503 -0
  26. exonware/xwnode/strategies/impls/edge_flow_network.py +555 -0
  27. exonware/xwnode/strategies/impls/edge_hyperedge_set.py +516 -0
  28. exonware/xwnode/strategies/impls/edge_neural_graph.py +650 -0
  29. exonware/xwnode/strategies/impls/edge_octree.py +574 -0
  30. exonware/xwnode/strategies/impls/edge_property_store.py +655 -0
  31. exonware/xwnode/strategies/impls/edge_quadtree.py +519 -0
  32. exonware/xwnode/strategies/impls/edge_rtree.py +820 -0
  33. exonware/xwnode/strategies/impls/edge_temporal_edgeset.py +558 -0
  34. exonware/xwnode/strategies/impls/edge_tree_graph_basic.py +271 -0
  35. exonware/xwnode/strategies/impls/edge_weighted_graph.py +411 -0
  36. exonware/xwnode/strategies/manager.py +775 -0
  37. exonware/xwnode/strategies/metrics.py +538 -0
  38. exonware/xwnode/strategies/migration.py +432 -0
  39. exonware/xwnode/strategies/nodes/__init__.py +50 -0
  40. exonware/xwnode/strategies/nodes/_base_node.py +307 -0
  41. exonware/xwnode/strategies/nodes/adjacency_list.py +267 -0
  42. exonware/xwnode/strategies/nodes/aho_corasick.py +345 -0
  43. exonware/xwnode/strategies/nodes/array_list.py +209 -0
  44. exonware/xwnode/strategies/nodes/base.py +247 -0
  45. exonware/xwnode/strategies/nodes/deque.py +200 -0
  46. exonware/xwnode/strategies/nodes/hash_map.py +135 -0
  47. exonware/xwnode/strategies/nodes/heap.py +307 -0
  48. exonware/xwnode/strategies/nodes/linked_list.py +232 -0
  49. exonware/xwnode/strategies/nodes/node_aho_corasick.py +520 -0
  50. exonware/xwnode/strategies/nodes/node_array_list.py +175 -0
  51. exonware/xwnode/strategies/nodes/node_avl_tree.py +371 -0
  52. exonware/xwnode/strategies/nodes/node_b_plus_tree.py +542 -0
  53. exonware/xwnode/strategies/nodes/node_bitmap.py +420 -0
  54. exonware/xwnode/strategies/nodes/node_bitset_dynamic.py +513 -0
  55. exonware/xwnode/strategies/nodes/node_bloom_filter.py +347 -0
  56. exonware/xwnode/strategies/nodes/node_btree.py +357 -0
  57. exonware/xwnode/strategies/nodes/node_count_min_sketch.py +470 -0
  58. exonware/xwnode/strategies/nodes/node_cow_tree.py +473 -0
  59. exonware/xwnode/strategies/nodes/node_cuckoo_hash.py +392 -0
  60. exonware/xwnode/strategies/nodes/node_fenwick_tree.py +301 -0
  61. exonware/xwnode/strategies/nodes/node_hash_map.py +269 -0
  62. exonware/xwnode/strategies/nodes/node_heap.py +191 -0
  63. exonware/xwnode/strategies/nodes/node_hyperloglog.py +407 -0
  64. exonware/xwnode/strategies/nodes/node_linked_list.py +409 -0
  65. exonware/xwnode/strategies/nodes/node_lsm_tree.py +400 -0
  66. exonware/xwnode/strategies/nodes/node_ordered_map.py +390 -0
  67. exonware/xwnode/strategies/nodes/node_ordered_map_balanced.py +565 -0
  68. exonware/xwnode/strategies/nodes/node_patricia.py +512 -0
  69. exonware/xwnode/strategies/nodes/node_persistent_tree.py +378 -0
  70. exonware/xwnode/strategies/nodes/node_radix_trie.py +452 -0
  71. exonware/xwnode/strategies/nodes/node_red_black_tree.py +497 -0
  72. exonware/xwnode/strategies/nodes/node_roaring_bitmap.py +570 -0
  73. exonware/xwnode/strategies/nodes/node_segment_tree.py +289 -0
  74. exonware/xwnode/strategies/nodes/node_set_hash.py +354 -0
  75. exonware/xwnode/strategies/nodes/node_set_tree.py +480 -0
  76. exonware/xwnode/strategies/nodes/node_skip_list.py +316 -0
  77. exonware/xwnode/strategies/nodes/node_splay_tree.py +393 -0
  78. exonware/xwnode/strategies/nodes/node_suffix_array.py +487 -0
  79. exonware/xwnode/strategies/nodes/node_treap.py +387 -0
  80. exonware/xwnode/strategies/nodes/node_tree_graph_hybrid.py +1434 -0
  81. exonware/xwnode/strategies/nodes/node_trie.py +252 -0
  82. exonware/xwnode/strategies/nodes/node_union_find.py +187 -0
  83. exonware/xwnode/strategies/nodes/node_xdata_optimized.py +369 -0
  84. exonware/xwnode/strategies/nodes/priority_queue.py +209 -0
  85. exonware/xwnode/strategies/nodes/queue.py +161 -0
  86. exonware/xwnode/strategies/nodes/sparse_matrix.py +206 -0
  87. exonware/xwnode/strategies/nodes/stack.py +152 -0
  88. exonware/xwnode/strategies/nodes/trie.py +274 -0
  89. exonware/xwnode/strategies/nodes/union_find.py +283 -0
  90. exonware/xwnode/strategies/pattern_detector.py +603 -0
  91. exonware/xwnode/strategies/performance_monitor.py +487 -0
  92. exonware/xwnode/strategies/queries/__init__.py +24 -0
  93. exonware/xwnode/strategies/queries/base.py +236 -0
  94. exonware/xwnode/strategies/queries/cql.py +201 -0
  95. exonware/xwnode/strategies/queries/cypher.py +181 -0
  96. exonware/xwnode/strategies/queries/datalog.py +70 -0
  97. exonware/xwnode/strategies/queries/elastic_dsl.py +70 -0
  98. exonware/xwnode/strategies/queries/eql.py +70 -0
  99. exonware/xwnode/strategies/queries/flux.py +70 -0
  100. exonware/xwnode/strategies/queries/gql.py +70 -0
  101. exonware/xwnode/strategies/queries/graphql.py +240 -0
  102. exonware/xwnode/strategies/queries/gremlin.py +181 -0
  103. exonware/xwnode/strategies/queries/hiveql.py +214 -0
  104. exonware/xwnode/strategies/queries/hql.py +70 -0
  105. exonware/xwnode/strategies/queries/jmespath.py +219 -0
  106. exonware/xwnode/strategies/queries/jq.py +66 -0
  107. exonware/xwnode/strategies/queries/json_query.py +66 -0
  108. exonware/xwnode/strategies/queries/jsoniq.py +248 -0
  109. exonware/xwnode/strategies/queries/kql.py +70 -0
  110. exonware/xwnode/strategies/queries/linq.py +238 -0
  111. exonware/xwnode/strategies/queries/logql.py +70 -0
  112. exonware/xwnode/strategies/queries/mql.py +68 -0
  113. exonware/xwnode/strategies/queries/n1ql.py +210 -0
  114. exonware/xwnode/strategies/queries/partiql.py +70 -0
  115. exonware/xwnode/strategies/queries/pig.py +215 -0
  116. exonware/xwnode/strategies/queries/promql.py +70 -0
  117. exonware/xwnode/strategies/queries/sparql.py +220 -0
  118. exonware/xwnode/strategies/queries/sql.py +275 -0
  119. exonware/xwnode/strategies/queries/xml_query.py +66 -0
  120. exonware/xwnode/strategies/queries/xpath.py +223 -0
  121. exonware/xwnode/strategies/queries/xquery.py +258 -0
  122. exonware/xwnode/strategies/queries/xwnode_executor.py +332 -0
  123. exonware/xwnode/strategies/queries/xwquery_strategy.py +424 -0
  124. exonware/xwnode/strategies/registry.py +604 -0
  125. exonware/xwnode/strategies/simple.py +273 -0
  126. exonware/xwnode/strategies/utils.py +532 -0
  127. exonware/xwnode/types.py +912 -0
  128. exonware/xwnode/version.py +78 -0
  129. exonware_xwnode-0.0.1.12.dist-info/METADATA +169 -0
  130. exonware_xwnode-0.0.1.12.dist-info/RECORD +132 -0
  131. exonware_xwnode-0.0.1.12.dist-info/WHEEL +4 -0
  132. exonware_xwnode-0.0.1.12.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,390 @@
1
+ """
2
+ Ordered Map Node Strategy Implementation
3
+
4
+ This module implements the ORDERED_MAP strategy for sorted key-value
5
+ operations with efficient range queries and ordered iteration.
6
+ """
7
+
8
+ from typing import Any, Iterator, List, Dict, Optional, Tuple
9
+ import bisect
10
+ from .base import ANodeTreeStrategy
11
+ from ...types import NodeMode, NodeTrait
12
+
13
+
14
+ class OrderedMapStrategy(ANodeTreeStrategy):
15
+ """
16
+ Ordered Map node strategy for sorted key-value operations.
17
+
18
+ Maintains keys in sorted order for efficient range queries,
19
+ ordered iteration, and logarithmic search operations.
20
+ """
21
+
22
+ def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
23
+ """Initialize the Ordered Map strategy."""
24
+ super().__init__(NodeMode.ORDERED_MAP, traits, **options)
25
+
26
+ self.case_sensitive = options.get('case_sensitive', True)
27
+ self.allow_duplicates = options.get('allow_duplicates', False)
28
+
29
+ # Core storage: parallel sorted arrays
30
+ self._keys: List[str] = []
31
+ self._values: List[Any] = []
32
+ self._size = 0
33
+
34
+ def get_supported_traits(self) -> NodeTrait:
35
+ """Get the traits supported by the ordered map strategy."""
36
+ return (NodeTrait.ORDERED | NodeTrait.INDEXED | NodeTrait.HIERARCHICAL)
37
+
38
+ def _normalize_key(self, key: str) -> str:
39
+ """Normalize key based on case sensitivity."""
40
+ return key if self.case_sensitive else key.lower()
41
+
42
+ def _find_key_position(self, key: str) -> int:
43
+ """Find position where key should be inserted (or exists)."""
44
+ normalized_key = self._normalize_key(key)
45
+ return bisect.bisect_left(self._keys, normalized_key)
46
+
47
+ def _insert_at_position(self, position: int, key: str, value: Any) -> None:
48
+ """Insert key-value pair at specific position."""
49
+ normalized_key = self._normalize_key(key)
50
+ self._keys.insert(position, normalized_key)
51
+ self._values.insert(position, value)
52
+ self._size += 1
53
+
54
+ def _remove_at_position(self, position: int) -> Any:
55
+ """Remove key-value pair at specific position."""
56
+ if 0 <= position < self._size:
57
+ self._keys.pop(position)
58
+ value = self._values.pop(position)
59
+ self._size -= 1
60
+ return value
61
+ return None
62
+
63
+ # ============================================================================
64
+ # CORE OPERATIONS
65
+ # ============================================================================
66
+
67
+ def put(self, key: Any, value: Any = None) -> None:
68
+ """Add/update key-value pair in sorted order."""
69
+ key_str = str(key)
70
+ normalized_key = self._normalize_key(key_str)
71
+ position = self._find_key_position(key_str)
72
+
73
+ # Check if key already exists
74
+ if (position < self._size and
75
+ self._keys[position] == normalized_key):
76
+ if not self.allow_duplicates:
77
+ # Update existing key
78
+ self._values[position] = value
79
+ return
80
+
81
+ # Insert new key-value pair
82
+ self._insert_at_position(position, key_str, value)
83
+
84
+ def get(self, key: Any, default: Any = None) -> Any:
85
+ """Get value by key."""
86
+ key_str = str(key)
87
+
88
+ if key_str == "sorted_keys":
89
+ return self._keys.copy()
90
+ elif key_str == "sorted_values":
91
+ return self._values.copy()
92
+ elif key_str == "map_info":
93
+ return {
94
+ 'size': self._size,
95
+ 'case_sensitive': self.case_sensitive,
96
+ 'allow_duplicates': self.allow_duplicates,
97
+ 'first_key': self.first_key(),
98
+ 'last_key': self.last_key()
99
+ }
100
+ elif key_str.isdigit():
101
+ # Numeric access by index
102
+ index = int(key_str)
103
+ if 0 <= index < self._size:
104
+ return self._values[index]
105
+ return default
106
+
107
+ normalized_key = self._normalize_key(key_str)
108
+ position = self._find_key_position(key_str)
109
+
110
+ if (position < self._size and
111
+ self._keys[position] == normalized_key):
112
+ return self._values[position]
113
+
114
+ return default
115
+
116
+ def has(self, key: Any) -> bool:
117
+ """Check if key exists."""
118
+ key_str = str(key)
119
+
120
+ if key_str in ["sorted_keys", "sorted_values", "map_info"]:
121
+ return True
122
+ elif key_str.isdigit():
123
+ index = int(key_str)
124
+ return 0 <= index < self._size
125
+
126
+ normalized_key = self._normalize_key(key_str)
127
+ position = self._find_key_position(key_str)
128
+
129
+ return (position < self._size and
130
+ self._keys[position] == normalized_key)
131
+
132
+ def remove(self, key: Any) -> bool:
133
+ """Remove key from map."""
134
+ key_str = str(key)
135
+
136
+ if key_str.isdigit():
137
+ # Remove by index
138
+ index = int(key_str)
139
+ if 0 <= index < self._size:
140
+ self._remove_at_position(index)
141
+ return True
142
+ return False
143
+
144
+ normalized_key = self._normalize_key(key_str)
145
+ position = self._find_key_position(key_str)
146
+
147
+ if (position < self._size and
148
+ self._keys[position] == normalized_key):
149
+ self._remove_at_position(position)
150
+ return True
151
+
152
+ return False
153
+
154
+ def delete(self, key: Any) -> bool:
155
+ """Remove key from map (alias for remove)."""
156
+ return self.remove(key)
157
+
158
+ def clear(self) -> None:
159
+ """Clear all data."""
160
+ self._keys.clear()
161
+ self._values.clear()
162
+ self._size = 0
163
+
164
+ def keys(self) -> Iterator[str]:
165
+ """Get all keys in sorted order."""
166
+ for key in self._keys:
167
+ yield key
168
+
169
+ def values(self) -> Iterator[Any]:
170
+ """Get all values in key order."""
171
+ for value in self._values:
172
+ yield value
173
+
174
+ def items(self) -> Iterator[tuple[str, Any]]:
175
+ """Get all key-value pairs in sorted order."""
176
+ for key, value in zip(self._keys, self._values):
177
+ yield (key, value)
178
+
179
+ def __len__(self) -> int:
180
+ """Get number of key-value pairs."""
181
+ return self._size
182
+
183
+ def to_native(self) -> Dict[str, Any]:
184
+ """Convert to native Python dict (preserves insertion order in Python 3.7+)."""
185
+ return dict(zip(self._keys, self._values))
186
+
187
+ @property
188
+ def is_list(self) -> bool:
189
+ """This can behave like a list for indexed access."""
190
+ return True
191
+
192
+ @property
193
+ def is_dict(self) -> bool:
194
+ """This is a dict-like structure."""
195
+ return True
196
+
197
+ # ============================================================================
198
+ # ORDERED MAP SPECIFIC OPERATIONS
199
+ # ============================================================================
200
+
201
+ def first_key(self) -> Optional[str]:
202
+ """Get first (smallest) key."""
203
+ return self._keys[0] if self._size > 0 else None
204
+
205
+ def last_key(self) -> Optional[str]:
206
+ """Get last (largest) key."""
207
+ return self._keys[-1] if self._size > 0 else None
208
+
209
+ def first_value(self) -> Any:
210
+ """Get value of first key."""
211
+ return self._values[0] if self._size > 0 else None
212
+
213
+ def last_value(self) -> Any:
214
+ """Get value of last key."""
215
+ return self._values[-1] if self._size > 0 else None
216
+
217
+ def get_range(self, start_key: str, end_key: str, inclusive: bool = True) -> List[Tuple[str, Any]]:
218
+ """Get key-value pairs in range [start_key, end_key]."""
219
+ start_norm = self._normalize_key(start_key)
220
+ end_norm = self._normalize_key(end_key)
221
+
222
+ result = []
223
+ for key, value in zip(self._keys, self._values):
224
+ if inclusive:
225
+ if start_norm <= key <= end_norm:
226
+ result.append((key, value))
227
+ else:
228
+ if start_norm < key < end_norm:
229
+ result.append((key, value))
230
+
231
+ return result
232
+
233
+ def get_keys_range(self, start_key: str, end_key: str, inclusive: bool = True) -> List[str]:
234
+ """Get keys in range."""
235
+ range_items = self.get_range(start_key, end_key, inclusive)
236
+ return [key for key, _ in range_items]
237
+
238
+ def get_values_range(self, start_key: str, end_key: str, inclusive: bool = True) -> List[Any]:
239
+ """Get values in key range."""
240
+ range_items = self.get_range(start_key, end_key, inclusive)
241
+ return [value for _, value in range_items]
242
+
243
+ def lower_bound(self, key: str) -> Optional[str]:
244
+ """Find first key >= given key."""
245
+ normalized_key = self._normalize_key(key)
246
+ position = bisect.bisect_left(self._keys, normalized_key)
247
+
248
+ return self._keys[position] if position < self._size else None
249
+
250
+ def upper_bound(self, key: str) -> Optional[str]:
251
+ """Find first key > given key."""
252
+ normalized_key = self._normalize_key(key)
253
+ position = bisect.bisect_right(self._keys, normalized_key)
254
+
255
+ return self._keys[position] if position < self._size else None
256
+
257
+ def floor(self, key: str) -> Optional[str]:
258
+ """Find largest key <= given key."""
259
+ normalized_key = self._normalize_key(key)
260
+ position = bisect.bisect_right(self._keys, normalized_key) - 1
261
+
262
+ return self._keys[position] if position >= 0 else None
263
+
264
+ def ceiling(self, key: str) -> Optional[str]:
265
+ """Find smallest key >= given key."""
266
+ return self.lower_bound(key)
267
+
268
+ def get_at_index(self, index: int) -> Optional[Tuple[str, Any]]:
269
+ """Get key-value pair at specific index."""
270
+ if 0 <= index < self._size:
271
+ return (self._keys[index], self._values[index])
272
+ return None
273
+
274
+ def index_of(self, key: str) -> int:
275
+ """Get index of key (-1 if not found)."""
276
+ normalized_key = self._normalize_key(key)
277
+ position = self._find_key_position(key)
278
+
279
+ if (position < self._size and
280
+ self._keys[position] == normalized_key):
281
+ return position
282
+
283
+ return -1
284
+
285
+ def pop_first(self) -> Optional[Tuple[str, Any]]:
286
+ """Remove and return first key-value pair."""
287
+ if self._size > 0:
288
+ key = self._keys[0]
289
+ value = self._remove_at_position(0)
290
+ return (key, value)
291
+ return None
292
+
293
+ def pop_last(self) -> Optional[Tuple[str, Any]]:
294
+ """Remove and return last key-value pair."""
295
+ if self._size > 0:
296
+ key = self._keys[-1]
297
+ value = self._remove_at_position(self._size - 1)
298
+ return (key, value)
299
+ return None
300
+
301
+ def reverse_keys(self) -> Iterator[str]:
302
+ """Get keys in reverse order."""
303
+ for i in range(self._size - 1, -1, -1):
304
+ yield self._keys[i]
305
+
306
+ def reverse_values(self) -> Iterator[Any]:
307
+ """Get values in reverse key order."""
308
+ for i in range(self._size - 1, -1, -1):
309
+ yield self._values[i]
310
+
311
+ def reverse_items(self) -> Iterator[Tuple[str, Any]]:
312
+ """Get key-value pairs in reverse order."""
313
+ for i in range(self._size - 1, -1, -1):
314
+ yield (self._keys[i], self._values[i])
315
+
316
+ def find_prefix_keys(self, prefix: str) -> List[str]:
317
+ """Find all keys starting with given prefix."""
318
+ normalized_prefix = self._normalize_key(prefix)
319
+ result = []
320
+
321
+ for key in self._keys:
322
+ if key.startswith(normalized_prefix):
323
+ result.append(key)
324
+ elif key > normalized_prefix and not key.startswith(normalized_prefix):
325
+ break # Keys are sorted, no more matches possible
326
+
327
+ return result
328
+
329
+ def count_range(self, start_key: str, end_key: str, inclusive: bool = True) -> int:
330
+ """Count keys in range."""
331
+ return len(self.get_keys_range(start_key, end_key, inclusive))
332
+
333
+ def get_statistics(self) -> Dict[str, Any]:
334
+ """Get comprehensive ordered map statistics."""
335
+ if self._size == 0:
336
+ return {'size': 0, 'first_key': None, 'last_key': None}
337
+
338
+ # Calculate key length statistics
339
+ key_lengths = [len(key) for key in self._keys]
340
+ avg_key_length = sum(key_lengths) / len(key_lengths)
341
+ min_key_length = min(key_lengths)
342
+ max_key_length = max(key_lengths)
343
+
344
+ return {
345
+ 'size': self._size,
346
+ 'first_key': self.first_key(),
347
+ 'last_key': self.last_key(),
348
+ 'case_sensitive': self.case_sensitive,
349
+ 'allow_duplicates': self.allow_duplicates,
350
+ 'avg_key_length': avg_key_length,
351
+ 'min_key_length': min_key_length,
352
+ 'max_key_length': max_key_length,
353
+ 'memory_usage': self._size * 50 # Estimated
354
+ }
355
+
356
+ # ============================================================================
357
+ # PERFORMANCE CHARACTERISTICS
358
+ # ============================================================================
359
+
360
+ @property
361
+ def backend_info(self) -> Dict[str, Any]:
362
+ """Get backend implementation info."""
363
+ return {
364
+ 'strategy': 'ORDERED_MAP',
365
+ 'backend': 'Parallel sorted arrays with binary search',
366
+ 'case_sensitive': self.case_sensitive,
367
+ 'allow_duplicates': self.allow_duplicates,
368
+ 'complexity': {
369
+ 'put': 'O(n)', # Due to array insertion
370
+ 'get': 'O(log n)', # Binary search
371
+ 'remove': 'O(n)', # Due to array removal
372
+ 'range_query': 'O(log n + k)', # k = result size
373
+ 'iteration': 'O(n)',
374
+ 'space': 'O(n)'
375
+ }
376
+ }
377
+
378
+ @property
379
+ def metrics(self) -> Dict[str, Any]:
380
+ """Get performance metrics."""
381
+ stats = self.get_statistics()
382
+
383
+ return {
384
+ 'size': stats['size'],
385
+ 'first_key': str(stats['first_key']) if stats['first_key'] else 'None',
386
+ 'last_key': str(stats['last_key']) if stats['last_key'] else 'None',
387
+ 'avg_key_length': f"{stats['avg_key_length']:.1f}" if stats.get('avg_key_length') else '0',
388
+ 'case_sensitive': stats['case_sensitive'],
389
+ 'memory_usage': f"{stats['memory_usage']} bytes (estimated)"
390
+ }