istr-python 1.1.40__tar.gz → 1.1.42__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.
- {istr_python-1.1.40 → istr_python-1.1.42}/PKG-INFO +1 -4
- {istr_python-1.1.40 → istr_python-1.1.42}/README.md +0 -3
- {istr_python-1.1.40 → istr_python-1.1.42}/istr/istr.py +22 -17
- {istr_python-1.1.40 → istr_python-1.1.42}/istr_python.egg-info/PKG-INFO +1 -4
- {istr_python-1.1.40 → istr_python-1.1.42}/istr_python.egg-info/SOURCES.txt +0 -1
- {istr_python-1.1.40 → istr_python-1.1.42}/pyproject.toml +1 -1
- {istr_python-1.1.40 → istr_python-1.1.42}/tests/test_istr.py +14 -2
- istr_python-1.1.40/istr/istr - Copy.py +0 -1084
- {istr_python-1.1.40 → istr_python-1.1.42}/istr/LICENSE.txt +0 -0
- {istr_python-1.1.40 → istr_python-1.1.42}/istr/__init__.py +0 -0
- {istr_python-1.1.40 → istr_python-1.1.42}/istr_python.egg-info/dependency_links.txt +0 -0
- {istr_python-1.1.40 → istr_python-1.1.42}/istr_python.egg-info/top_level.txt +0 -0
- {istr_python-1.1.40 → istr_python-1.1.42}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: istr-python
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.42
|
|
4
4
|
Summary: istr - strings you can count on
|
|
5
5
|
Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/salabim/istr
|
|
@@ -589,9 +589,6 @@ map(istr.join,filter(istr.prime, istr.combinations(range(10),2)))
|
|
|
589
589
|
==> istr('02'), istr('03'), ... istr('89')
|
|
590
590
|
```
|
|
591
591
|
|
|
592
|
-
#### version 1.1.30 2026-05-19
|
|
593
|
-
|
|
594
|
-
|
|
595
592
|
#### reverse an istr
|
|
596
593
|
|
|
597
594
|
The method `reversed()` will return an istr with the reversed content:
|
|
@@ -576,9 +576,6 @@ map(istr.join,filter(istr.prime, istr.combinations(range(10),2)))
|
|
|
576
576
|
==> istr('02'), istr('03'), ... istr('89')
|
|
577
577
|
```
|
|
578
578
|
|
|
579
|
-
#### version 1.1.30 2026-05-19
|
|
580
|
-
|
|
581
|
-
|
|
582
579
|
#### reverse an istr
|
|
583
580
|
|
|
584
581
|
The method `reversed()` will return an istr with the reversed content:
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# |_||___/ \__||_|
|
|
6
6
|
# strings you can count on
|
|
7
7
|
|
|
8
|
-
__version__ = "1.1.
|
|
8
|
+
__version__ = "1.1.42"
|
|
9
9
|
import functools
|
|
10
10
|
import itertools
|
|
11
11
|
import types
|
|
@@ -727,19 +727,16 @@ class istr(str):
|
|
|
727
727
|
return self[::-1]
|
|
728
728
|
|
|
729
729
|
def ceil(self, divisible_by=1):
|
|
730
|
-
if divisible_by
|
|
731
|
-
raise ValueError(f"
|
|
732
|
-
if divisible_by != int(divisible_by):
|
|
733
|
-
raise ValueError(f"step has to be an integer value, not {divisible_by}")
|
|
730
|
+
if divisible_by != int(divisible_by) or divisible_by<1:
|
|
731
|
+
raise ValueError(f"divisible_by has to be an integer value >=1 , not {divisible_by}")
|
|
734
732
|
n = istr.interpret_as_float(self)
|
|
735
733
|
return istr((math.ceil(n / divisible_by)) * divisible_by)
|
|
736
734
|
|
|
737
735
|
def floor(self, divisible_by=1):
|
|
738
|
-
if divisible_by
|
|
739
|
-
raise ValueError(f"
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
return istr((math.floor(self / divisible_by)) * divisible_by)
|
|
736
|
+
if divisible_by != int(divisible_by) or divisible_by<1:
|
|
737
|
+
raise ValueError(f"divisible_by has to be an integer value >=1 , not {divisible_by}")
|
|
738
|
+
n = istr.interpret_as_float(self)
|
|
739
|
+
return istr((math.floor(n / divisible_by)) * divisible_by)
|
|
743
740
|
|
|
744
741
|
def interpret_as_int(self):
|
|
745
742
|
if isinstance(self, istr):
|
|
@@ -767,6 +764,13 @@ class istr(str):
|
|
|
767
764
|
|
|
768
765
|
return str(self)
|
|
769
766
|
|
|
767
|
+
def interpret_as_istr(self):
|
|
768
|
+
# ignore leading zeroes
|
|
769
|
+
if isinstance(self, collections.abc.Iterable) and not isinstance(self, str):
|
|
770
|
+
return istr.join(self)*1
|
|
771
|
+
|
|
772
|
+
return istr(self)*1
|
|
773
|
+
|
|
770
774
|
def _str_method(self, name, *args, **kwargs):
|
|
771
775
|
return self.__class__(getattr(super(), name)(*args, **kwargs))
|
|
772
776
|
|
|
@@ -864,7 +868,7 @@ class istr(str):
|
|
|
864
868
|
if as_str is True, a formatted output will be returned
|
|
865
869
|
"""
|
|
866
870
|
|
|
867
|
-
number = istr
|
|
871
|
+
number = istr.interpret_as_istr(self)
|
|
868
872
|
|
|
869
873
|
lines = []
|
|
870
874
|
if len(number)%2==1:
|
|
@@ -889,8 +893,9 @@ class istr(str):
|
|
|
889
893
|
remainder = dividend - trial
|
|
890
894
|
|
|
891
895
|
root = root * 10 + x
|
|
892
|
-
|
|
893
|
-
|
|
896
|
+
if trial:
|
|
897
|
+
lines.append(dividend)
|
|
898
|
+
lines.append(trial)
|
|
894
899
|
|
|
895
900
|
lines.append(remainder)
|
|
896
901
|
lines = [root] + [number] + lines[1:]
|
|
@@ -921,8 +926,8 @@ class istr(str):
|
|
|
921
926
|
return lines
|
|
922
927
|
|
|
923
928
|
def long_multiplication(self, number,as_str=False):
|
|
924
|
-
number0 = istr
|
|
925
|
-
number1=istr(number)
|
|
929
|
+
number0 = istr.interpret_as_istr(self)
|
|
930
|
+
number1=istr.interpret_as_istr(number)
|
|
926
931
|
lines=[number0,number1]
|
|
927
932
|
for i in number1.reversed():
|
|
928
933
|
lines.append(number0*i)
|
|
@@ -942,8 +947,8 @@ class istr(str):
|
|
|
942
947
|
return lines
|
|
943
948
|
|
|
944
949
|
def long_division(self, divisor, as_str=False):
|
|
945
|
-
dividend = istr
|
|
946
|
-
divisor = istr(divisor)
|
|
950
|
+
dividend = istr.interpret_as_istr(self)
|
|
951
|
+
divisor = istr.interpret_as_istr(divisor)
|
|
947
952
|
|
|
948
953
|
if dividend < 0:
|
|
949
954
|
raise ValueError("dividend must be non-negative.")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: istr-python
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.42
|
|
4
4
|
Summary: istr - strings you can count on
|
|
5
5
|
Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/salabim/istr
|
|
@@ -589,9 +589,6 @@ map(istr.join,filter(istr.prime, istr.combinations(range(10),2)))
|
|
|
589
589
|
==> istr('02'), istr('03'), ... istr('89')
|
|
590
590
|
```
|
|
591
591
|
|
|
592
|
-
#### version 1.1.30 2026-05-19
|
|
593
|
-
|
|
594
|
-
|
|
595
592
|
#### reverse an istr
|
|
596
593
|
|
|
597
594
|
The method `reversed()` will return an istr with the reversed content:
|
|
@@ -1138,6 +1138,10 @@ def test_ceil():
|
|
|
1138
1138
|
assert istr.ceil(1000) == 1000
|
|
1139
1139
|
assert istr.ceil(1000, 1) == 1000
|
|
1140
1140
|
assert istr.ceil(1000.2, 1).equals(istr(1001))
|
|
1141
|
+
with pytest.raises(ValueError,match=re.escape(f"divisible_by has to be an integer value >=1")):
|
|
1142
|
+
istr(1000).ceil(0)
|
|
1143
|
+
with pytest.raises(ValueError,match=re.escape(f"divisible_by has to be an integer value >=1")):
|
|
1144
|
+
istr(1000).ceil(2.5)
|
|
1141
1145
|
|
|
1142
1146
|
|
|
1143
1147
|
def test_floor():
|
|
@@ -1146,8 +1150,10 @@ def test_floor():
|
|
|
1146
1150
|
assert istr(1000).floor(2) == 1000
|
|
1147
1151
|
assert istr(1000).floor(3) == 999
|
|
1148
1152
|
assert istr.floor(1000) == 1000
|
|
1149
|
-
|
|
1150
|
-
|
|
1153
|
+
with pytest.raises(ValueError,match=re.escape(f"divisible_by has to be an integer value >=1")):
|
|
1154
|
+
istr(1000).floor(0)
|
|
1155
|
+
with pytest.raises(ValueError,match=re.escape(f"divisible_by has to be an integer value >=1")):
|
|
1156
|
+
istr(1000).floor(2.5)
|
|
1151
1157
|
|
|
1152
1158
|
|
|
1153
1159
|
def test_tuple_join():
|
|
@@ -1259,6 +1265,8 @@ def test_long_sqrt():
|
|
|
1259
1265
|
istr("0"),
|
|
1260
1266
|
]
|
|
1261
1267
|
assert istr.long_sqrt(34**2) == [istr("34"), istr("1156"), istr("9"), istr("256"), istr("256"), istr("0")]
|
|
1268
|
+
assert istr.long_sqrt("001156") == [istr("34"), istr("1156"), istr("9"), istr("256"), istr("256"), istr("0")]
|
|
1269
|
+
|
|
1262
1270
|
assert (
|
|
1263
1271
|
istr.long_sqrt(34**2, as_str=True)
|
|
1264
1272
|
== """\
|
|
@@ -1277,6 +1285,8 @@ def test_long_sqrt():
|
|
|
1277
1285
|
def test_long_multiplication():
|
|
1278
1286
|
assert istr.long_multiplication(1234, 567) == [istr("1234"), istr("567"), istr("8638"), istr("7404"), istr("6170"), istr("699678")]
|
|
1279
1287
|
assert istr(1234).long_multiplication(567) == [istr("1234"), istr("567"), istr("8638"), istr("7404"), istr("6170"), istr("699678")]
|
|
1288
|
+
assert istr("01234").long_multiplication("0567") == [istr("1234"), istr("567"), istr("8638"), istr("7404"), istr("6170"), istr("699678")]
|
|
1289
|
+
|
|
1280
1290
|
assert (
|
|
1281
1291
|
istr.long_multiplication(1234, 567, as_str=True)
|
|
1282
1292
|
== """\
|
|
@@ -1294,6 +1304,8 @@ def test_long_multiplication():
|
|
|
1294
1304
|
def test_long_division():
|
|
1295
1305
|
assert istr.long_division(1395, 45) == [istr("31"), istr("45"), istr("1395"), istr("135"), istr("45"), istr("45"), istr("0")]
|
|
1296
1306
|
assert istr(1395).long_division(45) == [istr("31"), istr("45"), istr("1395"), istr("135"), istr("45"), istr("45"), istr("0")]
|
|
1307
|
+
assert istr.long_division(istr("01395"), istr("045")) == [istr("31"), istr("45"), istr("1395"), istr("135"), istr("45"), istr("45"), istr("0")]
|
|
1308
|
+
|
|
1297
1309
|
assert (
|
|
1298
1310
|
istr.long_division(1395, 45, as_str=True)
|
|
1299
1311
|
== """\
|
|
@@ -1,1084 +0,0 @@
|
|
|
1
|
-
# _ _
|
|
2
|
-
# (_) ___ | |_ _ __
|
|
3
|
-
# | |/ __|| __|| '__|
|
|
4
|
-
# | |\__ \| |_ | |
|
|
5
|
-
# |_||___/ \__||_|
|
|
6
|
-
# strings you can count on
|
|
7
|
-
|
|
8
|
-
__version__ = "1.1.33"
|
|
9
|
-
import functools
|
|
10
|
-
import itertools
|
|
11
|
-
import types
|
|
12
|
-
import sys
|
|
13
|
-
import inspect
|
|
14
|
-
import math
|
|
15
|
-
import operator
|
|
16
|
-
import copy
|
|
17
|
-
import bisect
|
|
18
|
-
import collections
|
|
19
|
-
import numbers
|
|
20
|
-
|
|
21
|
-
"""
|
|
22
|
-
Note: the changelog is in changelog.md
|
|
23
|
-
|
|
24
|
-
You can view the changelog on www.salabim.org/istr/changelog
|
|
25
|
-
|
|
26
|
-
The readme can be viewed on www.salabim.org/istr/
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
_0_to_Z = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class _range:
|
|
33
|
-
"""
|
|
34
|
-
based on https://codereview.stackexchange.com/questions/229073/pure-python-range-implementation
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
def __init__(self, cls, start, stop, step, length, base, int_format, repr_mode):
|
|
38
|
-
self.start, self.stop, self.step = process_start_stop_step_length(start, stop, step, length)
|
|
39
|
-
if self.step == 0:
|
|
40
|
-
raise ValueError(f"step must not be zero, not {self.step}")
|
|
41
|
-
if self.step < 0:
|
|
42
|
-
step_sign = -1
|
|
43
|
-
else:
|
|
44
|
-
step_sign = 1
|
|
45
|
-
self._len = max(1 + (self.stop - self.start - step_sign) // self.step, 0)
|
|
46
|
-
self.parent_cls = cls
|
|
47
|
-
self.base = cls._base if base is None else base
|
|
48
|
-
self.int_format = cls._int_format if int_format is None else int_format
|
|
49
|
-
self.repr_mode = cls._repr_mode if repr_mode is None else repr_mode
|
|
50
|
-
self.init_done = True
|
|
51
|
-
|
|
52
|
-
def __setattr__(self, name, value):
|
|
53
|
-
if getattr(self, "init_done", False):
|
|
54
|
-
raise AttributeError()
|
|
55
|
-
super().__setattr__(name, value)
|
|
56
|
-
|
|
57
|
-
def __contains__(self, value):
|
|
58
|
-
if isinstance(value, int):
|
|
59
|
-
return self._index(value) != -1
|
|
60
|
-
return any(n == value for n in self)
|
|
61
|
-
|
|
62
|
-
def __eq__(self, other):
|
|
63
|
-
if not isinstance(other, type(self)):
|
|
64
|
-
return False
|
|
65
|
-
if self._len != len(other):
|
|
66
|
-
return False
|
|
67
|
-
if self._len == 0:
|
|
68
|
-
return True
|
|
69
|
-
if self.start != other.start:
|
|
70
|
-
return False
|
|
71
|
-
if self[-1] == other[-1]:
|
|
72
|
-
return True
|
|
73
|
-
return False
|
|
74
|
-
|
|
75
|
-
def __getitem__(self, index):
|
|
76
|
-
def adjust_indices(length, start, stop, step):
|
|
77
|
-
if step is None:
|
|
78
|
-
step = 1
|
|
79
|
-
else:
|
|
80
|
-
step = int(step)
|
|
81
|
-
|
|
82
|
-
if start is None:
|
|
83
|
-
start = length - 1 if step < 0 else 0
|
|
84
|
-
else:
|
|
85
|
-
start = int(start)
|
|
86
|
-
if start < 0:
|
|
87
|
-
start += length
|
|
88
|
-
if start < 0:
|
|
89
|
-
start = -1 if step < 0 else 0
|
|
90
|
-
elif start >= length:
|
|
91
|
-
start = length - 1 if step < 0 else length
|
|
92
|
-
|
|
93
|
-
if stop is None:
|
|
94
|
-
stop = -1 if step < 0 else length
|
|
95
|
-
else:
|
|
96
|
-
stop = int(stop)
|
|
97
|
-
if stop < 0:
|
|
98
|
-
stop += length
|
|
99
|
-
if stop < 0:
|
|
100
|
-
stop = -1 if step < 0 else 0
|
|
101
|
-
elif stop >= length:
|
|
102
|
-
stop = length - 1 if step < 0 else length
|
|
103
|
-
|
|
104
|
-
return start, stop, step
|
|
105
|
-
|
|
106
|
-
if isinstance(index, slice):
|
|
107
|
-
start, stop, step = adjust_indices(self._len, index.start, index.stop, index.step)
|
|
108
|
-
return self.parent_cls.range(
|
|
109
|
-
self.start + self.step * start,
|
|
110
|
-
self.start + self.step * stop,
|
|
111
|
-
self.step * step,
|
|
112
|
-
base=self.base,
|
|
113
|
-
int_format=self.int_format,
|
|
114
|
-
repr_mode=self.repr_mode,
|
|
115
|
-
)
|
|
116
|
-
index = int(index)
|
|
117
|
-
if index < 0:
|
|
118
|
-
index += self._len
|
|
119
|
-
if not 0 <= index < self._len:
|
|
120
|
-
raise IndexError("range object index out of range")
|
|
121
|
-
return self.parent_cls(self.start + self.step * index, base=self.base, int_format=self.int_format, repr_mode=self.repr_mode)
|
|
122
|
-
|
|
123
|
-
def __hash__(self):
|
|
124
|
-
if self._len == 0:
|
|
125
|
-
return id(self.parent_cls.range)
|
|
126
|
-
return hash((self._len, self.start, int(self[-1])))
|
|
127
|
-
|
|
128
|
-
def __iter__(self):
|
|
129
|
-
value = self.start
|
|
130
|
-
if self.step > 0:
|
|
131
|
-
while value < self.stop:
|
|
132
|
-
yield self.parent_cls(value, base=self.base, int_format=self.int_format, repr_mode=self.repr_mode)
|
|
133
|
-
value += self.step
|
|
134
|
-
else:
|
|
135
|
-
while value > self.stop:
|
|
136
|
-
yield self.parent_cls(value, base=self.base, int_format=self.int_format, repr_mode=self.repr_mode)
|
|
137
|
-
value += self.step
|
|
138
|
-
|
|
139
|
-
def __len__(self):
|
|
140
|
-
return self._len
|
|
141
|
-
|
|
142
|
-
def __repr__(self):
|
|
143
|
-
if self.step == 1:
|
|
144
|
-
return f"{self.parent_cls.__name__}.range({self.start}, {self.stop})"
|
|
145
|
-
return f"{self.parent_cls.__name__}.range({self.start}, {self.stop}, {self.step})"
|
|
146
|
-
|
|
147
|
-
def __reversed__(self):
|
|
148
|
-
return iter(self[::-1])
|
|
149
|
-
|
|
150
|
-
def _index(self, value):
|
|
151
|
-
index_mul_step = value - self.start
|
|
152
|
-
if index_mul_step % self.step:
|
|
153
|
-
return -1
|
|
154
|
-
index = index_mul_step // self.step
|
|
155
|
-
if 0 <= index < self._len:
|
|
156
|
-
return index
|
|
157
|
-
return -1
|
|
158
|
-
|
|
159
|
-
def count(self, value):
|
|
160
|
-
"""
|
|
161
|
-
Rangeobject.count(value) -> integer
|
|
162
|
-
Return number of occurrences of value.
|
|
163
|
-
"""
|
|
164
|
-
return sum(1 for n in self if int(n) == int(value))
|
|
165
|
-
|
|
166
|
-
def index(self, value, start=0, stop=None):
|
|
167
|
-
if start < 0:
|
|
168
|
-
start = max(self._len + start, 0)
|
|
169
|
-
if stop is None:
|
|
170
|
-
stop = self._len
|
|
171
|
-
if stop < 0:
|
|
172
|
-
stop += self._len
|
|
173
|
-
|
|
174
|
-
if isinstance(value, int):
|
|
175
|
-
index = self._index(value)
|
|
176
|
-
if start <= index < stop:
|
|
177
|
-
return index
|
|
178
|
-
raise ValueError(f"{value} is not in range")
|
|
179
|
-
|
|
180
|
-
i = start
|
|
181
|
-
n = self.start + self.step * i
|
|
182
|
-
while i < stop:
|
|
183
|
-
if n == int(value):
|
|
184
|
-
return i
|
|
185
|
-
i += 1
|
|
186
|
-
n += self.step
|
|
187
|
-
raise ValueError(f"{value} is not in range")
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
class istr(str):
|
|
191
|
-
"""
|
|
192
|
-
istr object
|
|
193
|
-
|
|
194
|
-
Parameters
|
|
195
|
-
----------
|
|
196
|
-
value : any
|
|
197
|
-
if str, the value will to be interpreted as an int
|
|
198
|
-
istr('8') ==> istr('8')
|
|
199
|
-
if numeric, the value will be interpreted as an int
|
|
200
|
-
istr(8) ==> istr('8')
|
|
201
|
-
if str and starts with '=', the value will be retrieved from letter variables and other characters unprocessed, e.g.
|
|
202
|
-
if a=4 and b=32, istr('=ab1') will be istr'('4321')
|
|
203
|
-
if str and starts with ':=', the value will be retrieved from letter variables. And the corresponding value will be set, e.g.
|
|
204
|
-
if a=4 and b=32, istr(':=ab') will be istr'('432') and ab will be assigned that value too.
|
|
205
|
-
if a dict (or subtype of dict), the same type dict will be returned with all values istr'ed
|
|
206
|
-
istr({0: 0, 1: 1, 2: 4}) ==> {0: istr('0'), 1: istr('1'), 2: istr('4')}
|
|
207
|
-
if an iterator, the iterator will be mapped with istr
|
|
208
|
-
istr(i * i for i in range(3)) ==> <map object>
|
|
209
|
-
list(istr(i * i for i in range(3))) ==> [istr('0'), istr('1'), istr('4')]
|
|
210
|
-
if an iterable, the same type will be returned with all elements istr'ed
|
|
211
|
-
istr([0, 1, 4]) ==> [istr('0'), istr('1'), istr('4')]
|
|
212
|
-
istr((0, 1, 4)) ==> (istr('0'), istr('1'), istr('4'))
|
|
213
|
-
istr({0, 1, 4}) ==> {istr('4'), istr('0'), istr('1')} # or similar
|
|
214
|
-
if a range, an istr.range instance will be returned
|
|
215
|
-
istr(range(3)) ==> istr.range(3)
|
|
216
|
-
list(istr(range(3))) ==> [istr('0'), istr('1'), istr('2')]
|
|
217
|
-
len(istr(range(3))) ==> 3
|
|
218
|
-
if an istr, the same istr will be returned istr(istr('2')) ==> istr('2')
|
|
219
|
-
|
|
220
|
-
it is possible to give more than one parameter, in which case a tuple
|
|
221
|
-
of the istrs of the parameters will be returned, which can be handy
|
|
222
|
-
to multiple assign, e.g.
|
|
223
|
-
a, b, c = istr(5, 6, 7) ==> a=istr('5') , b=istr('6'), c=istr('7')
|
|
224
|
-
"""
|
|
225
|
-
|
|
226
|
-
__slots__ = ("_as_int", "_this_base", "_this_int_format", "_this_repr_mode")
|
|
227
|
-
|
|
228
|
-
_int_format = ""
|
|
229
|
-
_repr_mode = "istr"
|
|
230
|
-
_base = 10
|
|
231
|
-
_nan = object()
|
|
232
|
-
_digits_cache = {}
|
|
233
|
-
|
|
234
|
-
@classmethod
|
|
235
|
-
def _to_base(cls, number, base):
|
|
236
|
-
if number < 0:
|
|
237
|
-
raise ValueError(f"negative numbers are not allowed for base {base}")
|
|
238
|
-
result = ""
|
|
239
|
-
while number:
|
|
240
|
-
result += _0_to_Z[number % base]
|
|
241
|
-
number //= base
|
|
242
|
-
return result[::-1] or "0"
|
|
243
|
-
|
|
244
|
-
@classmethod
|
|
245
|
-
def _to_int(cls, value, base=10):
|
|
246
|
-
try:
|
|
247
|
-
if base != 10 and isinstance(value, str):
|
|
248
|
-
return int(value, base)
|
|
249
|
-
else:
|
|
250
|
-
return int(value)
|
|
251
|
-
except Exception:
|
|
252
|
-
return cls._nan
|
|
253
|
-
|
|
254
|
-
def __new__(cls, *value, namespace=None, base=None, int_format=None, repr_mode=None):
|
|
255
|
-
base = cls._base if base is None else base
|
|
256
|
-
int_format = cls._int_format if int_format is None else int_format
|
|
257
|
-
repr_mode = cls._repr_mode if repr_mode is None else repr_mode
|
|
258
|
-
if len(value) == 1:
|
|
259
|
-
value = value[0] # normal case of 1 parameter
|
|
260
|
-
elif len(value) == 0:
|
|
261
|
-
raise TypeError("no parameter given")
|
|
262
|
-
|
|
263
|
-
match value:
|
|
264
|
-
case range():
|
|
265
|
-
return cls.range(value.start, value.stop, value.step, base=base, int_format=int_format, repr_mode=repr_mode)
|
|
266
|
-
case _range():
|
|
267
|
-
return value
|
|
268
|
-
case cls():
|
|
269
|
-
if value.is_int():
|
|
270
|
-
return cls(value._as_int, base=base, int_format=int_format, repr_mode=repr_mode)
|
|
271
|
-
else:
|
|
272
|
-
return copy.copy(value)
|
|
273
|
-
case dict():
|
|
274
|
-
return type(value)(
|
|
275
|
-
(k, cls(v, base=base, int_format=int_format, repr_mode=repr_mode, namespace=get_namespace(namespace))) for k, v in value.items()
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
case _ if not isinstance(value, (str, type)) and hasattr(value, "__iter__"):
|
|
279
|
-
if hasattr(value, "__next__"):
|
|
280
|
-
return map(lambda v: cls(v, base=base, int_format=int_format, repr_mode=repr_mode, namespace=get_namespace(namespace)), value)
|
|
281
|
-
return type(value)(map(lambda v: cls(v, base=base, int_format=int_format, repr_mode=repr_mode, namespace=get_namespace(namespace)), value))
|
|
282
|
-
|
|
283
|
-
if isinstance(value, str) and (any(value.startswith(s) and value != s for s in ("=", ":="))):
|
|
284
|
-
if value[0] == "=":
|
|
285
|
-
value = str(cls.compose(value[1:], namespace=get_namespace(namespace)))
|
|
286
|
-
else: # it's :=
|
|
287
|
-
var_name = value[2:]
|
|
288
|
-
if not var_name.isidentifier():
|
|
289
|
-
raise ValueError(f"{var_name!r} is not a valid identifier")
|
|
290
|
-
value = str(cls.compose(var_name, namespace=get_namespace(namespace)))
|
|
291
|
-
get_namespace(namespace)[var_name] = cls(value)
|
|
292
|
-
as_int = cls._to_int(value, base)
|
|
293
|
-
if as_int is cls._nan or isinstance(value, str):
|
|
294
|
-
as_str = value
|
|
295
|
-
else:
|
|
296
|
-
if int_format == "" or base != 10:
|
|
297
|
-
if base == 10:
|
|
298
|
-
as_str = str(as_int)
|
|
299
|
-
else:
|
|
300
|
-
as_str = cls._to_base(as_int, base)
|
|
301
|
-
else:
|
|
302
|
-
as_str = f"{as_int:{int_format}}"
|
|
303
|
-
|
|
304
|
-
self = super().__new__(cls, as_str)
|
|
305
|
-
self._as_int = as_int
|
|
306
|
-
self._this_base = base
|
|
307
|
-
self._this_int_format = int_format
|
|
308
|
-
self._this_repr_mode = repr_mode
|
|
309
|
-
return self
|
|
310
|
-
|
|
311
|
-
def __iter__(self):
|
|
312
|
-
yield from self.__class__(super().__iter__())
|
|
313
|
-
|
|
314
|
-
def __hash__(self):
|
|
315
|
-
return hash((self.__class__, str(self)))
|
|
316
|
-
|
|
317
|
-
def __eq__(self, other):
|
|
318
|
-
if isinstance(other, istr):
|
|
319
|
-
if self.is_int() and other.is_int():
|
|
320
|
-
return self._as_int == other._as_int
|
|
321
|
-
if isinstance(other, str):
|
|
322
|
-
return super().__eq__(other)
|
|
323
|
-
if self.is_int():
|
|
324
|
-
try:
|
|
325
|
-
return self._as_int == int(other)
|
|
326
|
-
except Exception:
|
|
327
|
-
return False
|
|
328
|
-
return False
|
|
329
|
-
|
|
330
|
-
def __ne__(self, other):
|
|
331
|
-
return not self == other
|
|
332
|
-
|
|
333
|
-
def __repr__(self):
|
|
334
|
-
match self._this_repr_mode:
|
|
335
|
-
case "istr":
|
|
336
|
-
return f"{self.__class__.__name__}({repr(str(self))})"
|
|
337
|
-
case "int":
|
|
338
|
-
return "?" if self._as_int is self._nan else repr(self._as_int)
|
|
339
|
-
case _:
|
|
340
|
-
return repr(str(self))
|
|
341
|
-
|
|
342
|
-
def __bool__(self):
|
|
343
|
-
if self.is_int():
|
|
344
|
-
return bool(self._as_int)
|
|
345
|
-
return bool(str(self))
|
|
346
|
-
|
|
347
|
-
def _frepr(self, obj):
|
|
348
|
-
# like repr, but if obj is an istr, the as_repr is not used to make sure the
|
|
349
|
-
# the returned value is istr(...) and not infuenced by the repr mode
|
|
350
|
-
if isinstance(obj, self.__class__):
|
|
351
|
-
return f"{obj.__class__.__name__}({super(istr, obj).__repr__()})"
|
|
352
|
-
return repr(obj)
|
|
353
|
-
|
|
354
|
-
def _int_method(self, name, op, *args):
|
|
355
|
-
if len(args) == 1:
|
|
356
|
-
other = args[0]
|
|
357
|
-
if not self.is_int() or self._to_int(other) is self._nan:
|
|
358
|
-
if name.startswith("__r"):
|
|
359
|
-
raise TypeError(f"unsupported operand for {op}: {self._frepr(other)} and {self._frepr(self)}")
|
|
360
|
-
else:
|
|
361
|
-
raise TypeError(f"unsupported operand for {op}: {self._frepr(self)} and {self._frepr(other)}")
|
|
362
|
-
if "<" in op or ">" in op:
|
|
363
|
-
return getattr(self._as_int, name)(self._to_int(other))
|
|
364
|
-
else:
|
|
365
|
-
return self.__class__(getattr(self._as_int, name)(self._to_int(other)))
|
|
366
|
-
else:
|
|
367
|
-
if not self.is_int():
|
|
368
|
-
raise TypeError(f"unsupported operand for {op}: {self._frepr(self)}")
|
|
369
|
-
return self.__class__(getattr(self._as_int, name)())
|
|
370
|
-
|
|
371
|
-
for name_op in (
|
|
372
|
-
"__add__+ __radd__+ __sub__- __rsub__- __mul__* __rmul__* __floordiv__// __rfloordiv__// "
|
|
373
|
-
"__truediv__/ __rtruediv__/ __pow__** __rpow__** __mod__% __rmod__% "
|
|
374
|
-
"__divmod__divmod __rdivmod__divmod "
|
|
375
|
-
"__le__<= __lt__< __gt__> __ge__>= "
|
|
376
|
-
"__round__round __trunc__trunc __floor__floor __ceil__ceil __neg__- __pos__+ "
|
|
377
|
-
"__invert__~ __abs__abs "
|
|
378
|
-
).split():
|
|
379
|
-
i = len(name_op) - "".join(reversed(name_op)).find("_") # pos of last _
|
|
380
|
-
name = name_op[:i]
|
|
381
|
-
op = name_op[i:]
|
|
382
|
-
|
|
383
|
-
locals()[name] = functools.partialmethod(_int_method, name, op)
|
|
384
|
-
|
|
385
|
-
def __int__(self):
|
|
386
|
-
if not self.is_int():
|
|
387
|
-
raise ValueError(f"invalid literal for int(): {self._frepr(self)}")
|
|
388
|
-
return int(self._as_int)
|
|
389
|
-
|
|
390
|
-
def __float__(self):
|
|
391
|
-
if not self.is_int():
|
|
392
|
-
raise ValueError(f"invalid literal for float(): {self._frepr(self)}")
|
|
393
|
-
return float(self._as_int)
|
|
394
|
-
|
|
395
|
-
def __complex__(self):
|
|
396
|
-
if not self.is_int():
|
|
397
|
-
raise ValueError(f"invalid literal for complex(): {self._frepr(self)}")
|
|
398
|
-
return complex(self._as_int)
|
|
399
|
-
|
|
400
|
-
def is_even(self):
|
|
401
|
-
return istr.is_divisible_by(self, 2)
|
|
402
|
-
|
|
403
|
-
def is_odd(self):
|
|
404
|
-
return not istr.is_divisible_by(self, 2)
|
|
405
|
-
|
|
406
|
-
def is_palindrome(self):
|
|
407
|
-
self_as_str = istr.interpret_as_str(self)
|
|
408
|
-
return self_as_str == self_as_str[::-1]
|
|
409
|
-
|
|
410
|
-
def is_non_decreasing(self):
|
|
411
|
-
self_as_str = istr.interpret_as_str(self)
|
|
412
|
-
return all(i0 <= i1 for i0, i1 in zip(self_as_str, self_as_str[1:]))
|
|
413
|
-
|
|
414
|
-
def is_non_increasing(self):
|
|
415
|
-
self_as_str = istr.interpret_as_str(self)
|
|
416
|
-
return all(i0 >= i1 for i0, i1 in zip(self_as_str, self_as_str[1:]))
|
|
417
|
-
|
|
418
|
-
def is_increasing(self):
|
|
419
|
-
self_as_str = istr.interpret_as_str(self)
|
|
420
|
-
return all(i0 < i1 for i0, i1 in zip(self_as_str, self_as_str[1:]))
|
|
421
|
-
|
|
422
|
-
def is_decreasing(self):
|
|
423
|
-
self_as_str = istr.interpret_as_str(self)
|
|
424
|
-
return all(i0 > i1 for i0, i1 in zip(self_as_str, self_as_str[1:]))
|
|
425
|
-
|
|
426
|
-
def is_divisible_by(self, divisor):
|
|
427
|
-
return istr.divided_by(self, divisor) is not None
|
|
428
|
-
|
|
429
|
-
def divided_by(self, divisor, fallback=None):
|
|
430
|
-
if divisor == 0:
|
|
431
|
-
return fallback
|
|
432
|
-
quotient, remainder = divmod(istr.interpret_as_int(self), int(divisor))
|
|
433
|
-
return istr(quotient) if remainder == 0 else fallback
|
|
434
|
-
|
|
435
|
-
def is_prime(self):
|
|
436
|
-
n = istr.interpret_as_int(self)
|
|
437
|
-
if n < 1_000_000:
|
|
438
|
-
return n in istr._primes_up_to_1_000_000_as_set()
|
|
439
|
-
|
|
440
|
-
if not n & 1:
|
|
441
|
-
return False
|
|
442
|
-
|
|
443
|
-
for x in range(3, int(n**0.5) + 1, 2):
|
|
444
|
-
if n % x == 0:
|
|
445
|
-
return False
|
|
446
|
-
return True
|
|
447
|
-
|
|
448
|
-
@classmethod
|
|
449
|
-
def primes(cls, start=None, stop=None, /, length=None, cache=True):
|
|
450
|
-
"""
|
|
451
|
-
returns all primes up to a given upperbound or between a given lowerbound and upperbound
|
|
452
|
-
alternatively, the length can be given
|
|
453
|
-
"""
|
|
454
|
-
start, stop, step = process_start_stop_step_length(start, stop, 1, length)
|
|
455
|
-
if (cls, "primes", start, stop) in _cache:
|
|
456
|
-
return _cache[cls, "primes", start, stop]
|
|
457
|
-
|
|
458
|
-
if stop <= 1_000_000:
|
|
459
|
-
result = in_range(cls._primes_up_to_1_000_000(), start, stop)
|
|
460
|
-
else:
|
|
461
|
-
result = cls._primes(start, stop)
|
|
462
|
-
if cache:
|
|
463
|
-
_cache[cls, "primes", start, stop] = result
|
|
464
|
-
return result
|
|
465
|
-
|
|
466
|
-
@classmethod
|
|
467
|
-
def _primes(cls, start, stop):
|
|
468
|
-
sieve = bytearray(b"\x01") * (stop + 1)
|
|
469
|
-
sieve[0:2] = b"\x00\x00"
|
|
470
|
-
|
|
471
|
-
for i in range(2, int(stop**0.5) + 1):
|
|
472
|
-
if sieve[i]:
|
|
473
|
-
sieve[i * i : stop + 1 : i] = b"\x00" * (((stop - i * i) // i) + 1)
|
|
474
|
-
|
|
475
|
-
return list(map(cls, ([i for i, is_prime in enumerate(sieve) if is_prime and start <= i < stop]))) # range check just to be sure
|
|
476
|
-
|
|
477
|
-
@classmethod
|
|
478
|
-
@functools.lru_cache
|
|
479
|
-
def _primes_up_to_1_000_000(cls):
|
|
480
|
-
return cls._primes(0, 1_000_000)
|
|
481
|
-
|
|
482
|
-
@classmethod
|
|
483
|
-
@functools.lru_cache
|
|
484
|
-
def _primes_up_to_1_000_000_as_set(cls):
|
|
485
|
-
return set(map(int, cls._primes_up_to_1_000_000()))
|
|
486
|
-
|
|
487
|
-
def is_square(self):
|
|
488
|
-
return istr.is_power_of(self, 2)
|
|
489
|
-
|
|
490
|
-
def is_cube(self):
|
|
491
|
-
return istr.is_power_of(self, 3)
|
|
492
|
-
|
|
493
|
-
def is_power_of(self, exponent):
|
|
494
|
-
n = istr.interpret_as_int(self)
|
|
495
|
-
exponent = check_integer(exponent, "exponent")
|
|
496
|
-
if n < 0:
|
|
497
|
-
if exponent % 2 == 0:
|
|
498
|
-
return False
|
|
499
|
-
else:
|
|
500
|
-
n = -n
|
|
501
|
-
match exponent:
|
|
502
|
-
case 0:
|
|
503
|
-
return n == 1
|
|
504
|
-
case 1:
|
|
505
|
-
return True
|
|
506
|
-
case x if x < 0:
|
|
507
|
-
raise ValueError(f"exponent must be >=0; not {exponent}")
|
|
508
|
-
case _ if n < 1_000_000:
|
|
509
|
-
return n in istr._power_ofs_up_to_1_000_000_as_set(exponent)
|
|
510
|
-
case _:
|
|
511
|
-
...
|
|
512
|
-
return n == round(n ** (1 / exponent)) ** exponent
|
|
513
|
-
|
|
514
|
-
@classmethod
|
|
515
|
-
def squares(cls, start=None, stop=None, /, length=None, cache=True):
|
|
516
|
-
"""
|
|
517
|
-
returns all squares up to a given stop or between a given start and stop
|
|
518
|
-
alternatively, the length can be given
|
|
519
|
-
"""
|
|
520
|
-
return cls.power_ofs(2, start, stop, length=length, cache=cache)
|
|
521
|
-
|
|
522
|
-
@classmethod
|
|
523
|
-
def cubes(cls, start=None, stop=None, /, length=None, cache=True):
|
|
524
|
-
"""
|
|
525
|
-
returns all cubes up to a given stop or between a given start and stop
|
|
526
|
-
alternatively, the length can be given
|
|
527
|
-
"""
|
|
528
|
-
return cls.power_ofs(3, start, stop, length=length, cache=cache)
|
|
529
|
-
|
|
530
|
-
@classmethod
|
|
531
|
-
def power_ofs(cls, exponent, start=None, stop=None, /, length=None, cache=True):
|
|
532
|
-
"""
|
|
533
|
-
returns all power of n up to a given stop or between a given start and stop
|
|
534
|
-
alternatively, the length can be given
|
|
535
|
-
"""
|
|
536
|
-
start, stop, step = process_start_stop_step_length(start, stop, 1, length)
|
|
537
|
-
exponent = check_integer(exponent, "exponent")
|
|
538
|
-
|
|
539
|
-
if (cls, "power_ofs", exponent, start, stop) in _cache:
|
|
540
|
-
return _cache[cls, "power_ofs", exponent, start, stop]
|
|
541
|
-
match exponent:
|
|
542
|
-
case 0:
|
|
543
|
-
if start <= 1 < stop:
|
|
544
|
-
result = [istr(1)]
|
|
545
|
-
else:
|
|
546
|
-
result = []
|
|
547
|
-
case 1:
|
|
548
|
-
result = cls(list(range(start, stop)))
|
|
549
|
-
case x if (x % 2 == 0 or start >= 0) and stop <= 1_000_000:
|
|
550
|
-
result = in_range(cls._power_ofs_up_to_1_000_000(exponent), start, stop)
|
|
551
|
-
case _:
|
|
552
|
-
result = cls._power_ofs(exponent, start, stop)
|
|
553
|
-
if cache:
|
|
554
|
-
_cache[cls, "power_ofs", exponent, start, stop] = result
|
|
555
|
-
return result
|
|
556
|
-
|
|
557
|
-
@classmethod
|
|
558
|
-
def _power_ofs(cls, exponent, start, stop):
|
|
559
|
-
if exponent % 2 == 0:
|
|
560
|
-
start = max(0, start)
|
|
561
|
-
match exponent:
|
|
562
|
-
case 0:
|
|
563
|
-
if start <= 1 < stop:
|
|
564
|
-
result = [1]
|
|
565
|
-
else:
|
|
566
|
-
result = []
|
|
567
|
-
case 1:
|
|
568
|
-
result = [*range(start, stop)]
|
|
569
|
-
case _:
|
|
570
|
-
result = []
|
|
571
|
-
if start < 0: # can't be the case for even n (because of above limiting)
|
|
572
|
-
i = -int((-start) ** (1 / exponent))
|
|
573
|
-
else:
|
|
574
|
-
i = int(start ** (1 / exponent))
|
|
575
|
-
while (i_n := i**exponent) < stop:
|
|
576
|
-
if i_n >= start: # just to be sure
|
|
577
|
-
result.append(i_n)
|
|
578
|
-
i += 1
|
|
579
|
-
|
|
580
|
-
return list(map(cls, result))
|
|
581
|
-
|
|
582
|
-
@classmethod
|
|
583
|
-
@functools.lru_cache
|
|
584
|
-
def _power_ofs_up_to_1_000_000(cls, n):
|
|
585
|
-
return cls._power_ofs(n, 0, 1_000_000)
|
|
586
|
-
|
|
587
|
-
@classmethod
|
|
588
|
-
@functools.lru_cache
|
|
589
|
-
def _power_ofs_up_to_1_000_000_as_set(cls, n):
|
|
590
|
-
return set(map(int, cls._power_ofs_up_to_1_000_000(n)))
|
|
591
|
-
|
|
592
|
-
def decompose(self, letters, namespace=None):
|
|
593
|
-
"""
|
|
594
|
-
decompose one-letter variables into global variables
|
|
595
|
-
each one-letter variable must represent just one character
|
|
596
|
-
same one-letter variables represent the the same character
|
|
597
|
-
the istr must have the same length as the letters
|
|
598
|
-
"""
|
|
599
|
-
namespace = get_namespace(namespace)
|
|
600
|
-
|
|
601
|
-
lookup = {}
|
|
602
|
-
|
|
603
|
-
if len(letters) != len(self):
|
|
604
|
-
raise ValueError(f"incorrect number of variables {len(letters)}; should be {len(self)}")
|
|
605
|
-
|
|
606
|
-
for letter, ch in zip(letters, self):
|
|
607
|
-
if letter in lookup and lookup[letter] != ch:
|
|
608
|
-
raise ValueError(f"multiple values found for variable {letter}")
|
|
609
|
-
if not letter.isidentifier():
|
|
610
|
-
raise ValueError(f"{repr(letter)} cannot be used as a variable")
|
|
611
|
-
lookup[str(letter)] = ch
|
|
612
|
-
namespace.update(lookup)
|
|
613
|
-
|
|
614
|
-
@classmethod
|
|
615
|
-
def compose(cls, letters, namespace=None):
|
|
616
|
-
"""
|
|
617
|
-
compose an istr from individual letter variables
|
|
618
|
-
"""
|
|
619
|
-
namespace = get_namespace(namespace)
|
|
620
|
-
result = []
|
|
621
|
-
for letter in letters:
|
|
622
|
-
if letter.isidentifier():
|
|
623
|
-
if letter not in namespace:
|
|
624
|
-
raise ValueError(f"variable {repr(letter)} not defined")
|
|
625
|
-
result.append(str(namespace[letter]))
|
|
626
|
-
else:
|
|
627
|
-
result.append(letter)
|
|
628
|
-
return cls("".join(result))
|
|
629
|
-
|
|
630
|
-
def __or__(self, other):
|
|
631
|
-
if isinstance(other, str):
|
|
632
|
-
return self.__class__(str(self).__add__(other))
|
|
633
|
-
else:
|
|
634
|
-
raise TypeError(f"unsupported operand type(s) for |: {self._frepr(self)} and {self._frepr(other)}")
|
|
635
|
-
|
|
636
|
-
def __ror__(self, other):
|
|
637
|
-
if isinstance(other, str):
|
|
638
|
-
return self.__class__(other.__add__(str(self)))
|
|
639
|
-
else:
|
|
640
|
-
raise TypeError(f"unsupported operand type(s) for |: {self._frepr(other)} and {self._frepr(self)}")
|
|
641
|
-
|
|
642
|
-
def __matmul__(self, other):
|
|
643
|
-
try:
|
|
644
|
-
return self.__class__(super().__mul__(other))
|
|
645
|
-
except Exception: # TypeError:
|
|
646
|
-
raise TypeError(f"unsupported operand type(s) for @: {self._frepr(self)} and {self._frepr(other)}")
|
|
647
|
-
|
|
648
|
-
def __rmatmul__(self, other):
|
|
649
|
-
try:
|
|
650
|
-
return self.__class__(super().__rmul__(other))
|
|
651
|
-
except TypeError:
|
|
652
|
-
raise TypeError(f"unsupported operand type(s) for @|: {self._frepr(other)} and {self._frepr(self)}")
|
|
653
|
-
|
|
654
|
-
def __getitem__(self, key):
|
|
655
|
-
return self.__class__(super().__getitem__(key))
|
|
656
|
-
|
|
657
|
-
def all_distinct(self):
|
|
658
|
-
return len(self) == len(set(self))
|
|
659
|
-
|
|
660
|
-
def is_consecutive(self):
|
|
661
|
-
s = istr.interpret_as_str(self)
|
|
662
|
-
if len(s) <= 1:
|
|
663
|
-
return False
|
|
664
|
-
c0 = s[0]
|
|
665
|
-
for c1 in s[1:]:
|
|
666
|
-
if ord(c1) - ord(c0) != 1:
|
|
667
|
-
return False
|
|
668
|
-
c0 = c1
|
|
669
|
-
return True
|
|
670
|
-
|
|
671
|
-
def is_triangular(self):
|
|
672
|
-
n = istr.interpret_as_int(self)
|
|
673
|
-
if n <= 0:
|
|
674
|
-
return False
|
|
675
|
-
return istr.is_square(n * 8 + 1)
|
|
676
|
-
|
|
677
|
-
def reversed(self):
|
|
678
|
-
return self[::-1]
|
|
679
|
-
|
|
680
|
-
def ceil(self, divisible_by=1):
|
|
681
|
-
if divisible_by <= 0:
|
|
682
|
-
raise ValueError(f"step has to be >0, not {divisible_by}")
|
|
683
|
-
if divisible_by != int(divisible_by):
|
|
684
|
-
raise ValueError(f"step has to be an integer value, not {divisible_by}")
|
|
685
|
-
n = istr.interpret_as_float(self)
|
|
686
|
-
return istr((math.ceil(n / divisible_by)) * divisible_by)
|
|
687
|
-
|
|
688
|
-
def floor(self, divisible_by=1):
|
|
689
|
-
if divisible_by <= 0:
|
|
690
|
-
raise ValueError(f"step has to be >0, not {divisible_by}")
|
|
691
|
-
if divisible_by != int(divisible_by):
|
|
692
|
-
raise ValueError(f"step has to be an integer value, not {divisible_by}")
|
|
693
|
-
return istr((math.floor(self / divisible_by)) * divisible_by)
|
|
694
|
-
|
|
695
|
-
def interpret_as_int(self):
|
|
696
|
-
if isinstance(self, istr):
|
|
697
|
-
if not self.is_int():
|
|
698
|
-
raise TypeError(f"not interpretable as int: {self._frepr(self)}")
|
|
699
|
-
return self._as_int
|
|
700
|
-
if isinstance(self, collections.abc.Iterable) and not isinstance(self, str):
|
|
701
|
-
return int(istr.join(self))
|
|
702
|
-
|
|
703
|
-
return int(self)
|
|
704
|
-
|
|
705
|
-
def interpret_as_float(self):
|
|
706
|
-
if isinstance(self, istr):
|
|
707
|
-
if not self.is_int():
|
|
708
|
-
raise TypeError(f"not interpretable as float: {self._frepr(self)}")
|
|
709
|
-
return self._as_int
|
|
710
|
-
if isinstance(self, collections.abc.Iterable) and not isinstance(self, str):
|
|
711
|
-
return float(istr.join(self))
|
|
712
|
-
|
|
713
|
-
return float(self)
|
|
714
|
-
|
|
715
|
-
def interpret_as_str(self):
|
|
716
|
-
if isinstance(self, collections.abc.Iterable) and not isinstance(self, str):
|
|
717
|
-
return istr.join(self)
|
|
718
|
-
|
|
719
|
-
return str(self)
|
|
720
|
-
|
|
721
|
-
def _str_method(self, name, *args, **kwargs):
|
|
722
|
-
return self.__class__(getattr(super(), name)(*args, **kwargs))
|
|
723
|
-
|
|
724
|
-
for name in (
|
|
725
|
-
"capitalize casefold center expandtabs format join ljust lower lstrip partition removeprefix "
|
|
726
|
-
"removesuffix replace rjust rpartition rsplit rstrip split strip swapcase title translate upper zfill"
|
|
727
|
-
).split():
|
|
728
|
-
locals()[name] = functools.partialmethod(_str_method, name)
|
|
729
|
-
|
|
730
|
-
@classmethod
|
|
731
|
-
def zip(cls, *iterables, strict=False, join=False):
|
|
732
|
-
if join:
|
|
733
|
-
return cls.concat(cls(zip(*iterables, strict=strict)))
|
|
734
|
-
else:
|
|
735
|
-
return cls(zip(*iterables, strict=strict))
|
|
736
|
-
|
|
737
|
-
@classmethod
|
|
738
|
-
def batched(cls, iterable, n, *, strict=False, join=False): # will be overridden if in itertools and not Python 3.12
|
|
739
|
-
if n < 1:
|
|
740
|
-
raise ValueError("n must be at least one")
|
|
741
|
-
iterator = iter(iterable)
|
|
742
|
-
while batch := tuple(itertools.islice(iterator, n)):
|
|
743
|
-
if strict and len(batch) != n:
|
|
744
|
-
raise ValueError("istr.batched(): incomplete batch")
|
|
745
|
-
if join:
|
|
746
|
-
yield cls.join(cls(batch))
|
|
747
|
-
else:
|
|
748
|
-
yield cls(batch)
|
|
749
|
-
|
|
750
|
-
@classmethod
|
|
751
|
-
def _itertools_method(cls, name, *args, **kwargs):
|
|
752
|
-
return cls(getattr(itertools, name)(*args, **kwargs))
|
|
753
|
-
|
|
754
|
-
@classmethod
|
|
755
|
-
def _itertools_join_method(cls, name, *args, join=False, **kwargs):
|
|
756
|
-
res = cls(getattr(itertools, name)(*args, **kwargs))
|
|
757
|
-
return map(cls.join, res) if join else res
|
|
758
|
-
|
|
759
|
-
for name in dir(itertools):
|
|
760
|
-
if not name.startswith("_") and not name == "count": # count has its own method
|
|
761
|
-
match name:
|
|
762
|
-
case "groupby" | "tee":
|
|
763
|
-
locals()[name] = getattr(itertools, name)
|
|
764
|
-
case "permutations" | "combinations" | "combinations_with_replacement" | "product" | "batched" | "pairwise" | "zip_longest":
|
|
765
|
-
if name == "batched" and sys.version_info[:2] == (3, 12):
|
|
766
|
-
continue # version 3.12 does not support the strict parameter, so, we don't use the itertools method
|
|
767
|
-
locals()[name] = functools.partialmethod(_itertools_join_method, name)
|
|
768
|
-
case _:
|
|
769
|
-
locals()[name] = functools.partialmethod(_itertools_method, name)
|
|
770
|
-
|
|
771
|
-
def count(*args):
|
|
772
|
-
if len(args) >= 2 and isinstance(args[0], istr):
|
|
773
|
-
return str(args[0]).count(str(args[1]), *map(int, args[2:]))
|
|
774
|
-
else:
|
|
775
|
-
return istr(itertools.count(*args))
|
|
776
|
-
|
|
777
|
-
def is_int(self):
|
|
778
|
-
return self._as_int is not self._nan
|
|
779
|
-
|
|
780
|
-
def join(self, iterable=None):
|
|
781
|
-
if isinstance(self, istr):
|
|
782
|
-
return self.__class__(str(self).join(iterable))
|
|
783
|
-
if iterable is None:
|
|
784
|
-
return istr("").join(self)
|
|
785
|
-
if not isinstance(self, str):
|
|
786
|
-
raise TypeError(f"{self!r} should be istr, str or iterable, not {type(self)}")
|
|
787
|
-
return istr(self).join(iterable)
|
|
788
|
-
|
|
789
|
-
@classmethod
|
|
790
|
-
def concat(cls, iterable):
|
|
791
|
-
return map(cls.join, cls(iterable))
|
|
792
|
-
|
|
793
|
-
def prod(self, *, start=1):
|
|
794
|
-
return math.prod(self, start=istr(start))
|
|
795
|
-
|
|
796
|
-
@classmethod
|
|
797
|
-
def sumprod(cls, p, q, /, strict=True):
|
|
798
|
-
if "sumprod" in math.__dict__ and strict:
|
|
799
|
-
return cls(math.sumprod(p, q))
|
|
800
|
-
return sum(_map(operator.__mul__, cls(p), cls(q), strict=strict))
|
|
801
|
-
|
|
802
|
-
@classmethod
|
|
803
|
-
def enumerate(cls, iterable, start=0):
|
|
804
|
-
for i, value in enumerate(iterable, int(start)):
|
|
805
|
-
yield cls(i), value
|
|
806
|
-
|
|
807
|
-
def this_base(self):
|
|
808
|
-
return self._this_base
|
|
809
|
-
|
|
810
|
-
def this_int_format(self):
|
|
811
|
-
return self._this_int_format
|
|
812
|
-
|
|
813
|
-
def this_repr_mode(self):
|
|
814
|
-
return self._this_repr_mode
|
|
815
|
-
|
|
816
|
-
@classmethod
|
|
817
|
-
class int_format:
|
|
818
|
-
def __new__(cls, cls_int_format, int_format=None):
|
|
819
|
-
if int_format is None:
|
|
820
|
-
return cls_int_format._int_format
|
|
821
|
-
return super().__new__(cls)
|
|
822
|
-
|
|
823
|
-
def __init__(self, cls, int_format):
|
|
824
|
-
self.saved_int_format = cls._int_format
|
|
825
|
-
self.saved_cls = cls
|
|
826
|
-
if not (isinstance(int_format, str) and all(x in "0123456789" for x in int_format)):
|
|
827
|
-
raise ValueError(f"{repr(int_format)} is incorrect int_format")
|
|
828
|
-
|
|
829
|
-
cls._int_format = int_format
|
|
830
|
-
|
|
831
|
-
def __enter__(self): ...
|
|
832
|
-
|
|
833
|
-
def __exit__(self, exc_type, exc_value, exc_tb):
|
|
834
|
-
self.saved_cls._int_format = self.saved_int_format
|
|
835
|
-
|
|
836
|
-
@classmethod
|
|
837
|
-
class repr_mode:
|
|
838
|
-
def __new__(cls, cls_repr_mode, mode=None):
|
|
839
|
-
if mode is None:
|
|
840
|
-
return cls_repr_mode._repr_mode
|
|
841
|
-
if mode is int:
|
|
842
|
-
mode = "int"
|
|
843
|
-
if mode in ("istr", "str", "int"): # istr is used only for TypeErrors
|
|
844
|
-
return super().__new__(cls)
|
|
845
|
-
raise TypeError(f"mode not 'istr', 'str' or 'int', but {repr(mode)}")
|
|
846
|
-
|
|
847
|
-
def __init__(self, cls, mode):
|
|
848
|
-
self.saved_repr_mode = cls._repr_mode
|
|
849
|
-
self.saved_cls = cls
|
|
850
|
-
cls._repr_mode = mode
|
|
851
|
-
|
|
852
|
-
def __enter__(self): ...
|
|
853
|
-
|
|
854
|
-
def __exit__(self, exc_type, exc_value, exc_tb):
|
|
855
|
-
self.saved_cls._repr_mode = self.saved_repr_mode
|
|
856
|
-
|
|
857
|
-
@classmethod
|
|
858
|
-
class base:
|
|
859
|
-
def __new__(cls, cls_base, base=None):
|
|
860
|
-
if base is None:
|
|
861
|
-
return cls_base._base
|
|
862
|
-
if 2 <= base <= 36:
|
|
863
|
-
return super().__new__(cls)
|
|
864
|
-
raise ValueError(f"base not between 2 and 36, but {base}")
|
|
865
|
-
|
|
866
|
-
def __init__(self, cls, base):
|
|
867
|
-
self.saved_base = cls._base
|
|
868
|
-
self.saved_cls = cls
|
|
869
|
-
cls._base = base
|
|
870
|
-
|
|
871
|
-
def __enter__(self): ...
|
|
872
|
-
|
|
873
|
-
def __exit__(self, exc_type, exc_value, exc_tb):
|
|
874
|
-
self.saved_cls._base = self.saved_base
|
|
875
|
-
|
|
876
|
-
@classmethod
|
|
877
|
-
def range(cls, start=None, stop=None, step=1, /, length=None, base=None, int_format=None, repr_mode=None):
|
|
878
|
-
return _range(cls, start, stop, step, length, base=base, int_format=int_format, repr_mode=repr_mode)
|
|
879
|
-
|
|
880
|
-
@classmethod
|
|
881
|
-
def digits(cls, *args):
|
|
882
|
-
"""
|
|
883
|
-
return an istr of istr'ed digits as specified with args
|
|
884
|
-
|
|
885
|
-
if no args, 0-9 will be used
|
|
886
|
-
|
|
887
|
-
all given args will be used
|
|
888
|
-
each arg has to be either null string, <digit>, <digit>-<digit>, <digit>- or -<digit>
|
|
889
|
-
|
|
890
|
-
the digits may be '0' through '9' and 'A' through 'Z' (not case sensitive)
|
|
891
|
-
The returned value will always be in uppercase (if applicable).
|
|
892
|
-
|
|
893
|
-
Examples
|
|
894
|
-
--------
|
|
895
|
-
istr.digits() ==> istr('0123456789')
|
|
896
|
-
istr.digits('') ==> istr('0123456789')
|
|
897
|
-
istr.digits('1') ==> istr('1')
|
|
898
|
-
istr.digits('3-') ==> istr('3456789')
|
|
899
|
-
istr.digits('-3') ==> istr('0123')
|
|
900
|
-
istr('1-4', '6', '8-9') ==> istr('1234689')
|
|
901
|
-
istr('1', '1-2', '1-3') ==> istr('11213')
|
|
902
|
-
istr.digits('-z') ==> istr('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ')
|
|
903
|
-
istr.digits('C') ==> istr('C')
|
|
904
|
-
istr.digits('A-F') ==> istr('ABCDEF')
|
|
905
|
-
istr.digits('X-') ==> istr('XYZ')
|
|
906
|
-
|
|
907
|
-
Note
|
|
908
|
-
----
|
|
909
|
-
A digit can occur more than once.
|
|
910
|
-
"""
|
|
911
|
-
key = (args, cls._base, cls._int_format, cls._repr_mode)
|
|
912
|
-
if key in cls._digits_cache:
|
|
913
|
-
return cls._digits_cache[key]
|
|
914
|
-
result = []
|
|
915
|
-
if not args:
|
|
916
|
-
args = ["0-9"]
|
|
917
|
-
for arg in args:
|
|
918
|
-
if arg.strip() == "":
|
|
919
|
-
arg = "0-9"
|
|
920
|
-
pre, *post = arg.split("-", 1)
|
|
921
|
-
if pre.strip() == "":
|
|
922
|
-
pre = "0"
|
|
923
|
-
pre = pre.upper()
|
|
924
|
-
if len(pre) > 1 or pre not in _0_to_Z:
|
|
925
|
-
raise ValueError(f"incorrect specifier: {repr(arg)}")
|
|
926
|
-
start = _0_to_Z.index(pre)
|
|
927
|
-
|
|
928
|
-
if post:
|
|
929
|
-
post = post[0]
|
|
930
|
-
if post.strip() == "":
|
|
931
|
-
if pre in "0123456789":
|
|
932
|
-
post = "9"
|
|
933
|
-
else:
|
|
934
|
-
post = "Z"
|
|
935
|
-
post = post.upper()
|
|
936
|
-
if len(post) > 1 or post not in _0_to_Z:
|
|
937
|
-
raise ValueError(f"incorrect specifier: {repr(arg)}")
|
|
938
|
-
stop = _0_to_Z.index(post)
|
|
939
|
-
if start > stop:
|
|
940
|
-
raise ValueError(f"incorrect specifier: {repr(arg)}")
|
|
941
|
-
else:
|
|
942
|
-
stop = start
|
|
943
|
-
result.extend(_0_to_Z[i] for i in range(start, stop + 1))
|
|
944
|
-
|
|
945
|
-
result = cls("".join(result))
|
|
946
|
-
cls._digits_cache[key] = result
|
|
947
|
-
return result
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
def _map(func, *iterables, strict=False):
|
|
951
|
-
"""
|
|
952
|
-
like map, but with a strict parameter (also for Python < 3.14)
|
|
953
|
-
"""
|
|
954
|
-
if sys.version_info >= (3, 14):
|
|
955
|
-
yield from map(func, *iterables, strict=strict)
|
|
956
|
-
return
|
|
957
|
-
|
|
958
|
-
if not strict:
|
|
959
|
-
yield from map(func, *iterables)
|
|
960
|
-
return
|
|
961
|
-
|
|
962
|
-
iterators = [iter(it) for it in iterables]
|
|
963
|
-
|
|
964
|
-
while True:
|
|
965
|
-
values = []
|
|
966
|
-
exhausted = []
|
|
967
|
-
for it in iterators:
|
|
968
|
-
try:
|
|
969
|
-
v = next(it)
|
|
970
|
-
values.append(v)
|
|
971
|
-
exhausted.append(False)
|
|
972
|
-
except StopIteration:
|
|
973
|
-
values.append(None)
|
|
974
|
-
exhausted.append(True)
|
|
975
|
-
|
|
976
|
-
if all(exhausted):
|
|
977
|
-
return
|
|
978
|
-
|
|
979
|
-
if any(exhausted) and not all(exhausted):
|
|
980
|
-
raise ValueError("map_strict: iterables have different lengths")
|
|
981
|
-
|
|
982
|
-
yield func(*values)
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
_cache = {}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
def in_range(lst, start, stop):
|
|
989
|
-
"""
|
|
990
|
-
this function will give all values of lst in the range [start, stop)
|
|
991
|
-
|
|
992
|
-
Parameters
|
|
993
|
-
----------
|
|
994
|
-
lst : list
|
|
995
|
-
must be sorted!
|
|
996
|
-
|
|
997
|
-
start : int (or istr)
|
|
998
|
-
lowerbound
|
|
999
|
-
|
|
1000
|
-
stop : int (or istr)
|
|
1001
|
-
non inclusive upperbound
|
|
1002
|
-
|
|
1003
|
-
Returns
|
|
1004
|
-
-------
|
|
1005
|
-
list of all items in [start, stop)
|
|
1006
|
-
|
|
1007
|
-
Note
|
|
1008
|
-
----
|
|
1009
|
-
Equivalent to
|
|
1010
|
-
[item for item in lst if start <= item < stop], but more efficient.
|
|
1011
|
-
"""
|
|
1012
|
-
start = int(start)
|
|
1013
|
-
stop = int(stop)
|
|
1014
|
-
left = bisect.bisect_left(lst, start)
|
|
1015
|
-
right = bisect.bisect_left(lst, stop)
|
|
1016
|
-
return lst[left:right]
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
def check_integer(value, value_description):
|
|
1020
|
-
|
|
1021
|
-
if not isinstance(value, numbers.Number):
|
|
1022
|
-
try:
|
|
1023
|
-
return int(value)
|
|
1024
|
-
except TypeError:
|
|
1025
|
-
raise ValueError(f"{value_description} should be an integer, not {value}")
|
|
1026
|
-
|
|
1027
|
-
if value != int(value):
|
|
1028
|
-
raise ValueError(f"{value_description} should be an integer, not {value}")
|
|
1029
|
-
return int(value)
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
def process_start_stop_step_length(start, stop, step, length):
|
|
1033
|
-
if start is None and length is None:
|
|
1034
|
-
raise ValueError("no bound(s) or length specified")
|
|
1035
|
-
if length is not None:
|
|
1036
|
-
if start is not None:
|
|
1037
|
-
raise ValueError("both bound(s) and length specified")
|
|
1038
|
-
length = check_integer(length, "length")
|
|
1039
|
-
if length < 1:
|
|
1040
|
-
raise ValueError(f"length must be >=1, not {length}")
|
|
1041
|
-
start = 10 ** (length - 1)
|
|
1042
|
-
stop = start * 10
|
|
1043
|
-
else:
|
|
1044
|
-
start, stop = (0, start) if stop is None else (start, stop)
|
|
1045
|
-
start = check_integer(start, "start")
|
|
1046
|
-
stop = check_integer(stop, "upperbound")
|
|
1047
|
-
step = check_integer(step, "step")
|
|
1048
|
-
return start, stop, step
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
def get_namespace(namespace):
|
|
1052
|
-
if namespace is None:
|
|
1053
|
-
frame = real_caller_frame()
|
|
1054
|
-
namespace = frame.f_globals
|
|
1055
|
-
return namespace
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
def real_caller_frame():
|
|
1059
|
-
# this will return the frame of the first frame on the stack that does not belong to this module,
|
|
1060
|
-
# so in the 'user' space
|
|
1061
|
-
frame = inspect.currentframe()
|
|
1062
|
-
frame_name = frame.f_globals.get("__name__")
|
|
1063
|
-
frame = frame.f_back
|
|
1064
|
-
while frame is not None and frame_name == frame.f_globals.get("__name__"):
|
|
1065
|
-
frame = frame.f_back
|
|
1066
|
-
return frame
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
istr.type = type(istr(0))
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
class istrModule(types.ModuleType):
|
|
1073
|
-
def __call__(self, *args, **kwargs):
|
|
1074
|
-
return istr(*args, **kwargs)
|
|
1075
|
-
|
|
1076
|
-
def __setattr__(self, item, value):
|
|
1077
|
-
setattr(istr, item, value)
|
|
1078
|
-
|
|
1079
|
-
def __getattr__(self, item):
|
|
1080
|
-
return getattr(istr, item)
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
if __name__ != "__main__":
|
|
1084
|
-
sys.modules["istr"].__class__ = istrModule
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|