reykit 1.0.0__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.
reykit/rrandom.py ADDED
@@ -0,0 +1,351 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2023-04-22 22:32:34
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Random methods.
9
+ """
10
+
11
+
12
+ from __future__ import annotations
13
+ from typing import Dict, Union, Optional, Literal, Self, overload
14
+ from types import TracebackType
15
+ from collections.abc import Sequence
16
+ from string import digits as string_digits, ascii_letters as string_ascii_letters, punctuation as string_punctuation
17
+ from math import ceil as math_ceil
18
+ from random import Random
19
+ from secrets import randbelow as secrets_randbelow
20
+ from threading import get_ident as threading_get_ident
21
+
22
+ from .rdata import Element
23
+ from .rexception import throw
24
+ from .rnumber import digits
25
+ from .rtype import RConfigMeta
26
+
27
+
28
+ __all__ = (
29
+ 'RConfigRandom',
30
+ 'RRandomSeed',
31
+ 'randn',
32
+ 'randb',
33
+ 'randi',
34
+ 'randchar',
35
+ 'randsort'
36
+ )
37
+
38
+
39
+ class RConfigRandom(object, metaclass=RConfigMeta):
40
+ """
41
+ Rey's `config random` type.
42
+ """
43
+
44
+ # RRandom.
45
+ _rrandom_dict: Dict[int, RRandomSeed] = {}
46
+
47
+
48
+ class RRandomSeed(object):
49
+ """
50
+ Rey's `random seed` type.
51
+ To be used in syntax `with`, set random seed.
52
+ If set, based on `random` package.
53
+ If not set, based on `secrets` package.
54
+ """
55
+
56
+
57
+ def __init__(self, seed: Union[int, float, str, bytes, bytearray]) -> None:
58
+ """
59
+ Build `random` attributes.
60
+ """
61
+
62
+ # Build.
63
+ self.seed = seed
64
+ self.random = Random(seed)
65
+
66
+ # Record.
67
+ thread_id = threading_get_ident()
68
+ RConfigRandom._rrandom_dict[thread_id] = self
69
+
70
+
71
+ def __enter__(self) -> Self:
72
+ """
73
+ Enter syntax `with`.
74
+
75
+ Returns
76
+ -------
77
+ Self.
78
+ """
79
+
80
+ return self
81
+
82
+
83
+ def __exit__(
84
+ self,
85
+ exc_type: Optional[type[BaseException]],
86
+ exc_instance: Optional[BaseException],
87
+ exc_traceback: Optional[TracebackType]
88
+ ) -> None:
89
+ """
90
+ Exit syntax `with`.
91
+
92
+ Parameters
93
+ ----------
94
+ exc_type : Exception type.
95
+ exc_instance : Exception instance.
96
+ exc_traceback : Exception traceback instance.
97
+ """
98
+
99
+ # Delete.
100
+ thread_id = threading_get_ident()
101
+ del RConfigRandom._rrandom_dict[thread_id]
102
+
103
+
104
+ @overload
105
+ def randn(
106
+ *thresholds: int,
107
+ precision: None = None
108
+ ) -> int: ...
109
+
110
+ @overload
111
+ def randn(
112
+ *thresholds: float,
113
+ precision: None = None
114
+ ) -> float: ...
115
+
116
+ @overload
117
+ def randn(
118
+ *thresholds: float,
119
+ precision: Literal[0] = None
120
+ ) -> int: ...
121
+
122
+ @overload
123
+ def randn(
124
+ *thresholds: float,
125
+ precision: int = None
126
+ ) -> float: ...
127
+
128
+ def randn(
129
+ *thresholds: float,
130
+ precision: Optional[int] = None
131
+ ) -> Union[int, float]:
132
+ """
133
+ Random number.
134
+
135
+ Parameters
136
+ ----------
137
+ thresholds : Low and high thresholds of random range, range contains thresholds.
138
+ - When `length is 0`, then low and high thresholds is `0` and `10`.
139
+ - When `length is 1`, then low and high thresholds is `0` and `thresholds[0]`.
140
+ - When `length is 2`, then low and high thresholds is `thresholds[0]` and `thresholds[1]`.
141
+ precision : Precision of random range, that is maximum decimal digits of return value.
142
+ - `None`: Set to Maximum decimal digits of element of parameter `thresholds`.
143
+ - `int`: Set to this value.
144
+
145
+ Returns
146
+ -------
147
+ Random number.
148
+ - When parameters `precision` is 0, then return int.
149
+ - When parameters `precision` is greater than 0, then return float.
150
+ """
151
+
152
+ # Handle parameter.
153
+ thresholds_len = len(thresholds)
154
+ match thresholds_len:
155
+ case 0:
156
+ threshold_low = 0
157
+ threshold_high = 10
158
+ case 1:
159
+ threshold_low = 0
160
+ threshold_high = thresholds[0]
161
+ case 2:
162
+ threshold_low = thresholds[0]
163
+ threshold_high = thresholds[1]
164
+ case _:
165
+ raise ValueError('number of parameter "thresholds" must is 0 or 1 or 2')
166
+ if precision is None:
167
+ threshold_low_desimal_digits = digits(threshold_low)[1]
168
+ threshold_high_desimal_digits = digits(threshold_high)[1]
169
+ desimal_digits_max = max(threshold_low_desimal_digits, threshold_high_desimal_digits)
170
+ precision = desimal_digits_max
171
+
172
+ # Get random number.
173
+ magnifier = 10 ** precision
174
+ threshold_low = int(threshold_low * magnifier)
175
+ threshold_high = int(threshold_high * magnifier)
176
+
177
+ ## No seed.
178
+ thread_id = threading_get_ident()
179
+ rrandom = RConfigRandom._rrandom_dict.get(thread_id)
180
+ if rrandom is None:
181
+ range_ = threshold_high - threshold_low + 1
182
+ number = secrets_randbelow(range_)
183
+ number += threshold_low
184
+
185
+ ## Seed.
186
+ else:
187
+ number = rrandom.random.randint(threshold_low, threshold_high)
188
+ number = number / magnifier
189
+
190
+ # Convert Integer.
191
+ if precision == 0:
192
+ number = int(number)
193
+
194
+ return number
195
+
196
+
197
+ def randb(pr: float = 0.5) -> bool:
198
+ """
199
+ Random bool.
200
+
201
+ Parameters
202
+ ----------
203
+ pr : Probability setting.
204
+ - `∈(0, 1)`: Random probability, formula is `randn(1, int(1 / pr * 100)) <= 100`.
205
+ - `∈(1, +∞)`: Random range, formula is `randn(1, ceil(pr)) == 1`.
206
+
207
+ Returns
208
+ -------
209
+ Random bool.
210
+ """
211
+
212
+ # Random probability.
213
+ if 0 < pr < 1:
214
+ high = int(1 / pr * 100)
215
+ result = randn(1, high) <= 100
216
+
217
+ # Random range.
218
+ elif 1 < pr:
219
+ high = math_ceil(pr)
220
+ result = randn(1, high) == 1
221
+
222
+ # Throw exception.
223
+ else:
224
+ throw(ValueError, pr)
225
+
226
+ return result
227
+
228
+
229
+ @overload
230
+ def randi(
231
+ data: Sequence[Element],
232
+ multi: None = None,
233
+ unique: bool = True
234
+ ) -> Element: ...
235
+
236
+ @overload
237
+ def randi(
238
+ data: Sequence,
239
+ multi: int = None,
240
+ unique: bool = True
241
+ ) -> list[Element]: ...
242
+
243
+ def randi(
244
+ data: Sequence,
245
+ multi: Optional[int] = None,
246
+ unique: bool = True
247
+ ) -> Union[Element, list[Element]]:
248
+ """
249
+ Random index data element.
250
+
251
+ Parameters
252
+ ----------
253
+ data : Sequence data.
254
+ multi : Whether index multiple data elements.
255
+ - `None`: Return a value.
256
+ - `int`: Return multiple values.
257
+ unique : Whether index unique, non value constraint.
258
+
259
+ Returns
260
+ -------
261
+ Element.
262
+ """
263
+
264
+ # Random.
265
+ data_len = len(data)
266
+ match multi:
267
+
268
+ ## One.
269
+ case None:
270
+ index = randn(data_len - 1)
271
+ result = data[index]
272
+
273
+ ## Multiple.
274
+ case _:
275
+ match unique:
276
+
277
+ ### Unique.
278
+ case True:
279
+
280
+ #### Check.
281
+ if multi > data_len:
282
+ throw(IndexError, multi, data_len)
283
+
284
+ indexes = list(range(data_len))
285
+ indexes_randsort = [
286
+ indexes.pop(randn(indexes_len - 1))
287
+ for indexes_len in range(data_len, data_len - multi, -1)
288
+ ]
289
+ result = [
290
+ data[index]
291
+ for index in indexes_randsort
292
+ ]
293
+
294
+ ### Not unique.
295
+ case False:
296
+ rand_max = data_len - 1
297
+ result = [
298
+ data[randn(rand_max)]
299
+ for _ in range(multi)
300
+ ]
301
+
302
+ return result
303
+
304
+
305
+ def randchar(
306
+ length: int,
307
+ punctuation: bool = True
308
+ ) -> str:
309
+ """
310
+ Generate random characters.
311
+
312
+ Parameters
313
+ ----------
314
+ length : Character length.
315
+ punctuation : Whether contain punctuation.
316
+
317
+ Returns
318
+ -------
319
+ Random characters.
320
+ """
321
+
322
+ # Get parameter.
323
+ char_range = string_digits + string_ascii_letters
324
+ if punctuation:
325
+ char_range += string_punctuation
326
+
327
+ # Generate.
328
+ char_list = randi(char_range, length, False)
329
+ chars = ''.join(char_list)
330
+
331
+ return chars
332
+
333
+
334
+ def randsort(data: Sequence[Element]) -> list[Element]:
335
+ """
336
+ Random sorting data.
337
+
338
+ Parameters
339
+ ----------
340
+ data : Sequence data.
341
+
342
+ Returns
343
+ -------
344
+ Sorted data.
345
+ """
346
+
347
+ # Random.
348
+ data_len = len(data)
349
+ data_randsort = randi(data, data_len)
350
+
351
+ return data_randsort
reykit/rregex.py ADDED
@@ -0,0 +1,293 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2022-12-11 23:25:36
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Regular expression methods.
9
+ """
10
+
11
+
12
+ from typing import Optional, Union, Literal, overload
13
+ from collections.abc import Callable
14
+ from re import (
15
+ search as re_search,
16
+ sub as re_sub,
17
+ split as re_split,
18
+ findall as re_findall,
19
+ S as RS,
20
+ Match as RMatch
21
+ )
22
+
23
+ from .rdata import unique
24
+
25
+
26
+ __all__ = (
27
+ 'search',
28
+ 'findall',
29
+ 'sub',
30
+ 'split',
31
+ 'search_batch',
32
+ 'findall_batch',
33
+ 'sub_batch',
34
+ 'split_batch'
35
+ )
36
+
37
+
38
+ def search(
39
+ pattern: str,
40
+ text: str
41
+ ) -> Optional[Union[str, tuple[Optional[str], ...]]]:
42
+ """
43
+ Regular search text.
44
+
45
+ Parameters
46
+ ----------
47
+ pattern : Regular pattern, `period match any character`.
48
+ text : Match text.
49
+
50
+ Returns
51
+ -------
52
+ Matching result.
53
+ - When match to and not use `group`, then return string.
54
+ - When match to and use `group`, then return tuple with value string or None.
55
+ If tuple length is `1`, extract and return string.
56
+ - When no match, then return None.
57
+ """
58
+
59
+ # Search.
60
+ obj_re = re_search(pattern, text, RS)
61
+
62
+ # Return result.
63
+ if obj_re is not None:
64
+ result = obj_re.groups()
65
+ if result == ():
66
+ result = obj_re[0]
67
+ elif len(result) == 1:
68
+ result = obj_re[1]
69
+
70
+ return result
71
+
72
+
73
+ def findall(
74
+ pattern: str,
75
+ text: str,
76
+ ) -> Union[list[str], list[tuple[str, ...]]]:
77
+ """
78
+ Regular find all text.
79
+
80
+ Parameters
81
+ ----------
82
+ pattern : Regular pattern, `period match any character`.
83
+ text : Match text.
84
+
85
+ Returns
86
+ -------
87
+ Find result.
88
+ """
89
+
90
+ # Find all.
91
+ result = re_findall(pattern, text, RS)
92
+
93
+ return result
94
+
95
+
96
+ def sub(
97
+ pattern: str,
98
+ text: str,
99
+ replace: Optional[Union[str, Callable[[RMatch], str]]] = None,
100
+ count: Optional[int] = None
101
+ ) -> str:
102
+ """
103
+ Regular replace text.
104
+
105
+ Parameters
106
+ ----------
107
+ pattern : Regular pattern, `period match any character`.
108
+ text : Match text.
109
+ replace : Replace text or handle function.
110
+ - `None`: Delete match part.
111
+ count : Replace maximum count.
112
+
113
+ Returns
114
+ -------
115
+ Replaced result.
116
+ """
117
+
118
+ # Handle parameter.
119
+ if replace is None:
120
+ replace = ''
121
+ if count is None:
122
+ count = 0
123
+
124
+ # Replace.
125
+ result = re_sub(pattern, replace, text, count, RS)
126
+
127
+ return result
128
+
129
+
130
+ def split(
131
+ pattern: str,
132
+ text: str,
133
+ count: Optional[int] = None
134
+ ) -> list[str]:
135
+ """
136
+ Regular split text.
137
+
138
+ Parameters
139
+ ----------
140
+ pattern : Regular pattern, `period match any character`.
141
+ text : Match text.
142
+ count : Split maximum count.
143
+
144
+ Returns
145
+ -------
146
+ Split result.
147
+ """
148
+
149
+ # Handle parameter.
150
+ if count is None:
151
+ count = 0
152
+
153
+ # Replace.
154
+ result = re_split(pattern, text, count, RS)
155
+
156
+ return result
157
+
158
+
159
+ @overload
160
+ def search_batch(
161
+ text: str,
162
+ *patterns: str,
163
+ first: Literal[True] = True
164
+ ) -> Optional[Union[str, tuple[Optional[str], ...]]]: ...
165
+
166
+ @overload
167
+ def search_batch(
168
+ text: str,
169
+ *patterns: str,
170
+ first: Literal[False] = True
171
+ ) -> list[Optional[Union[str, tuple[Optional[str], ...]]]]: ...
172
+
173
+ def search_batch(
174
+ text: str,
175
+ *patterns: str,
176
+ first: bool = True
177
+ ) -> Union[
178
+ Optional[Union[str, tuple[Optional[str], ...]]],
179
+ list[Optional[Union[str, tuple[Optional[str], ...]]]]
180
+ ]:
181
+ """
182
+ Batch regular search text.
183
+
184
+ Parameters
185
+ ----------
186
+ text : Match text.
187
+ pattern : Regular pattern, `period match any character`.
188
+ first : Whether return first successful match.
189
+
190
+ Returns
191
+ -------
192
+ Matching result.
193
+ - When match to and not use group, then return string.
194
+ - When match to and use group, then return tuple with value string or None.
195
+ - When no match, then return.
196
+ """
197
+
198
+ # Search.
199
+
200
+ ## Return first result.
201
+ if first:
202
+ for pattern in patterns:
203
+ result = search(pattern, text)
204
+ if result is not None:
205
+ return result
206
+
207
+ ## Return all result.
208
+ else:
209
+ result = [search(pattern, text) for pattern in patterns]
210
+ return result
211
+
212
+
213
+ def findall_batch(text: str, *patterns: str) -> str:
214
+ """
215
+ Batch regular find all text.
216
+
217
+ Parameters
218
+ ----------
219
+ text : Match text.
220
+ patterns : Regular pattern, `period match any character`.
221
+
222
+ Returns
223
+ -------
224
+ List of Find result.
225
+ """
226
+
227
+ # Find all.
228
+ texts = [
229
+ string
230
+ for pattern in patterns
231
+ for string in findall(pattern, text)
232
+ ]
233
+
234
+ # De duplicate.
235
+ texts = unique(texts)
236
+
237
+ return texts
238
+
239
+
240
+ def sub_batch(text: str, *patterns: Union[str, tuple[str, Union[str, Callable[[RMatch], str]]]]) -> str:
241
+ """
242
+ Batch regular replace text.
243
+
244
+ Parameters
245
+ ----------
246
+ text : Match text.
247
+ patterns : Regular pattern and replace text, `period match any character`.
248
+ - `str`: Regular pattern, delete match part.
249
+ - `tuple[str, str]`: Regular pattern and replace text.
250
+ - `tuple[str, Callable[[RMatch], str]]`: Regular pattern and replace handle function.
251
+
252
+ Returns
253
+ -------
254
+ Replaced result.
255
+ """
256
+
257
+ # Replace.
258
+ for pattern in patterns:
259
+ if pattern.__class__ == str:
260
+ replace = None
261
+ else:
262
+ pattern, replace = pattern
263
+ text = sub(pattern, text, replace)
264
+
265
+ return text
266
+
267
+
268
+ def split_batch(text: str, *patterns: str) -> str:
269
+ """
270
+ Batch regular split text.
271
+
272
+ Parameters
273
+ ----------
274
+ text : Match text.
275
+ patterns : Regular pattern, `period match any character`.
276
+
277
+ Returns
278
+ -------
279
+ Split result.
280
+ """
281
+
282
+ # Split.
283
+ texts = [
284
+ string
285
+ for pattern in patterns
286
+ for string in split(pattern, text)
287
+ if string != ''
288
+ ]
289
+
290
+ # De duplicate.
291
+ texts = unique(texts)
292
+
293
+ return texts