pythonstl 0.1.4__cp311-cp311-win_amd64.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.
@@ -0,0 +1,331 @@
1
+ """
2
+ C++ STL Algorithms Suite.
3
+
4
+ This module provides replicas of standard C++ algorithms from <algorithm>
5
+ with dynamic Rust backend loading and in-place list mutation.
6
+ """
7
+
8
+ from typing import Callable, Any
9
+
10
+ try:
11
+ from pythonstl._rust import next_permutation as _rust_next_permutation
12
+ from pythonstl._rust import prev_permutation as _rust_prev_permutation
13
+ from pythonstl._rust import nth_element as _rust_nth_element
14
+ from pythonstl._rust import partition as _rust_partition
15
+ from pythonstl._rust import lower_bound as _rust_lower_bound
16
+ from pythonstl._rust import upper_bound as _rust_upper_bound
17
+ from pythonstl._rust import binary_search as _rust_binary_search
18
+ from pythonstl._rust import equal_range as _rust_equal_range
19
+ RUST_AVAILABLE = True
20
+ except ImportError:
21
+ RUST_AVAILABLE = False
22
+
23
+
24
+ # ----------------- Pure-Python Fallbacks -----------------
25
+
26
+ def _py_next_permutation(arr: list) -> bool:
27
+ n = len(arr)
28
+ if n <= 1:
29
+ return False
30
+
31
+ i = n - 2
32
+ while i >= 0 and arr[i] >= arr[i + 1]:
33
+ i -= 1
34
+
35
+ if i < 0:
36
+ arr.reverse()
37
+ return False
38
+
39
+ j = n - 1
40
+ while arr[j] <= arr[i]:
41
+ j -= 1
42
+
43
+ arr[i], arr[j] = arr[j], arr[i]
44
+ arr[i + 1:] = reversed(arr[i + 1:])
45
+ return True
46
+
47
+
48
+ def _py_prev_permutation(arr: list) -> bool:
49
+ n = len(arr)
50
+ if n <= 1:
51
+ return False
52
+
53
+ i = n - 2
54
+ while i >= 0 and arr[i] <= arr[i + 1]:
55
+ i -= 1
56
+
57
+ if i < 0:
58
+ arr.reverse()
59
+ return False
60
+
61
+ j = n - 1
62
+ while arr[j] >= arr[i]:
63
+ j -= 1
64
+
65
+ arr[i], arr[j] = arr[j], arr[i]
66
+ arr[i + 1:] = reversed(arr[i + 1:])
67
+ return True
68
+
69
+
70
+ def _py_nth_element(arr: list, nth: int) -> None:
71
+ n = len(arr)
72
+ if nth < 0 or nth >= n:
73
+ return
74
+
75
+ left = 0
76
+ right = n - 1
77
+ while left < right:
78
+ mid = left + (right - left) // 2
79
+ arr[mid], arr[right] = arr[right], arr[mid]
80
+ pivot = arr[right]
81
+ i = left
82
+ for j in range(left, right):
83
+ if arr[j] < pivot:
84
+ arr[i], arr[j] = arr[j], arr[i]
85
+ i += 1
86
+ arr[i], arr[right] = arr[right], arr[i]
87
+
88
+ pivot_idx = i
89
+ if pivot_idx == nth:
90
+ return
91
+ elif pivot_idx > nth:
92
+ right = pivot_idx - 1
93
+ else:
94
+ left = pivot_idx + 1
95
+
96
+
97
+
98
+ def _py_partition(arr: list, predicate: Callable[[Any], bool]) -> int:
99
+ i = 0
100
+ for j in range(len(arr)):
101
+ if predicate(arr[j]):
102
+ arr[i], arr[j] = arr[j], arr[i]
103
+ i += 1
104
+ return i
105
+
106
+
107
+ def _py_lower_bound(arr: list, val: Any, comp: Callable[[Any, Any], bool] = None) -> int:
108
+ left = 0
109
+ right = len(arr)
110
+ while left < right:
111
+ mid = left + (right - left) // 2
112
+ mid_val = arr[mid]
113
+ is_less = comp(mid_val, val) if comp else (mid_val < val)
114
+ if is_less:
115
+ left = mid + 1
116
+ else:
117
+ right = mid
118
+ return left
119
+
120
+
121
+ def _py_upper_bound(arr: list, val: Any, comp: Callable[[Any, Any], bool] = None) -> int:
122
+ left = 0
123
+ right = len(arr)
124
+ while left < right:
125
+ mid = left + (right - left) // 2
126
+ mid_val = arr[mid]
127
+ is_less = comp(val, mid_val) if comp else (val < mid_val)
128
+ if is_less:
129
+ right = mid
130
+ else:
131
+ left = mid + 1
132
+ return left
133
+
134
+
135
+ def _py_binary_search(arr: list, val: Any, comp: Callable[[Any, Any], bool] = None) -> bool:
136
+ if not arr:
137
+ return False
138
+ idx = _py_lower_bound(arr, val, comp)
139
+ if idx < len(arr):
140
+ elem = arr[idx]
141
+ if comp:
142
+ return not comp(elem, val) and not comp(val, elem)
143
+ return elem == val
144
+ return False
145
+
146
+
147
+ def _py_equal_range(arr: list, val: Any, comp: Callable[[Any, Any], bool] = None) -> tuple[int, int]:
148
+ return _py_lower_bound(arr, val, comp), _py_upper_bound(arr, val, comp)
149
+
150
+
151
+ # ----------------- Public API Interfaces -----------------
152
+
153
+ def next_permutation(arr: list, use_rust: bool = True) -> bool:
154
+ """
155
+ Rearranges elements in-place to the next lexicographically greater permutation.
156
+
157
+ If the next permutation exists, rearranges elements and returns True.
158
+ Otherwise, reverses the array to the smallest ascending order and returns False.
159
+
160
+ Args:
161
+ arr: The list to modify in-place.
162
+ use_rust: Whether to use the compiled Rust backend (default: True).
163
+
164
+ Returns:
165
+ True if next permutation exists, False otherwise.
166
+
167
+ Time Complexity:
168
+ O(n) where n is len(arr)
169
+ """
170
+ if use_rust and RUST_AVAILABLE:
171
+ return _rust_next_permutation(arr)
172
+ return _py_next_permutation(arr)
173
+
174
+
175
+ def prev_permutation(arr: list, use_rust: bool = True) -> bool:
176
+ """
177
+ Rearranges elements in-place to the next lexicographically smaller permutation.
178
+
179
+ If the previous permutation exists, rearranges elements and returns True.
180
+ Otherwise, reverses the array to the largest descending order and returns False.
181
+
182
+ Args:
183
+ arr: The list to modify in-place.
184
+ use_rust: Whether to use the compiled Rust backend (default: True).
185
+
186
+ Returns:
187
+ True if prev permutation exists, False otherwise.
188
+
189
+ Time Complexity:
190
+ O(n) where n is len(arr)
191
+ """
192
+ if use_rust and RUST_AVAILABLE:
193
+ return _rust_prev_permutation(arr)
194
+ return _py_prev_permutation(arr)
195
+
196
+
197
+ def nth_element(arr: list, nth: int, use_rust: bool = True) -> None:
198
+ """
199
+ Partitions the list in-place so that the element at index `nth` is the one
200
+ that would be there if the list were completely sorted.
201
+
202
+ All elements preceding `nth` are partitioned to be less than or equal to `nth`.
203
+ All elements succeeding `nth` are partitioned to be greater than or equal to `nth`.
204
+ Does not guarantee sorted order of the surrounding elements.
205
+
206
+ Args:
207
+ arr: The list to modify in-place.
208
+ nth: The index that should contain the sorted element.
209
+ use_rust: Whether to use the compiled Rust backend (default: True).
210
+
211
+ Time Complexity:
212
+ O(n) average case
213
+ """
214
+ if use_rust and RUST_AVAILABLE:
215
+ _rust_nth_element(arr, nth)
216
+ else:
217
+ _py_nth_element(arr, nth)
218
+
219
+
220
+ def partition(arr: list, predicate: Callable[[Any], bool], use_rust: bool = True) -> int:
221
+ """
222
+ Reorders the elements in the list in-place such that all elements for which
223
+ `predicate` returns True precede all elements for which it returns False.
224
+
225
+ Does not guarantee stable relative ordering.
226
+
227
+ Args:
228
+ arr: The list to modify in-place.
229
+ predicate: A callable returning True or False for each element.
230
+ use_rust: Whether to use the compiled Rust backend (default: True).
231
+
232
+ Returns:
233
+ The boundary index pointing to the first element that returned False.
234
+
235
+ Time Complexity:
236
+ O(n) where n is len(arr)
237
+ """
238
+ if use_rust and RUST_AVAILABLE:
239
+ return _rust_partition(arr, predicate)
240
+ return _py_partition(arr, predicate)
241
+
242
+
243
+ def lower_bound(arr: list, val: Any, comp: Callable[[Any, Any], bool] = None, use_rust: bool = True) -> int:
244
+ """
245
+ Returns the index of the first element in the range that does not compare less than `val`.
246
+
247
+ Args:
248
+ arr: The sorted list to search.
249
+ val: The value to search for.
250
+ comp: Optional custom binary comparator Callable(a, b) defining custom less-than.
251
+ use_rust: Whether to use the compiled Rust backend (default: True).
252
+
253
+ Returns:
254
+ The index of the first element that is >= val, or len(arr) if not found.
255
+
256
+ Time Complexity:
257
+ O(log n)
258
+ """
259
+ if use_rust and RUST_AVAILABLE:
260
+ return _rust_lower_bound(arr, val, comp)
261
+ return _py_lower_bound(arr, val, comp)
262
+
263
+
264
+ def upper_bound(arr: list, val: Any, comp: Callable[[Any, Any], bool] = None, use_rust: bool = True) -> int:
265
+ """
266
+ Returns the index of the first element in the range that compares greater than `val`.
267
+
268
+ Args:
269
+ arr: The sorted list to search.
270
+ val: The value to search for.
271
+ comp: Optional custom binary comparator Callable(a, b) defining custom less-than.
272
+ use_rust: Whether to use the compiled Rust backend (default: True).
273
+
274
+ Returns:
275
+ The index of the first element that is > val, or len(arr) if not found.
276
+
277
+ Time Complexity:
278
+ O(log n)
279
+ """
280
+ if use_rust and RUST_AVAILABLE:
281
+ return _rust_upper_bound(arr, val, comp)
282
+ return _py_upper_bound(arr, val, comp)
283
+
284
+
285
+ def binary_search(arr: list, val: Any, comp: Callable[[Any, Any], bool] = None, use_rust: bool = True) -> bool:
286
+ """
287
+ Checks if a value is present in the sorted range.
288
+
289
+ Args:
290
+ arr: The sorted list to search.
291
+ val: The value to search for.
292
+ comp: Optional custom binary comparator Callable(a, b) defining custom less-than.
293
+ use_rust: Whether to use the compiled Rust backend (default: True).
294
+
295
+ Returns:
296
+ True if the element equivalent to val is found, False otherwise.
297
+
298
+ Time Complexity:
299
+ O(log n)
300
+ """
301
+ if use_rust and RUST_AVAILABLE:
302
+ return _rust_binary_search(arr, val, comp)
303
+ return _py_binary_search(arr, val, comp)
304
+
305
+
306
+ def equal_range(arr: list, val: Any, comp: Callable[[Any, Any], bool] = None, use_rust: bool = True) -> tuple[int, int]:
307
+ """
308
+ Returns the range of elements equivalent to a given value.
309
+
310
+ Args:
311
+ arr: The sorted list to search.
312
+ val: The value to search for.
313
+ comp: Optional custom binary comparator Callable(a, b) defining custom less-than.
314
+ use_rust: Whether to use the compiled Rust backend (default: True).
315
+
316
+ Returns:
317
+ A tuple (lower_bound_index, upper_bound_index) defining the range of equivalent elements.
318
+
319
+ Time Complexity:
320
+ O(log n)
321
+ """
322
+ if use_rust and RUST_AVAILABLE:
323
+ return _rust_equal_range(arr, val, comp)
324
+ return _py_equal_range(arr, val, comp)
325
+
326
+
327
+ __all__ = [
328
+ 'next_permutation', 'prev_permutation', 'nth_element', 'partition',
329
+ 'lower_bound', 'upper_bound', 'binary_search', 'equal_range',
330
+ 'RUST_AVAILABLE'
331
+ ]
@@ -0,0 +1,298 @@
1
+ """
2
+ Map facade class.
3
+
4
+ This module provides the public-facing map class that users interact with.
5
+ """
6
+
7
+ from typing import TypeVar, Iterator as TypingIterator, Tuple
8
+ from copy import deepcopy
9
+ from pythonstl.core.exceptions import KeyNotFoundError
10
+ from pythonstl.implementations.associative._map_impl import _MapImpl
11
+ from pythonstl.core.iterator import MapIterator
12
+
13
+ try:
14
+ from pythonstl._rust import RustMap
15
+ RUST_AVAILABLE = True
16
+ except ImportError:
17
+ RUST_AVAILABLE = False
18
+
19
+ K = TypeVar('K')
20
+ V = TypeVar('V')
21
+
22
+
23
+ class stl_map:
24
+ """
25
+ A map data structure following C++ STL semantics.
26
+
27
+ This is an associative container that stores key-value pairs.
28
+ Named 'stl_map' to avoid potential conflicts.
29
+
30
+ Example:
31
+ >>> from pythonstl import stl_map
32
+ >>> m = stl_map()
33
+ >>> m.insert("key1", 100)
34
+ >>> m.insert("key2", 200)
35
+ >>> m.at("key1")
36
+ 100
37
+ >>> "key1" in m
38
+ True
39
+ >>> len(m)
40
+ 2
41
+ """
42
+
43
+ def __init__(self, use_rust: bool = True) -> None:
44
+ """
45
+ Initialize an empty map.
46
+
47
+ Time Complexity:
48
+ O(1)
49
+ """
50
+ if use_rust and RUST_AVAILABLE:
51
+ self._impl = RustMap()
52
+ self._is_rust = True
53
+ else:
54
+ self._impl = _MapImpl()
55
+ self._is_rust = False
56
+
57
+ def insert(self, key: K, value: V) -> None:
58
+ """
59
+ Insert a key-value pair into the map.
60
+
61
+ Args:
62
+ key: The key to insert.
63
+ value: The value associated with the key.
64
+
65
+ Note:
66
+ If the key already exists, the value is updated.
67
+
68
+ Time Complexity:
69
+ O(1) average case
70
+ """
71
+ self._impl.insert(key, value)
72
+
73
+ def erase(self, key: K) -> None:
74
+ """
75
+ Remove a key-value pair from the map.
76
+
77
+ Args:
78
+ key: The key to remove.
79
+
80
+ Note:
81
+ Does nothing if the key is not present (matches C++ STL behavior).
82
+
83
+ Time Complexity:
84
+ O(1) average case
85
+ """
86
+ self._impl.erase(key)
87
+
88
+ def find(self, key: K) -> bool:
89
+ """
90
+ Check if a key exists in the map.
91
+
92
+ Args:
93
+ key: The key to search for.
94
+
95
+ Returns:
96
+ True if the key exists, False otherwise.
97
+
98
+ Time Complexity:
99
+ O(1) average case
100
+ """
101
+ return self._impl.find(key)
102
+
103
+ def at(self, key: K) -> V:
104
+ """
105
+ Access the value associated with a key.
106
+
107
+ Args:
108
+ key: The key to access.
109
+
110
+ Returns:
111
+ The value associated with the key.
112
+
113
+ Raises:
114
+ KeyNotFoundError: If the key does not exist.
115
+
116
+ Time Complexity:
117
+ O(1) average case
118
+ """
119
+ if not self.find(key):
120
+ raise KeyNotFoundError(key)
121
+ if self._is_rust:
122
+ return self._impl.at(key)
123
+ return self._impl.at(key)
124
+
125
+ def empty(self) -> bool:
126
+ """
127
+ Check if the map is empty.
128
+
129
+ Returns:
130
+ True if the map is empty, False otherwise.
131
+
132
+ Time Complexity:
133
+ O(1)
134
+ """
135
+ return self._impl.empty()
136
+
137
+ def size(self) -> int:
138
+ """
139
+ Get the number of key-value pairs in the map.
140
+
141
+ Returns:
142
+ The number of key-value pairs in the map.
143
+
144
+ Time Complexity:
145
+ O(1)
146
+ """
147
+ return self._impl.size()
148
+
149
+ def begin(self) -> MapIterator:
150
+ """
151
+ Get iterator to the beginning of the map.
152
+
153
+ Returns:
154
+ Iterator pointing to the first key-value pair.
155
+
156
+ Time Complexity:
157
+ O(1)
158
+ """
159
+ if self._is_rust:
160
+ return MapIterator(dict(self._impl.get_data()))
161
+ return self._impl.begin()
162
+
163
+ def end(self) -> MapIterator:
164
+ """
165
+ Get iterator to the end of the map.
166
+
167
+ Returns:
168
+ Iterator pointing past the last key-value pair.
169
+
170
+ Time Complexity:
171
+ O(1)
172
+ """
173
+ if self._is_rust:
174
+ return MapIterator({})
175
+ return self._impl.end()
176
+
177
+ def copy(self) -> 'stl_map':
178
+ """
179
+ Create a deep copy of the map.
180
+
181
+ Returns:
182
+ A new map with copied key-value pairs.
183
+
184
+ Time Complexity:
185
+ O(n) where n is the number of key-value pairs
186
+ """
187
+ new_map = stl_map(use_rust=self._is_rust)
188
+ if self._is_rust:
189
+ new_map._impl.set_data(self._impl.get_data())
190
+ else:
191
+ new_map._impl._data = self._impl._data.copy()
192
+ return new_map
193
+
194
+ # Python magic methods
195
+
196
+ def __len__(self) -> int:
197
+ """
198
+ Get the number of key-value pairs (Python len() support).
199
+
200
+ Returns:
201
+ The number of key-value pairs in the map.
202
+ """
203
+ return self.size()
204
+
205
+ def __bool__(self) -> bool:
206
+ """
207
+ Check if map is non-empty (Python bool() support).
208
+
209
+ Returns:
210
+ True if map is non-empty, False otherwise.
211
+ """
212
+ return not self.empty()
213
+
214
+ def __contains__(self, key: K) -> bool:
215
+ """
216
+ Check if key exists in map (Python 'in' operator support).
217
+
218
+ Args:
219
+ key: The key to search for.
220
+
221
+ Returns:
222
+ True if key exists, False otherwise.
223
+
224
+ Time Complexity:
225
+ O(1) average case
226
+ """
227
+ return self.find(key)
228
+
229
+ def __repr__(self) -> str:
230
+ """
231
+ Get string representation of the map.
232
+
233
+ Returns:
234
+ String representation showing all key-value pairs.
235
+ """
236
+ pairs = [f"{k}: {v}" for k, v in self]
237
+ return f"stl_map({{{', '.join(pairs)}}})"
238
+
239
+ def __eq__(self, other: object) -> bool:
240
+ """
241
+ Check equality with another map.
242
+
243
+ Args:
244
+ other: Another map to compare with.
245
+
246
+ Returns:
247
+ True if maps are equal, False otherwise.
248
+ """
249
+ if not isinstance(other, stl_map):
250
+ return False
251
+
252
+ self_data = dict(self._impl.get_data()) if self._is_rust else self._impl._data
253
+ other_data = dict(other._impl.get_data()) if other._is_rust else other._impl._data
254
+ return self_data == other_data
255
+
256
+ def __iter__(self) -> TypingIterator[Tuple[K, V]]:
257
+ """
258
+ Get Python iterator for the map.
259
+
260
+ Returns:
261
+ Iterator over key-value pairs as tuples.
262
+ """
263
+ if self._is_rust:
264
+ return iter(self._impl.get_data())
265
+ return iter(self._impl.get_data().items())
266
+
267
+ def __copy__(self) -> 'stl_map':
268
+ """
269
+ Support for copy.copy().
270
+
271
+ Returns:
272
+ A shallow copy of the map.
273
+ """
274
+ return self.copy()
275
+
276
+ def __deepcopy__(self, memo) -> 'stl_map':
277
+ """
278
+ Support for copy.deepcopy().
279
+
280
+ Args:
281
+ memo: Memoization dictionary for deepcopy.
282
+
283
+ Returns:
284
+ A deep copy of the map.
285
+ """
286
+ new_map = stl_map(use_rust=self._is_rust)
287
+ if self._is_rust:
288
+ new_pairs = []
289
+ for k, v in self._impl.get_data():
290
+ new_pairs.append((deepcopy(k, memo), deepcopy(v, memo)))
291
+ new_map._impl.set_data(new_pairs)
292
+ else:
293
+ new_map._impl._data = deepcopy(self._impl._data, memo)
294
+ return new_map
295
+
296
+
297
+
298
+ __all__ = ['stl_map']