istr-python 1.1.43__tar.gz → 1.1.45__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.43 → istr_python-1.1.45}/PKG-INFO +58 -8
- {istr_python-1.1.43 → istr_python-1.1.45}/README.md +57 -7
- {istr_python-1.1.43 → istr_python-1.1.45}/istr/istr.py +130 -27
- {istr_python-1.1.43 → istr_python-1.1.45}/istr_python.egg-info/PKG-INFO +58 -8
- {istr_python-1.1.43 → istr_python-1.1.45}/pyproject.toml +1 -1
- {istr_python-1.1.43 → istr_python-1.1.45}/tests/test_istr.py +45 -2
- {istr_python-1.1.43 → istr_python-1.1.45}/istr/LICENSE.txt +0 -0
- {istr_python-1.1.43 → istr_python-1.1.45}/istr/__init__.py +0 -0
- {istr_python-1.1.43 → istr_python-1.1.45}/istr_python.egg-info/SOURCES.txt +0 -0
- {istr_python-1.1.43 → istr_python-1.1.45}/istr_python.egg-info/dependency_links.txt +0 -0
- {istr_python-1.1.43 → istr_python-1.1.45}/istr_python.egg-info/top_level.txt +0 -0
- {istr_python-1.1.43 → istr_python-1.1.45}/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.45
|
|
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
|
|
@@ -370,14 +370,20 @@ a, b, c = istr(*range(3)) ==> a=istr('0') , b=istr('1'), c=istr('2')
|
|
|
370
370
|
```
|
|
371
371
|
#### Alternative way of range specification
|
|
372
372
|
|
|
373
|
-
Instead of `istr(range(..))` it is possible to use `istr
|
|
373
|
+
Instead of `istr(range(..))` it is possible to use `istr.range(..)` instead, like
|
|
374
374
|
|
|
375
375
|
`list(istr.range(3,6))` ==> `[istr('3'), istr('4'), istr('5')]`
|
|
376
376
|
|
|
377
|
-
In contrast to the builtin `range`, `istr.range` supports a
|
|
377
|
+
In contrast to the builtin `range`, `istr.range` supports a keyword parameter `length`:
|
|
378
378
|
|
|
379
379
|
`list(istr.range(length=3))` is equivalent to `istr.range(100, 1000)`
|
|
380
380
|
|
|
381
|
+
On top of this, `istr.range` support a keyword parameter `distinct`, to return a generator that yields only numbers with distinct numbers:
|
|
382
|
+
|
|
383
|
+
`list(istr.range(98,104,distinct=True))` is `[istr('98'), istr('102'), istr('103')]`
|
|
384
|
+
|
|
385
|
+
For `istr.range(length=..., distict=True)` the generation is optimized (not just checking each possible value).
|
|
386
|
+
|
|
381
387
|
#### square root, cubic root and nth root
|
|
382
388
|
The methods `sqrt`, `cbrt`, `nth_root`provide the square root, cubic root and nth root of an istr. If there is no integer root, the fallback value (default `istr('0')` will be returned.
|
|
383
389
|
```
|
|
@@ -547,15 +553,15 @@ istr.divisors(18, False) ==> [istr('1'), istr('18'), istr('2'), istr('9'), istr(
|
|
|
547
553
|
|
|
548
554
|
#### test whether all characters are distinct
|
|
549
555
|
|
|
550
|
-
With the `
|
|
556
|
+
With the `is_distinct` method, it is possible to test whether all characters are distinct (i.e. no character appears more than once).
|
|
551
557
|
|
|
552
558
|
```
|
|
553
|
-
istr('01234').
|
|
554
|
-
istr('012340').
|
|
559
|
+
istr('01234').is_distict() ==> True
|
|
560
|
+
istr('012340').is_distict() ==> False
|
|
555
561
|
n98 = istr(98)
|
|
556
562
|
n100 = n98 + 2
|
|
557
|
-
istr(n98).
|
|
558
|
-
istr(n100).
|
|
563
|
+
istr(n98).is_distinct() ==> True
|
|
564
|
+
istr(n100).is_distinct() ==> False
|
|
559
565
|
```
|
|
560
566
|
#### test if characters are consecutive
|
|
561
567
|
|
|
@@ -824,8 +830,17 @@ Alternatively, it is possible to get a list of all squares, cubes, power_ofs or
|
|
|
824
830
|
|
|
825
831
|
`istr.squares (length=2)` returns a list of all squares of length 2, so between 10 and 99.
|
|
826
832
|
|
|
833
|
+
If the keyword `distinct` is True, all values that are not distinct, will be excluded, like:
|
|
834
|
+
|
|
835
|
+
`list(istr.primes(list(istr.primes(100,132,distinct=True))` is `[istr('103'), istr('107'), istr('109'), istr('127')]`, thus leaving out 101, 113 and 131
|
|
836
|
+
|
|
827
837
|
Unless `cache=False` is specified, the query result is cached.
|
|
828
838
|
|
|
839
|
+
#### filter distinct_only
|
|
840
|
+
|
|
841
|
+
With `istr.distinct_only` any iterable can skip values that are not distinct:
|
|
842
|
+
`list(istr.distinct_only([10, 11, 100, 123, 'abc', 'aaa']))` is `[istr('10'), istr('123'), istr('abc')]`
|
|
843
|
+
|
|
829
844
|
#### Safe indexing (getitem)
|
|
830
845
|
|
|
831
846
|
The `istr.getitem` method is essentially, a safe version of indexing an istr.
|
|
@@ -1041,6 +1056,41 @@ Note that `str(istr(":="))` is ":=" and does not assign any value.
|
|
|
1041
1056
|
|
|
1042
1057
|
Usually, composing and decomposing uses the globals namespace, but this can be overridden with the namespace parameter. See the test suite for details.
|
|
1043
1058
|
|
|
1059
|
+
#### Evaluating expressions with one letter variables
|
|
1060
|
+
The `istr.eval` method is essentially the same as `istr.compose`, but (safely) evaluates the result.
|
|
1061
|
+
E.g.
|
|
1062
|
+
|
|
1063
|
+
```
|
|
1064
|
+
a = 5
|
|
1065
|
+
b = 2
|
|
1066
|
+
print(istr.eval('aab + ba')
|
|
1067
|
+
```
|
|
1068
|
+
will print `577`
|
|
1069
|
+
|
|
1070
|
+
The operators +, -, * and / are supported.
|
|
1071
|
+
Comparisons (==, <=, <, >, >= are also supported. In that case, the result is also istr-ed, so istr('0') for False and istr('1') for True. E.g.
|
|
1072
|
+
|
|
1073
|
+
```
|
|
1074
|
+
a = 5
|
|
1075
|
+
b = 2
|
|
1076
|
+
print(istr.eval('a > b')
|
|
1077
|
+
```
|
|
1078
|
+
will print 1 (meaning True)
|
|
1079
|
+
|
|
1080
|
+
Note that leading zeroes are accepted. So, `istr.eval('03 + 7')` is `10`.
|
|
1081
|
+
Instead of ==, = can be used. So, `istr.eval('12 = 5 + 7')` is `1` (meaning True)
|
|
1082
|
+
|
|
1083
|
+
##### Alternative method
|
|
1084
|
+
|
|
1085
|
+
If istr is called with a string starting with a `?`, the string without the leading `?` will be evaluated using the new `istr.eval` method. So, the above examples read:
|
|
1086
|
+
```
|
|
1087
|
+
a = 5
|
|
1088
|
+
b = 2
|
|
1089
|
+
print(istr("?aab + ba':")
|
|
1090
|
+
print(istr("?a > b")
|
|
1091
|
+
```
|
|
1092
|
+
with output `577` and `1` (meaning True)
|
|
1093
|
+
|
|
1044
1094
|
#### Subclassing istr
|
|
1045
1095
|
|
|
1046
1096
|
When a class is derived from istr, all methods will return that newly derived class.
|
|
@@ -357,14 +357,20 @@ a, b, c = istr(*range(3)) ==> a=istr('0') , b=istr('1'), c=istr('2')
|
|
|
357
357
|
```
|
|
358
358
|
#### Alternative way of range specification
|
|
359
359
|
|
|
360
|
-
Instead of `istr(range(..))` it is possible to use `istr
|
|
360
|
+
Instead of `istr(range(..))` it is possible to use `istr.range(..)` instead, like
|
|
361
361
|
|
|
362
362
|
`list(istr.range(3,6))` ==> `[istr('3'), istr('4'), istr('5')]`
|
|
363
363
|
|
|
364
|
-
In contrast to the builtin `range`, `istr.range` supports a
|
|
364
|
+
In contrast to the builtin `range`, `istr.range` supports a keyword parameter `length`:
|
|
365
365
|
|
|
366
366
|
`list(istr.range(length=3))` is equivalent to `istr.range(100, 1000)`
|
|
367
367
|
|
|
368
|
+
On top of this, `istr.range` support a keyword parameter `distinct`, to return a generator that yields only numbers with distinct numbers:
|
|
369
|
+
|
|
370
|
+
`list(istr.range(98,104,distinct=True))` is `[istr('98'), istr('102'), istr('103')]`
|
|
371
|
+
|
|
372
|
+
For `istr.range(length=..., distict=True)` the generation is optimized (not just checking each possible value).
|
|
373
|
+
|
|
368
374
|
#### square root, cubic root and nth root
|
|
369
375
|
The methods `sqrt`, `cbrt`, `nth_root`provide the square root, cubic root and nth root of an istr. If there is no integer root, the fallback value (default `istr('0')` will be returned.
|
|
370
376
|
```
|
|
@@ -534,15 +540,15 @@ istr.divisors(18, False) ==> [istr('1'), istr('18'), istr('2'), istr('9'), istr(
|
|
|
534
540
|
|
|
535
541
|
#### test whether all characters are distinct
|
|
536
542
|
|
|
537
|
-
With the `
|
|
543
|
+
With the `is_distinct` method, it is possible to test whether all characters are distinct (i.e. no character appears more than once).
|
|
538
544
|
|
|
539
545
|
```
|
|
540
|
-
istr('01234').
|
|
541
|
-
istr('012340').
|
|
546
|
+
istr('01234').is_distict() ==> True
|
|
547
|
+
istr('012340').is_distict() ==> False
|
|
542
548
|
n98 = istr(98)
|
|
543
549
|
n100 = n98 + 2
|
|
544
|
-
istr(n98).
|
|
545
|
-
istr(n100).
|
|
550
|
+
istr(n98).is_distinct() ==> True
|
|
551
|
+
istr(n100).is_distinct() ==> False
|
|
546
552
|
```
|
|
547
553
|
#### test if characters are consecutive
|
|
548
554
|
|
|
@@ -811,8 +817,17 @@ Alternatively, it is possible to get a list of all squares, cubes, power_ofs or
|
|
|
811
817
|
|
|
812
818
|
`istr.squares (length=2)` returns a list of all squares of length 2, so between 10 and 99.
|
|
813
819
|
|
|
820
|
+
If the keyword `distinct` is True, all values that are not distinct, will be excluded, like:
|
|
821
|
+
|
|
822
|
+
`list(istr.primes(list(istr.primes(100,132,distinct=True))` is `[istr('103'), istr('107'), istr('109'), istr('127')]`, thus leaving out 101, 113 and 131
|
|
823
|
+
|
|
814
824
|
Unless `cache=False` is specified, the query result is cached.
|
|
815
825
|
|
|
826
|
+
#### filter distinct_only
|
|
827
|
+
|
|
828
|
+
With `istr.distinct_only` any iterable can skip values that are not distinct:
|
|
829
|
+
`list(istr.distinct_only([10, 11, 100, 123, 'abc', 'aaa']))` is `[istr('10'), istr('123'), istr('abc')]`
|
|
830
|
+
|
|
816
831
|
#### Safe indexing (getitem)
|
|
817
832
|
|
|
818
833
|
The `istr.getitem` method is essentially, a safe version of indexing an istr.
|
|
@@ -1028,6 +1043,41 @@ Note that `str(istr(":="))` is ":=" and does not assign any value.
|
|
|
1028
1043
|
|
|
1029
1044
|
Usually, composing and decomposing uses the globals namespace, but this can be overridden with the namespace parameter. See the test suite for details.
|
|
1030
1045
|
|
|
1046
|
+
#### Evaluating expressions with one letter variables
|
|
1047
|
+
The `istr.eval` method is essentially the same as `istr.compose`, but (safely) evaluates the result.
|
|
1048
|
+
E.g.
|
|
1049
|
+
|
|
1050
|
+
```
|
|
1051
|
+
a = 5
|
|
1052
|
+
b = 2
|
|
1053
|
+
print(istr.eval('aab + ba')
|
|
1054
|
+
```
|
|
1055
|
+
will print `577`
|
|
1056
|
+
|
|
1057
|
+
The operators +, -, * and / are supported.
|
|
1058
|
+
Comparisons (==, <=, <, >, >= are also supported. In that case, the result is also istr-ed, so istr('0') for False and istr('1') for True. E.g.
|
|
1059
|
+
|
|
1060
|
+
```
|
|
1061
|
+
a = 5
|
|
1062
|
+
b = 2
|
|
1063
|
+
print(istr.eval('a > b')
|
|
1064
|
+
```
|
|
1065
|
+
will print 1 (meaning True)
|
|
1066
|
+
|
|
1067
|
+
Note that leading zeroes are accepted. So, `istr.eval('03 + 7')` is `10`.
|
|
1068
|
+
Instead of ==, = can be used. So, `istr.eval('12 = 5 + 7')` is `1` (meaning True)
|
|
1069
|
+
|
|
1070
|
+
##### Alternative method
|
|
1071
|
+
|
|
1072
|
+
If istr is called with a string starting with a `?`, the string without the leading `?` will be evaluated using the new `istr.eval` method. So, the above examples read:
|
|
1073
|
+
```
|
|
1074
|
+
a = 5
|
|
1075
|
+
b = 2
|
|
1076
|
+
print(istr("?aab + ba':")
|
|
1077
|
+
print(istr("?a > b")
|
|
1078
|
+
```
|
|
1079
|
+
with output `577` and `1` (meaning True)
|
|
1080
|
+
|
|
1031
1081
|
#### Subclassing istr
|
|
1032
1082
|
|
|
1033
1083
|
When a class is derived from istr, all methods will return that newly derived class.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# |_||___/ \__||_|
|
|
6
6
|
# strings you can count on
|
|
7
7
|
|
|
8
|
-
__version__ = "1.1.
|
|
8
|
+
__version__ = "1.1.45"
|
|
9
9
|
import functools
|
|
10
10
|
import itertools
|
|
11
11
|
import types
|
|
@@ -17,6 +17,8 @@ import copy
|
|
|
17
17
|
import bisect
|
|
18
18
|
import collections
|
|
19
19
|
import numbers
|
|
20
|
+
import re
|
|
21
|
+
import ast
|
|
20
22
|
|
|
21
23
|
"""
|
|
22
24
|
Note: the changelog is in changelog.md
|
|
@@ -282,15 +284,18 @@ class istr(str):
|
|
|
282
284
|
return map(lambda v: cls(v, base=base, int_format=int_format, repr_mode=repr_mode, namespace=get_namespace(namespace)), value)
|
|
283
285
|
return type(value)(map(lambda v: cls(v, base=base, int_format=int_format, repr_mode=repr_mode, namespace=get_namespace(namespace)), value))
|
|
284
286
|
|
|
285
|
-
if isinstance(value, str) and (any(value.startswith(s) and value != s for s in ("=", ":="))):
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
287
|
+
if isinstance(value, str) and (any(value.startswith(s) and value != s for s in ("=", ":=",'?'))):
|
|
288
|
+
match value[0]:
|
|
289
|
+
case '?':
|
|
290
|
+
value = str(cls.eval(value[1:], namespace=get_namespace(namespace)))
|
|
291
|
+
case '=':
|
|
292
|
+
value = str(cls.compose(value[1:], namespace=get_namespace(namespace)))
|
|
293
|
+
case ':':
|
|
294
|
+
var_name = value[2:]
|
|
295
|
+
if not var_name.isidentifier():
|
|
296
|
+
raise ValueError(f"{var_name!r} is not a valid identifier")
|
|
297
|
+
value = str(cls.compose(var_name, namespace=get_namespace(namespace)))
|
|
298
|
+
get_namespace(namespace)[var_name] = cls(value)
|
|
294
299
|
as_int = cls._to_int(value, base)
|
|
295
300
|
if as_int is cls._nan or isinstance(value, str):
|
|
296
301
|
as_str = value
|
|
@@ -487,21 +492,25 @@ class istr(str):
|
|
|
487
492
|
return True
|
|
488
493
|
|
|
489
494
|
@classmethod
|
|
490
|
-
def primes(cls, start=None, stop=None, /, length=None, cache=True):
|
|
495
|
+
def primes(cls, start=None, stop=None, /, length=None, distinct=False, cache=True):
|
|
491
496
|
"""
|
|
492
497
|
returns all primes up to a given upperbound or between a given lowerbound and upperbound
|
|
493
498
|
alternatively, the length can be given
|
|
494
499
|
"""
|
|
495
500
|
start, stop, step = process_start_stop_step_length(start, stop, 1, length)
|
|
496
|
-
if (cls, "primes", start, stop) in _cache:
|
|
497
|
-
return _cache[cls, "primes", start, stop]
|
|
501
|
+
if (cls, "primes", start, stop,distinct) in _cache:
|
|
502
|
+
return _cache[cls, "primes", start, stop,distinct]
|
|
498
503
|
|
|
499
504
|
if stop <= 1_000_000:
|
|
500
505
|
result = in_range(cls._primes_up_to_1_000_000(), start, stop)
|
|
501
506
|
else:
|
|
502
507
|
result = cls._primes(start, stop)
|
|
508
|
+
if distinct:
|
|
509
|
+
result=list(filter(cls.is_distinct,result))
|
|
510
|
+
|
|
503
511
|
if cache:
|
|
504
|
-
_cache[cls, "primes", start, stop] = result
|
|
512
|
+
_cache[cls, "primes", start, stop,distinct] = result
|
|
513
|
+
|
|
505
514
|
return result
|
|
506
515
|
|
|
507
516
|
@classmethod
|
|
@@ -555,23 +564,23 @@ class istr(str):
|
|
|
555
564
|
return n == round(n ** (1 / exponent)) ** exponent
|
|
556
565
|
|
|
557
566
|
@classmethod
|
|
558
|
-
def squares(cls, start=None, stop=None, /, length=None, cache=True):
|
|
567
|
+
def squares(cls, start=None, stop=None, /, length=None, distinct=False, cache=True):
|
|
559
568
|
"""
|
|
560
569
|
returns all squares up to a given stop or between a given start and stop
|
|
561
570
|
alternatively, the length can be given
|
|
562
571
|
"""
|
|
563
|
-
return cls.power_ofs(2, start, stop, length=length, cache=cache)
|
|
572
|
+
return cls.power_ofs(2, start, stop, length=length, distinct=distinct, cache=cache)
|
|
564
573
|
|
|
565
574
|
@classmethod
|
|
566
|
-
def cubes(cls, start=None, stop=None, /, length=None, cache=True):
|
|
575
|
+
def cubes(cls, start=None, stop=None, /, length=None, distinct=False,cache=True):
|
|
567
576
|
"""
|
|
568
577
|
returns all cubes up to a given stop or between a given start and stop
|
|
569
578
|
alternatively, the length can be given
|
|
570
579
|
"""
|
|
571
|
-
return cls.power_ofs(3, start, stop, length=length, cache=cache)
|
|
580
|
+
return cls.power_ofs(3, start, stop, length=length, distinct=distinct,cache=cache)
|
|
572
581
|
|
|
573
582
|
@classmethod
|
|
574
|
-
def power_ofs(cls, exponent, start=None, stop=None, /, length=None, cache=True):
|
|
583
|
+
def power_ofs(cls, exponent, start=None, stop=None, /, length=None, distinct=None, cache=True):
|
|
575
584
|
"""
|
|
576
585
|
returns all power of n up to a given stop or between a given start and stop
|
|
577
586
|
alternatively, the length can be given
|
|
@@ -579,8 +588,8 @@ class istr(str):
|
|
|
579
588
|
start, stop, step = process_start_stop_step_length(start, stop, 1, length)
|
|
580
589
|
exponent = check_integer(exponent, "exponent")
|
|
581
590
|
|
|
582
|
-
if (cls, "power_ofs", exponent, start, stop) in _cache:
|
|
583
|
-
return _cache[cls, "power_ofs", exponent, start, stop]
|
|
591
|
+
if (cls, "power_ofs", exponent, start, stop,distinct) in _cache:
|
|
592
|
+
return _cache[cls, "power_ofs", exponent, start, stop,distinct]
|
|
584
593
|
match exponent:
|
|
585
594
|
case 0:
|
|
586
595
|
if start <= 1 < stop:
|
|
@@ -593,8 +602,10 @@ class istr(str):
|
|
|
593
602
|
result = in_range(cls._power_ofs_up_to_1_000_000(exponent), start, stop)
|
|
594
603
|
case _:
|
|
595
604
|
result = cls._power_ofs(exponent, start, stop)
|
|
605
|
+
if distinct:
|
|
606
|
+
result=list(filter(cls.is_distinct,result))
|
|
596
607
|
if cache:
|
|
597
|
-
_cache[cls, "power_ofs", exponent, start, stop] = result
|
|
608
|
+
_cache[cls, "power_ofs", exponent, start, stop, distinct] = result
|
|
598
609
|
return result
|
|
599
610
|
|
|
600
611
|
@classmethod
|
|
@@ -674,7 +685,20 @@ class istr(str):
|
|
|
674
685
|
result.append(str(namespace[letter]))
|
|
675
686
|
else:
|
|
676
687
|
result.append(letter)
|
|
677
|
-
|
|
688
|
+
|
|
689
|
+
result_str = "".join(result)
|
|
690
|
+
return cls(result_str)
|
|
691
|
+
|
|
692
|
+
@classmethod
|
|
693
|
+
def eval(cls, letters, namespace=None):
|
|
694
|
+
"""
|
|
695
|
+
evaluates an istr from individual letter variables
|
|
696
|
+
"""
|
|
697
|
+
expr = cls.compose(letters, namespace=namespace)
|
|
698
|
+
expr = re.sub(r"\b0+(\d+)\b", r"\1", expr) # delete leading zeroes
|
|
699
|
+
expr=re.sub(r'(?<![<>=!])=(?!=)', '==', expr) # support =
|
|
700
|
+
result = safe_eval(expr)
|
|
701
|
+
return cls(result)
|
|
678
702
|
|
|
679
703
|
def __or__(self, other):
|
|
680
704
|
if isinstance(other, str):
|
|
@@ -703,8 +727,12 @@ class istr(str):
|
|
|
703
727
|
def __getitem__(self, key):
|
|
704
728
|
return self.__class__(super().__getitem__(key))
|
|
705
729
|
|
|
706
|
-
def
|
|
707
|
-
|
|
730
|
+
def is_distinct(self):
|
|
731
|
+
s = istr.interpret_as_str(self)
|
|
732
|
+
return len(s) == len(set(s))
|
|
733
|
+
|
|
734
|
+
all_distinct=is_distinct
|
|
735
|
+
|
|
708
736
|
|
|
709
737
|
def is_consecutive(self):
|
|
710
738
|
s = istr.interpret_as_str(self)
|
|
@@ -859,6 +887,7 @@ class istr(str):
|
|
|
859
887
|
def enumerate(cls, iterable, start=0):
|
|
860
888
|
for i, value in enumerate(iterable, int(start)):
|
|
861
889
|
yield cls(i), value
|
|
890
|
+
|
|
862
891
|
|
|
863
892
|
@staticmethod
|
|
864
893
|
@functools.lru_cache(maxsize=1)
|
|
@@ -912,13 +941,26 @@ class istr(str):
|
|
|
912
941
|
return istr._long_sqrt(self.number)
|
|
913
942
|
case "line_lengths":
|
|
914
943
|
return [*map(len, istr._long_sqrt(self.number))]
|
|
944
|
+
case "indents":
|
|
945
|
+
lines = self.output
|
|
946
|
+
result = []
|
|
947
|
+
for line in lines.splitlines():
|
|
948
|
+
if '-' not in line:
|
|
949
|
+
for i,c in enumerate(line[2:]):
|
|
950
|
+
if c != ' ':
|
|
951
|
+
break
|
|
952
|
+
else:
|
|
953
|
+
i=0
|
|
954
|
+
result.append(i)
|
|
955
|
+
return result
|
|
956
|
+
|
|
915
957
|
case "output":
|
|
916
958
|
lines = istr._long_sqrt(self.number)
|
|
917
959
|
result = []
|
|
918
960
|
extra = len(number) % 2 == 0
|
|
919
961
|
result.append(f" {extra*' '}{' '.join(lines[0])}")
|
|
920
962
|
result.append(f" {len(lines[1])*'-'}")
|
|
921
|
-
result.append(f"
|
|
963
|
+
result.append(f"\\/{lines[1]}")
|
|
922
964
|
|
|
923
965
|
last_len = len(lines[2])
|
|
924
966
|
for i, line in enumerate(lines):
|
|
@@ -952,6 +994,8 @@ class istr(str):
|
|
|
952
994
|
|
|
953
995
|
return lines
|
|
954
996
|
|
|
997
|
+
|
|
998
|
+
|
|
955
999
|
def long_multiplication(self, number, as_str=False):
|
|
956
1000
|
class NumbersHolder:
|
|
957
1001
|
def __init__(self, number0, number1):
|
|
@@ -1139,7 +1183,18 @@ class istr(str):
|
|
|
1139
1183
|
self.saved_cls._base = self.saved_base
|
|
1140
1184
|
|
|
1141
1185
|
@classmethod
|
|
1142
|
-
def
|
|
1186
|
+
def _range_distinct(cls,length):
|
|
1187
|
+
for c0 in range(1,10):
|
|
1188
|
+
for crest in itertools.permutations(itertools.chain(range(0,c0),range(c0+1,10)),length-1):
|
|
1189
|
+
yield cls(c0)|cls.join(cls(crest))
|
|
1190
|
+
|
|
1191
|
+
@classmethod
|
|
1192
|
+
def range(cls, start=None, stop=None, step=1, /, length=None, distinct=False, base=None, int_format=None, repr_mode=None):
|
|
1193
|
+
if distinct:
|
|
1194
|
+
if length is None:
|
|
1195
|
+
return filter(cls.is_distinct,_range(cls,start,stop,step,length,base=base, int_format=int_format, repr_mode=repr_mode))
|
|
1196
|
+
else:
|
|
1197
|
+
return cls._range_distinct(length)
|
|
1143
1198
|
return _range(cls, start, stop, step, length, base=base, int_format=int_format, repr_mode=repr_mode)
|
|
1144
1199
|
|
|
1145
1200
|
@classmethod
|
|
@@ -1330,6 +1385,54 @@ def real_caller_frame():
|
|
|
1330
1385
|
return frame
|
|
1331
1386
|
|
|
1332
1387
|
|
|
1388
|
+
OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, ast.Pow: operator.pow}
|
|
1389
|
+
|
|
1390
|
+
COMPARE_OPS = {
|
|
1391
|
+
ast.Eq: operator.eq, # ==
|
|
1392
|
+
ast.NotEq: operator.ne, # !=
|
|
1393
|
+
ast.Lt: operator.lt, # <
|
|
1394
|
+
ast.LtE: operator.le, # <=
|
|
1395
|
+
ast.Gt: operator.gt, # >
|
|
1396
|
+
ast.GtE: operator.ge, # >=
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
|
|
1400
|
+
def safe_eval(expr):
|
|
1401
|
+
def evaluate(node):
|
|
1402
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float, str, bool)):
|
|
1403
|
+
return node.value
|
|
1404
|
+
|
|
1405
|
+
if isinstance(node, ast.BinOp) and type(node.op) in OPS:
|
|
1406
|
+
return OPS[type(node.op)](evaluate(node.left), evaluate(node.right))
|
|
1407
|
+
|
|
1408
|
+
if isinstance(node, ast.UnaryOp) and isinstance(node.op, (ast.UAdd, ast.USub)):
|
|
1409
|
+
value = evaluate(node.operand)
|
|
1410
|
+
return value if isinstance(node.op, ast.UAdd) else -value
|
|
1411
|
+
|
|
1412
|
+
if isinstance(node, ast.Compare):
|
|
1413
|
+
left = evaluate(node.left)
|
|
1414
|
+
|
|
1415
|
+
# Supports chained comparisons, e.g. 1 < 2 < 3
|
|
1416
|
+
for op, comparator in zip(node.ops, node.comparators):
|
|
1417
|
+
op_func = COMPARE_OPS.get(type(op))
|
|
1418
|
+
if op_func is None:
|
|
1419
|
+
raise ValueError("Unsupported comparison")
|
|
1420
|
+
|
|
1421
|
+
right = evaluate(comparator)
|
|
1422
|
+
|
|
1423
|
+
if not op_func(left, right):
|
|
1424
|
+
return False
|
|
1425
|
+
|
|
1426
|
+
left = right
|
|
1427
|
+
|
|
1428
|
+
return True
|
|
1429
|
+
|
|
1430
|
+
raise ValueError("Unsupported expression")
|
|
1431
|
+
|
|
1432
|
+
tree = ast.parse(expr, mode="eval")
|
|
1433
|
+
return evaluate(tree.body)
|
|
1434
|
+
|
|
1435
|
+
|
|
1333
1436
|
istr.type = type(istr(0))
|
|
1334
1437
|
|
|
1335
1438
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: istr-python
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.45
|
|
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
|
|
@@ -370,14 +370,20 @@ a, b, c = istr(*range(3)) ==> a=istr('0') , b=istr('1'), c=istr('2')
|
|
|
370
370
|
```
|
|
371
371
|
#### Alternative way of range specification
|
|
372
372
|
|
|
373
|
-
Instead of `istr(range(..))` it is possible to use `istr
|
|
373
|
+
Instead of `istr(range(..))` it is possible to use `istr.range(..)` instead, like
|
|
374
374
|
|
|
375
375
|
`list(istr.range(3,6))` ==> `[istr('3'), istr('4'), istr('5')]`
|
|
376
376
|
|
|
377
|
-
In contrast to the builtin `range`, `istr.range` supports a
|
|
377
|
+
In contrast to the builtin `range`, `istr.range` supports a keyword parameter `length`:
|
|
378
378
|
|
|
379
379
|
`list(istr.range(length=3))` is equivalent to `istr.range(100, 1000)`
|
|
380
380
|
|
|
381
|
+
On top of this, `istr.range` support a keyword parameter `distinct`, to return a generator that yields only numbers with distinct numbers:
|
|
382
|
+
|
|
383
|
+
`list(istr.range(98,104,distinct=True))` is `[istr('98'), istr('102'), istr('103')]`
|
|
384
|
+
|
|
385
|
+
For `istr.range(length=..., distict=True)` the generation is optimized (not just checking each possible value).
|
|
386
|
+
|
|
381
387
|
#### square root, cubic root and nth root
|
|
382
388
|
The methods `sqrt`, `cbrt`, `nth_root`provide the square root, cubic root and nth root of an istr. If there is no integer root, the fallback value (default `istr('0')` will be returned.
|
|
383
389
|
```
|
|
@@ -547,15 +553,15 @@ istr.divisors(18, False) ==> [istr('1'), istr('18'), istr('2'), istr('9'), istr(
|
|
|
547
553
|
|
|
548
554
|
#### test whether all characters are distinct
|
|
549
555
|
|
|
550
|
-
With the `
|
|
556
|
+
With the `is_distinct` method, it is possible to test whether all characters are distinct (i.e. no character appears more than once).
|
|
551
557
|
|
|
552
558
|
```
|
|
553
|
-
istr('01234').
|
|
554
|
-
istr('012340').
|
|
559
|
+
istr('01234').is_distict() ==> True
|
|
560
|
+
istr('012340').is_distict() ==> False
|
|
555
561
|
n98 = istr(98)
|
|
556
562
|
n100 = n98 + 2
|
|
557
|
-
istr(n98).
|
|
558
|
-
istr(n100).
|
|
563
|
+
istr(n98).is_distinct() ==> True
|
|
564
|
+
istr(n100).is_distinct() ==> False
|
|
559
565
|
```
|
|
560
566
|
#### test if characters are consecutive
|
|
561
567
|
|
|
@@ -824,8 +830,17 @@ Alternatively, it is possible to get a list of all squares, cubes, power_ofs or
|
|
|
824
830
|
|
|
825
831
|
`istr.squares (length=2)` returns a list of all squares of length 2, so between 10 and 99.
|
|
826
832
|
|
|
833
|
+
If the keyword `distinct` is True, all values that are not distinct, will be excluded, like:
|
|
834
|
+
|
|
835
|
+
`list(istr.primes(list(istr.primes(100,132,distinct=True))` is `[istr('103'), istr('107'), istr('109'), istr('127')]`, thus leaving out 101, 113 and 131
|
|
836
|
+
|
|
827
837
|
Unless `cache=False` is specified, the query result is cached.
|
|
828
838
|
|
|
839
|
+
#### filter distinct_only
|
|
840
|
+
|
|
841
|
+
With `istr.distinct_only` any iterable can skip values that are not distinct:
|
|
842
|
+
`list(istr.distinct_only([10, 11, 100, 123, 'abc', 'aaa']))` is `[istr('10'), istr('123'), istr('abc')]`
|
|
843
|
+
|
|
829
844
|
#### Safe indexing (getitem)
|
|
830
845
|
|
|
831
846
|
The `istr.getitem` method is essentially, a safe version of indexing an istr.
|
|
@@ -1041,6 +1056,41 @@ Note that `str(istr(":="))` is ":=" and does not assign any value.
|
|
|
1041
1056
|
|
|
1042
1057
|
Usually, composing and decomposing uses the globals namespace, but this can be overridden with the namespace parameter. See the test suite for details.
|
|
1043
1058
|
|
|
1059
|
+
#### Evaluating expressions with one letter variables
|
|
1060
|
+
The `istr.eval` method is essentially the same as `istr.compose`, but (safely) evaluates the result.
|
|
1061
|
+
E.g.
|
|
1062
|
+
|
|
1063
|
+
```
|
|
1064
|
+
a = 5
|
|
1065
|
+
b = 2
|
|
1066
|
+
print(istr.eval('aab + ba')
|
|
1067
|
+
```
|
|
1068
|
+
will print `577`
|
|
1069
|
+
|
|
1070
|
+
The operators +, -, * and / are supported.
|
|
1071
|
+
Comparisons (==, <=, <, >, >= are also supported. In that case, the result is also istr-ed, so istr('0') for False and istr('1') for True. E.g.
|
|
1072
|
+
|
|
1073
|
+
```
|
|
1074
|
+
a = 5
|
|
1075
|
+
b = 2
|
|
1076
|
+
print(istr.eval('a > b')
|
|
1077
|
+
```
|
|
1078
|
+
will print 1 (meaning True)
|
|
1079
|
+
|
|
1080
|
+
Note that leading zeroes are accepted. So, `istr.eval('03 + 7')` is `10`.
|
|
1081
|
+
Instead of ==, = can be used. So, `istr.eval('12 = 5 + 7')` is `1` (meaning True)
|
|
1082
|
+
|
|
1083
|
+
##### Alternative method
|
|
1084
|
+
|
|
1085
|
+
If istr is called with a string starting with a `?`, the string without the leading `?` will be evaluated using the new `istr.eval` method. So, the above examples read:
|
|
1086
|
+
```
|
|
1087
|
+
a = 5
|
|
1088
|
+
b = 2
|
|
1089
|
+
print(istr("?aab + ba':")
|
|
1090
|
+
print(istr("?a > b")
|
|
1091
|
+
```
|
|
1092
|
+
with output `577` and `1` (meaning True)
|
|
1093
|
+
|
|
1044
1094
|
#### Subclassing istr
|
|
1045
1095
|
|
|
1046
1096
|
When a class is derived from istr, all methods will return that newly derived class.
|
|
@@ -3,6 +3,8 @@ import itertools
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
5
5
|
import re
|
|
6
|
+
import time
|
|
7
|
+
from collections.abc import Iterator
|
|
6
8
|
|
|
7
9
|
if __name__ == "__main__": # to make the tests run without the pytest cli
|
|
8
10
|
import os, sys # three lines to use the local package and chdir
|
|
@@ -196,7 +198,30 @@ def test_range():
|
|
|
196
198
|
with pytest.raises(ValueError, match=re.escape("both bound(s) and length specified")):
|
|
197
199
|
istr.range(10, length=2)
|
|
198
200
|
|
|
199
|
-
|
|
201
|
+
assert list(map(int,istr.range(98,124,distinct=True)))==[98, 102, 103, 104, 105, 106, 107, 108, 109, 120, 123]
|
|
202
|
+
assert sorted(map(int,set(istr.range(length=2))-set(istr.range(length=2,distinct=True))))==[11, 22, 33, 44, 55, 66, 77, 88, 99]
|
|
203
|
+
|
|
204
|
+
assert type(istr.range(12)).__name__=='_range'
|
|
205
|
+
assert isinstance(istr.range(12,distinct=True),Iterator)
|
|
206
|
+
assert type(istr.range(length=2)).__name__=='_range'
|
|
207
|
+
assert isinstance(istr.range(length=2,distinct=True),Iterator)
|
|
208
|
+
|
|
209
|
+
t0=time.perf_counter()
|
|
210
|
+
result0=istr.range(length=4,distinct=True)
|
|
211
|
+
duration0=time.perf_counter()-t0
|
|
212
|
+
t0=time.perf_counter()
|
|
213
|
+
result1=istr.range(1000,10000,distinct=True)
|
|
214
|
+
duration1=time.perf_counter()-t0
|
|
215
|
+
assert list(result0)==list(result1)
|
|
216
|
+
assert duration1>duration0*2
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_various_distinct():
|
|
220
|
+
assert sorted(set(istr.primes(80,140))-set(istr.primes(80,140,distinct=True)))==istr([101, 113, 131])
|
|
221
|
+
assert sorted(set(istr.squares(150))-set(istr.squares(150,distinct=True)))==istr([100, 121,144])
|
|
222
|
+
assert sorted(set(istr.cubes(10000))-set(istr.cubes(10000,distinct=True)))==istr([343, 1000, 1331,2744,3375,8000])
|
|
223
|
+
assert sorted(set(istr.power_ofs(4,50000))-set(istr.power_ofs(4,50000,distinct=True)))==istr([6561,10000,14641])
|
|
224
|
+
|
|
200
225
|
def test_misc():
|
|
201
226
|
assert istr("") == ""
|
|
202
227
|
assert istr("") != 0
|
|
@@ -1128,8 +1153,26 @@ def test_decompose():
|
|
|
1128
1153
|
istr(12).decompose("xyz")
|
|
1129
1154
|
with pytest.raises(ValueError):
|
|
1130
1155
|
istr(123).decompose("xy1")
|
|
1156
|
+
|
|
1157
|
+
def test_eval():
|
|
1158
|
+
global a,b,c
|
|
1159
|
+
a = 3
|
|
1160
|
+
b = istr('4')
|
|
1161
|
+
c=istr(7)
|
|
1162
|
+
assert istr.eval('a + b')==7
|
|
1163
|
+
assert istr.eval('a + b = c')
|
|
1164
|
+
assert istr.eval('a + b > 6')
|
|
1165
|
+
assert not istr.eval('a + b < 6')
|
|
1166
|
+
with pytest.raises(ValueError):
|
|
1167
|
+
assert istr.eval('d')
|
|
1131
1168
|
|
|
1132
|
-
|
|
1169
|
+
assert istr('?a + b')==7
|
|
1170
|
+
assert istr('?a + b = c')
|
|
1171
|
+
assert istr('?a + b > 6')
|
|
1172
|
+
assert not istr('?a + b < 6')
|
|
1173
|
+
with pytest.raises(ValueError):
|
|
1174
|
+
assert istr('?d')
|
|
1175
|
+
|
|
1133
1176
|
def test_ceil():
|
|
1134
1177
|
assert istr(1000).ceil() == 1000
|
|
1135
1178
|
assert istr(1000).ceil(1) == 1000
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|