istr-python 1.1.42__tar.gz → 1.1.44__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: istr-python
3
- Version: 1.1.42
3
+ Version: 1.1.44
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
@@ -852,16 +852,24 @@ istr.getitem('1234',5, None) ==> None
852
852
  Note that this method has the advantage that it can accept an istr as index, in contrast to normal indexing.
853
853
 
854
854
  #### long square root
855
- The method `long_sqrt` gives a list of the lines of a long square root. Both the result (root) and the original number, possibly with a trailing 0 are returned, along with the calculated values.
856
- E.g. `istr.long_sqrt(123 ** 2)` results in
855
+ The method `long_sqrt` returns an instance of a class with attributes `lines`, `line_lengths` and `output`.
856
+
857
+ `lines` is list of the lines of a long square root. Both the result (root) and the original number, possibly with a trailing 0 are returned, along with the calculated values.
858
+ E.g. `istr.long_sqrt(123 ** 2).lines` results in
857
859
 
858
860
  `[istr('123'), istr('15129'), istr('1'), istr('51'), istr('44'), istr('729'), istr('729'), istr('0')]`
859
861
 
860
862
  Note the final `istr('0')` is included to support also long square roots that are not perfect.
861
863
 
862
- The method `long_sqrt`has an optional parameter `as_str`, that can be used to get a nice string representation of the long square root lines.
864
+ `line_lengths` returns a list of the length of the lines (not istr-ed). E.g.:
865
+
866
+ E.g. `istr.long_sqrt(123 ** 2).line_lengths` results in
867
+
868
+ `[3, 5, 1, 2, 2, 3, 3, 1]`
863
869
 
864
- E.g. `print(istr.long_sqrt(123 ** 2, as_str=True))` will print
870
+ `output` can be used to get a nice string representation of the long square root lines.
871
+
872
+ E.g. `print(istr.long_sqrt(123 ** 2).output)` will print
865
873
  ```
866
874
  1 2 3
867
875
  -----
@@ -879,18 +887,26 @@ E.g. `print(istr.long_sqrt(123 ** 2, as_str=True))` will print
879
887
 
880
888
  #### long multiplication
881
889
 
882
- The method `long_multiplication` gives a list of all lines to do a long multiplition.
890
+ The method `long_multiplication` returns an instance of a class with attributes `lines`, `line_lengths` and `output`.
891
+
892
+ `lines` is list of the lines of a long multiplication.
893
+
894
+ E.g. `print(istr.long_multiplication(1234,567).lines` will print
883
895
 
884
- E.g. `print(istr.long_multiplication(1234,567)` will print
885
-
886
896
  `[istr('1234'), istr('567'), istr('8638'), istr('7404'), istr('6170'), istr('699678')]`
887
-
888
- The method has an optional parameter `as_str`, that can be used t0 get a nice representation of the long multiplication lines.
889
-
890
- E.g. `print(istr.long_multiplication(1234,567, as_str=True)` will print
897
+
898
+ `line_lengths` returns a list of the length of the lines (not istr-ed). E.g.:
899
+
900
+ E. g. `print(istr.long_multiplication(1234,567).line_lengths` results in
901
+
902
+ `[4, 3, 4, 4, 4, 6]`
903
+
904
+ `output` can be used to get a nice string representation of the long multiplication lines.
905
+
906
+ E.g. `print(istr.long_multiplication(1234, 567).output)` will print
891
907
 
892
908
  ```
893
- 1234
909
+ 1234
894
910
  567
895
911
  ------ x
896
912
  8638
@@ -902,15 +918,23 @@ E.g. `print(istr.long_multiplication(1234,567, as_str=True)` will print
902
918
 
903
919
  #### long division
904
920
 
905
- The method `long_division` gives a list of all lines to do a long division (note that the divisor and dividend count as separate lines).
921
+ The method `long_division` returns an instance of a class with attributes `lines`, `line_lengths` and `output`.
906
922
 
907
- E.g. `print(istr.long_division(1395, 45)` will print
923
+ `lines` is a list of the lines of a long division.
908
924
 
909
- [istr('31'), istr('45'), istr('1395'), istr('135'), istr('45'), istr('45'), istr('0')]
910
-
911
- The method has an optional parameter `as_str`, that can be used to get a nice representation of the long multiplication lines.
912
-
913
- E.g. `print(istr.long_division(1395, 45, as_str=True)` will print
925
+ E.g. `print(istr.long_division(1395, 45).lines` will print
926
+
927
+ `[istr('31'), istr('45'), istr('1395'), istr('135'), istr('45'), istr('45'), istr('0')]`
928
+
929
+ Note the final `istr('0')` is included to support also long divisions roots that are not perfect.
930
+
931
+ E.g. `print(istr.long_division(1395, 45).line_lengths` will print
932
+
933
+ `[2, 2, 4, 3, 2, 2, 1]`
934
+
935
+ `output` can be used to get a nice string representation of the long divsion lines.
936
+
937
+ E.g. `print(istr.long_division(1395, 45).output` will print
914
938
 
915
939
  ```
916
940
  31
@@ -922,8 +946,8 @@ E.g. `print(istr.long_division(1395, 45, as_str=True)` will print
922
946
  45
923
947
  --
924
948
  0
925
- ```
926
-
949
+ ```
950
+
927
951
  #### generate istr with digits
928
952
 
929
953
  The class method `digits` can be used to return an istr of digits according to a given specification.
@@ -1017,6 +1041,41 @@ Note that `str(istr(":="))` is ":=" and does not assign any value.
1017
1041
 
1018
1042
  Usually, composing and decomposing uses the globals namespace, but this can be overridden with the namespace parameter. See the test suite for details.
1019
1043
 
1044
+ #### Evaluating expressions with one letter variables
1045
+ The `istr.eval` method is essentially the same as `istr.compose`, but (safely) evaluates the result.
1046
+ E.g.
1047
+
1048
+ ```
1049
+ a = 5
1050
+ b = 2
1051
+ print(istr.eval('aab + ba')
1052
+ ```
1053
+ will print `577`
1054
+
1055
+ The operators +, -, * and / are supported.
1056
+ 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.
1057
+
1058
+ ```
1059
+ a = 5
1060
+ b = 2
1061
+ print(istr.eval('a > b')
1062
+ ```
1063
+ will print 1 (meaning True)
1064
+
1065
+ Note that leading zeroes are accepted. So, `istr.eval('03 + 7')` is `10`.
1066
+ Instead of ==, = can be used. So, `istr.eval('12 = 5 + 7')` is `1` (meaning True)
1067
+
1068
+ ##### Alternative method
1069
+
1070
+ 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:
1071
+ ```
1072
+ a = 5
1073
+ b = 2
1074
+ print(istr("?aab + ba':")
1075
+ print(istr("?a > b")
1076
+ ```
1077
+ with output `577` and `1` (meaning True)
1078
+
1020
1079
  #### Subclassing istr
1021
1080
 
1022
1081
  When a class is derived from istr, all methods will return that newly derived class.
@@ -839,16 +839,24 @@ istr.getitem('1234',5, None) ==> None
839
839
  Note that this method has the advantage that it can accept an istr as index, in contrast to normal indexing.
840
840
 
841
841
  #### long square root
842
- The method `long_sqrt` gives a list of the lines of a long square root. Both the result (root) and the original number, possibly with a trailing 0 are returned, along with the calculated values.
843
- E.g. `istr.long_sqrt(123 ** 2)` results in
842
+ The method `long_sqrt` returns an instance of a class with attributes `lines`, `line_lengths` and `output`.
843
+
844
+ `lines` is list of the lines of a long square root. Both the result (root) and the original number, possibly with a trailing 0 are returned, along with the calculated values.
845
+ E.g. `istr.long_sqrt(123 ** 2).lines` results in
844
846
 
845
847
  `[istr('123'), istr('15129'), istr('1'), istr('51'), istr('44'), istr('729'), istr('729'), istr('0')]`
846
848
 
847
849
  Note the final `istr('0')` is included to support also long square roots that are not perfect.
848
850
 
849
- The method `long_sqrt`has an optional parameter `as_str`, that can be used to get a nice string representation of the long square root lines.
851
+ `line_lengths` returns a list of the length of the lines (not istr-ed). E.g.:
852
+
853
+ E.g. `istr.long_sqrt(123 ** 2).line_lengths` results in
854
+
855
+ `[3, 5, 1, 2, 2, 3, 3, 1]`
850
856
 
851
- E.g. `print(istr.long_sqrt(123 ** 2, as_str=True))` will print
857
+ `output` can be used to get a nice string representation of the long square root lines.
858
+
859
+ E.g. `print(istr.long_sqrt(123 ** 2).output)` will print
852
860
  ```
853
861
  1 2 3
854
862
  -----
@@ -866,18 +874,26 @@ E.g. `print(istr.long_sqrt(123 ** 2, as_str=True))` will print
866
874
 
867
875
  #### long multiplication
868
876
 
869
- The method `long_multiplication` gives a list of all lines to do a long multiplition.
877
+ The method `long_multiplication` returns an instance of a class with attributes `lines`, `line_lengths` and `output`.
878
+
879
+ `lines` is list of the lines of a long multiplication.
880
+
881
+ E.g. `print(istr.long_multiplication(1234,567).lines` will print
870
882
 
871
- E.g. `print(istr.long_multiplication(1234,567)` will print
872
-
873
883
  `[istr('1234'), istr('567'), istr('8638'), istr('7404'), istr('6170'), istr('699678')]`
874
-
875
- The method has an optional parameter `as_str`, that can be used t0 get a nice representation of the long multiplication lines.
876
-
877
- E.g. `print(istr.long_multiplication(1234,567, as_str=True)` will print
884
+
885
+ `line_lengths` returns a list of the length of the lines (not istr-ed). E.g.:
886
+
887
+ E. g. `print(istr.long_multiplication(1234,567).line_lengths` results in
888
+
889
+ `[4, 3, 4, 4, 4, 6]`
890
+
891
+ `output` can be used to get a nice string representation of the long multiplication lines.
892
+
893
+ E.g. `print(istr.long_multiplication(1234, 567).output)` will print
878
894
 
879
895
  ```
880
- 1234
896
+ 1234
881
897
  567
882
898
  ------ x
883
899
  8638
@@ -889,15 +905,23 @@ E.g. `print(istr.long_multiplication(1234,567, as_str=True)` will print
889
905
 
890
906
  #### long division
891
907
 
892
- The method `long_division` gives a list of all lines to do a long division (note that the divisor and dividend count as separate lines).
908
+ The method `long_division` returns an instance of a class with attributes `lines`, `line_lengths` and `output`.
893
909
 
894
- E.g. `print(istr.long_division(1395, 45)` will print
910
+ `lines` is a list of the lines of a long division.
895
911
 
896
- [istr('31'), istr('45'), istr('1395'), istr('135'), istr('45'), istr('45'), istr('0')]
897
-
898
- The method has an optional parameter `as_str`, that can be used to get a nice representation of the long multiplication lines.
899
-
900
- E.g. `print(istr.long_division(1395, 45, as_str=True)` will print
912
+ E.g. `print(istr.long_division(1395, 45).lines` will print
913
+
914
+ `[istr('31'), istr('45'), istr('1395'), istr('135'), istr('45'), istr('45'), istr('0')]`
915
+
916
+ Note the final `istr('0')` is included to support also long divisions roots that are not perfect.
917
+
918
+ E.g. `print(istr.long_division(1395, 45).line_lengths` will print
919
+
920
+ `[2, 2, 4, 3, 2, 2, 1]`
921
+
922
+ `output` can be used to get a nice string representation of the long divsion lines.
923
+
924
+ E.g. `print(istr.long_division(1395, 45).output` will print
901
925
 
902
926
  ```
903
927
  31
@@ -909,8 +933,8 @@ E.g. `print(istr.long_division(1395, 45, as_str=True)` will print
909
933
  45
910
934
  --
911
935
  0
912
- ```
913
-
936
+ ```
937
+
914
938
  #### generate istr with digits
915
939
 
916
940
  The class method `digits` can be used to return an istr of digits according to a given specification.
@@ -1004,6 +1028,41 @@ Note that `str(istr(":="))` is ":=" and does not assign any value.
1004
1028
 
1005
1029
  Usually, composing and decomposing uses the globals namespace, but this can be overridden with the namespace parameter. See the test suite for details.
1006
1030
 
1031
+ #### Evaluating expressions with one letter variables
1032
+ The `istr.eval` method is essentially the same as `istr.compose`, but (safely) evaluates the result.
1033
+ E.g.
1034
+
1035
+ ```
1036
+ a = 5
1037
+ b = 2
1038
+ print(istr.eval('aab + ba')
1039
+ ```
1040
+ will print `577`
1041
+
1042
+ The operators +, -, * and / are supported.
1043
+ 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.
1044
+
1045
+ ```
1046
+ a = 5
1047
+ b = 2
1048
+ print(istr.eval('a > b')
1049
+ ```
1050
+ will print 1 (meaning True)
1051
+
1052
+ Note that leading zeroes are accepted. So, `istr.eval('03 + 7')` is `10`.
1053
+ Instead of ==, = can be used. So, `istr.eval('12 = 5 + 7')` is `1` (meaning True)
1054
+
1055
+ ##### Alternative method
1056
+
1057
+ 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:
1058
+ ```
1059
+ a = 5
1060
+ b = 2
1061
+ print(istr("?aab + ba':")
1062
+ print(istr("?a > b")
1063
+ ```
1064
+ with output `577` and `1` (meaning True)
1065
+
1007
1066
  #### Subclassing istr
1008
1067
 
1009
1068
  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.42"
8
+ __version__ = "1.1.44"
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
- if value[0] == "=":
287
- value = str(cls.compose(value[1:], namespace=get_namespace(namespace)))
288
- else: # it's :=
289
- var_name = value[2:]
290
- if not var_name.isidentifier():
291
- raise ValueError(f"{var_name!r} is not a valid identifier")
292
- value = str(cls.compose(var_name, namespace=get_namespace(namespace)))
293
- get_namespace(namespace)[var_name] = cls(value)
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
@@ -674,7 +679,20 @@ class istr(str):
674
679
  result.append(str(namespace[letter]))
675
680
  else:
676
681
  result.append(letter)
677
- return cls("".join(result))
682
+
683
+ result_str = "".join(result)
684
+ return cls(result_str)
685
+
686
+ @classmethod
687
+ def eval(cls, letters, namespace=None):
688
+ """
689
+ evaluates an istr from individual letter variables
690
+ """
691
+ expr = cls.compose(letters, namespace=namespace)
692
+ expr = re.sub(r"\b0+(\d+)\b", r"\1", expr) # delete leading zeroes
693
+ expr=re.sub(r'(?<![<>=!])=(?!=)', '==', expr) # support =
694
+ result = safe_eval(expr)
695
+ return cls(result)
678
696
 
679
697
  def __or__(self, other):
680
698
  if isinstance(other, str):
@@ -727,14 +745,14 @@ class istr(str):
727
745
  return self[::-1]
728
746
 
729
747
  def ceil(self, divisible_by=1):
730
- if divisible_by != int(divisible_by) or divisible_by<1:
748
+ if divisible_by != int(divisible_by) or divisible_by < 1:
731
749
  raise ValueError(f"divisible_by has to be an integer value >=1 , not {divisible_by}")
732
750
  n = istr.interpret_as_float(self)
733
751
  return istr((math.ceil(n / divisible_by)) * divisible_by)
734
752
 
735
753
  def floor(self, divisible_by=1):
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}")
754
+ if divisible_by != int(divisible_by) or divisible_by < 1:
755
+ raise ValueError(f"divisible_by has to be an integer value >=1 , not {divisible_by}")
738
756
  n = istr.interpret_as_float(self)
739
757
  return istr((math.floor(n / divisible_by)) * divisible_by)
740
758
 
@@ -764,13 +782,13 @@ class istr(str):
764
782
 
765
783
  return str(self)
766
784
 
767
- def interpret_as_istr(self):
785
+ def interpret_as_istr(self):
768
786
  # ignore leading zeroes
769
787
  if isinstance(self, collections.abc.Iterable) and not isinstance(self, str):
770
- return istr.join(self)*1
788
+ return istr.join(self) * 1
789
+
790
+ return istr(self) * 1
771
791
 
772
- return istr(self)*1
773
-
774
792
  def _str_method(self, name, *args, **kwargs):
775
793
  return self.__class__(getattr(super(), name)(*args, **kwargs))
776
794
 
@@ -860,22 +878,15 @@ class istr(str):
860
878
  for i, value in enumerate(iterable, int(start)):
861
879
  yield cls(i), value
862
880
 
863
- def long_sqrt(self, as_str=False):
864
- """
865
- this function does long squre root and returns all the lines to be written.
866
- (modified from a ChatGPT code suggestion)
867
- returns root, number and the lines below the number, all istr-ed
868
- if as_str is True, a formatted output will be returned
869
- """
870
-
871
- number = istr.interpret_as_istr(self)
872
-
881
+ @staticmethod
882
+ @functools.lru_cache(maxsize=1)
883
+ def _long_sqrt(number):
873
884
  lines = []
874
- if len(number)%2==1:
875
- pairs=istr.chain(number[0],istr.batched(number[1:],2,join=True))
876
- else:
877
- pairs=istr.batched(number,2,join=True)
878
-
885
+ if len(number) % 2 == 1:
886
+ pairs = istr.chain(number[0], istr.batched(number[1:], 2, join=True))
887
+ else:
888
+ pairs = istr.batched(number, 2, join=True)
889
+
879
890
  root = istr(0)
880
891
  remainder = 0
881
892
 
@@ -899,118 +910,180 @@ class istr(str):
899
910
 
900
911
  lines.append(remainder)
901
912
  lines = [root] + [number] + lines[1:]
902
- if as_str:
903
- result = []
904
- extra = len(number)%2==0
905
- result.append(f" {extra*' '}{' '.join(lines[0])}")
906
- result.append(f" {len(lines[1])*'-'}")
907
- result.append(f"\/{lines[1]}")
908
-
909
- last_len=len(lines[2])
910
- for i, line in enumerate(lines):
911
-
912
- if i >= 2:
913
- if i == len(lines) - 1:
914
- n = i - 2 + extra
915
- else:
916
- n = i + extra
913
+ return lines
917
914
 
918
- if i % 2 == 0:
919
- result.append(f" {line:>{n}}")
920
- result.append(f" {last_len*'-':>{n}}")
921
- else:
922
- result.append(f" {line:>{n+1}}")
923
- last_len=len(line)
924
- return "\n".join(result)
925
- else:
926
- return lines
927
-
928
- def long_multiplication(self, number,as_str=False):
929
- number0 = istr.interpret_as_istr(self)
930
- number1=istr.interpret_as_istr(number)
931
- lines=[number0,number1]
915
+ def long_sqrt(self):
916
+ """
917
+ this function does long squre root and returns all the lines to be written.
918
+ (modified from a ChatGPT code suggestion)
919
+ returns root, number and the lines below the number, all istr-ed
920
+ if as_str is True, a formatted output will be returned
921
+ """
922
+
923
+ class NumberHolder:
924
+ def __init__(self, number):
925
+ self.number = number
926
+
927
+ def __getattr__(self, attr):
928
+ match attr:
929
+ case "lines":
930
+ return istr._long_sqrt(self.number)
931
+ case "line_lengths":
932
+ return [*map(len, istr._long_sqrt(self.number))]
933
+ case "output":
934
+ lines = istr._long_sqrt(self.number)
935
+ result = []
936
+ extra = len(number) % 2 == 0
937
+ result.append(f" {extra*' '}{' '.join(lines[0])}")
938
+ result.append(f" {len(lines[1])*'-'}")
939
+ result.append(f"\/{lines[1]}")
940
+
941
+ last_len = len(lines[2])
942
+ for i, line in enumerate(lines):
943
+ if i >= 2:
944
+ if i == len(lines) - 1:
945
+ n = i - 2 + extra
946
+ else:
947
+ n = i + extra
948
+
949
+ if i % 2 == 0:
950
+ result.append(f" {line:>{n}}")
951
+ result.append(f" {last_len*'-':>{n}}")
952
+ else:
953
+ result.append(f" {line:>{n+1}}")
954
+ last_len = len(line)
955
+ return "\n".join(result)
956
+ case _:
957
+ raise AttributeError()
958
+
959
+ number = istr.interpret_as_istr(self)
960
+
961
+ return NumberHolder(number)
962
+
963
+ @staticmethod
964
+ @functools.lru_cache(maxsize=1)
965
+ def _long_multiplication(number0, number1):
966
+ lines = [number0, number1]
932
967
  for i in number1.reversed():
933
- lines.append(number0*i)
934
- lines.append(number0*number1)
935
- if as_str:
936
- result=[]
937
- n=len(lines[-1])
938
- result.append(f'{lines[0]:>{n}}')
939
- result.append(f'{lines[1]:>{n}}')
940
- result.append(f"{n*'-'} x")
941
- for i,line in enumerate(lines[2:-1]):
942
- result.append(f"{line:>{n-i}}")
943
- result.append(f"{n*'-'}")
944
- result.append(lines[-1])
945
- return '\n'.join(result)
946
- else:
947
- return lines
948
-
949
- def long_division(self, divisor, as_str=False):
950
- dividend = istr.interpret_as_istr(self)
951
- divisor = istr.interpret_as_istr(divisor)
952
-
968
+ lines.append(number0 * i)
969
+ lines.append(number0 * number1)
970
+
971
+ return lines
972
+
973
+ def long_multiplication(self, number, as_str=False):
974
+ class NumbersHolder:
975
+ def __init__(self, number0, number1):
976
+ self.number0 = number0
977
+ self.number1 = number1
978
+
979
+ def __getattr__(self, attr):
980
+ match attr:
981
+ case "lines":
982
+ return istr._long_multiplication(self.number0, self.number1)
983
+ case "line_lengths":
984
+ return [*map(len, istr._long_multiplication(self.number0, self.number1))]
985
+ case "output":
986
+ lines = istr._long_multiplication(self.number0, self.number1)
987
+ result = []
988
+
989
+ n = len(lines[-1])
990
+ result.append(f"{lines[0]:>{n}}")
991
+ result.append(f"{lines[1]:>{n}}")
992
+ result.append(f"{n*'-'} x")
993
+ for i, line in enumerate(lines[2:-1]):
994
+ result.append(f"{line:>{n-i}}")
995
+ result.append(f"{n*'-'}")
996
+ result.append(lines[-1])
997
+ return "\n".join(result)
998
+ case _:
999
+ raise AttributeError()
1000
+
1001
+ number0 = istr.interpret_as_istr(self)
1002
+ number1 = istr.interpret_as_istr(number)
1003
+ return NumbersHolder(number0, number1)
1004
+
1005
+ @staticmethod
1006
+ @functools.lru_cache(maxsize=1)
1007
+ def _long_division(dividend, divisor, as_str=False):
953
1008
  if dividend < 0:
954
1009
  raise ValueError("dividend must be non-negative.")
955
-
1010
+
956
1011
  if divisor <= 0:
957
1012
  raise ValueError("divisor must be positive.")
958
-
1013
+
959
1014
  dividend_start = len(divisor) + 3
960
1015
  total_width = dividend_start + len(dividend)
961
-
1016
+
962
1017
  quotient_characters = [" "] * len(dividend)
963
-
1018
+
964
1019
  remainder = 0
965
1020
  division_started = False
966
- lines=[]
967
-
1021
+ lines = []
1022
+
968
1023
  for digit_index, digit_character in enumerate(dividend):
969
1024
  current = remainder * 10 + digit_character
970
1025
  quotient_digit = current // divisor
971
-
1026
+
972
1027
  if quotient_digit != 0 or division_started:
973
1028
  division_started = True
974
-
1029
+
975
1030
  if digit_index == len(dividend) - 1:
976
1031
  division_started = True
977
-
1032
+
978
1033
  if not division_started:
979
1034
  remainder = current
980
1035
  continue
981
-
1036
+
982
1037
  quotient_characters[digit_index] = quotient_digit
983
-
1038
+
984
1039
  product = quotient_digit * divisor
985
1040
  remainder = current - product
986
-
1041
+
987
1042
  if as_str:
988
1043
  end_column = dividend_start + digit_index
989
1044
  current_start = end_column - len(current) + 1
990
- product_start = end_column - len(product) + 1
1045
+ product_start = end_column - len(product) + 1
991
1046
  bar_start = min(current_start, product_start)
992
1047
  bar_length = end_column - bar_start + 1
993
1048
  lines.append(" " * current_start | current)
994
1049
  lines.append(" " * product_start | product)
995
1050
  lines.append(" " * bar_start + "-" * bar_length)
996
-
1051
+
997
1052
  else:
998
1053
  lines.append(current)
999
1054
  lines.append(product)
1000
-
1055
+
1001
1056
  quotient = istr.join(quotient_characters).rstrip()
1002
1057
  if as_str:
1003
1058
  remainder_start = total_width - len(remainder)
1004
1059
  lines.append(" " * remainder_start | remainder)
1005
- lines=[" " * dividend_start | quotient," " * dividend_start + "-" * len(dividend),f"{divisor} ) {dividend}",*lines[1:]]
1060
+ lines = [" " * dividend_start | quotient, " " * dividend_start + "-" * len(dividend), f"{divisor} ) {dividend}", *lines[1:]]
1006
1061
  return "\n".join(lines)
1007
-
1062
+
1008
1063
  else:
1009
1064
  lines.append(remainder)
1010
- lines=[istr(quotient),divisor,dividend,*lines[1:]]
1011
- return lines
1012
-
1013
-
1065
+ lines = [istr(quotient), divisor, dividend, *lines[1:]]
1066
+ return lines
1067
+
1068
+ def long_division(self, divisor):
1069
+ class NumbersHolder:
1070
+ def __init__(self, dividend, divisor):
1071
+ self.dividend = dividend
1072
+ self.divisor = divisor
1073
+
1074
+ def __getattr__(self, attr):
1075
+ match attr:
1076
+ case "lines":
1077
+ return istr._long_division(self.dividend, self.divisor)
1078
+ case "line_lengths":
1079
+ return [*map(len, istr._long_division(self.dividend, self.divisor))]
1080
+ case "output":
1081
+ return istr._long_division(self.dividend, self.divisor, as_str=True)
1082
+
1083
+ dividend = istr.interpret_as_istr(self)
1084
+ divisor = istr.interpret_as_istr(divisor)
1085
+ return NumbersHolder(dividend, divisor)
1086
+
1014
1087
  def this_base(self):
1015
1088
  return self._this_base
1016
1089
 
@@ -1275,6 +1348,54 @@ def real_caller_frame():
1275
1348
  return frame
1276
1349
 
1277
1350
 
1351
+ OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, ast.Pow: operator.pow}
1352
+
1353
+ COMPARE_OPS = {
1354
+ ast.Eq: operator.eq, # ==
1355
+ ast.NotEq: operator.ne, # !=
1356
+ ast.Lt: operator.lt, # <
1357
+ ast.LtE: operator.le, # <=
1358
+ ast.Gt: operator.gt, # >
1359
+ ast.GtE: operator.ge, # >=
1360
+ }
1361
+
1362
+
1363
+ def safe_eval(expr):
1364
+ def evaluate(node):
1365
+ if isinstance(node, ast.Constant) and isinstance(node.value, (int, float, str, bool)):
1366
+ return node.value
1367
+
1368
+ if isinstance(node, ast.BinOp) and type(node.op) in OPS:
1369
+ return OPS[type(node.op)](evaluate(node.left), evaluate(node.right))
1370
+
1371
+ if isinstance(node, ast.UnaryOp) and isinstance(node.op, (ast.UAdd, ast.USub)):
1372
+ value = evaluate(node.operand)
1373
+ return value if isinstance(node.op, ast.UAdd) else -value
1374
+
1375
+ if isinstance(node, ast.Compare):
1376
+ left = evaluate(node.left)
1377
+
1378
+ # Supports chained comparisons, e.g. 1 < 2 < 3
1379
+ for op, comparator in zip(node.ops, node.comparators):
1380
+ op_func = COMPARE_OPS.get(type(op))
1381
+ if op_func is None:
1382
+ raise ValueError("Unsupported comparison")
1383
+
1384
+ right = evaluate(comparator)
1385
+
1386
+ if not op_func(left, right):
1387
+ return False
1388
+
1389
+ left = right
1390
+
1391
+ return True
1392
+
1393
+ raise ValueError("Unsupported expression")
1394
+
1395
+ tree = ast.parse(expr, mode="eval")
1396
+ return evaluate(tree.body)
1397
+
1398
+
1278
1399
  istr.type = type(istr(0))
1279
1400
 
1280
1401
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: istr-python
3
- Version: 1.1.42
3
+ Version: 1.1.44
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
@@ -852,16 +852,24 @@ istr.getitem('1234',5, None) ==> None
852
852
  Note that this method has the advantage that it can accept an istr as index, in contrast to normal indexing.
853
853
 
854
854
  #### long square root
855
- The method `long_sqrt` gives a list of the lines of a long square root. Both the result (root) and the original number, possibly with a trailing 0 are returned, along with the calculated values.
856
- E.g. `istr.long_sqrt(123 ** 2)` results in
855
+ The method `long_sqrt` returns an instance of a class with attributes `lines`, `line_lengths` and `output`.
856
+
857
+ `lines` is list of the lines of a long square root. Both the result (root) and the original number, possibly with a trailing 0 are returned, along with the calculated values.
858
+ E.g. `istr.long_sqrt(123 ** 2).lines` results in
857
859
 
858
860
  `[istr('123'), istr('15129'), istr('1'), istr('51'), istr('44'), istr('729'), istr('729'), istr('0')]`
859
861
 
860
862
  Note the final `istr('0')` is included to support also long square roots that are not perfect.
861
863
 
862
- The method `long_sqrt`has an optional parameter `as_str`, that can be used to get a nice string representation of the long square root lines.
864
+ `line_lengths` returns a list of the length of the lines (not istr-ed). E.g.:
865
+
866
+ E.g. `istr.long_sqrt(123 ** 2).line_lengths` results in
867
+
868
+ `[3, 5, 1, 2, 2, 3, 3, 1]`
863
869
 
864
- E.g. `print(istr.long_sqrt(123 ** 2, as_str=True))` will print
870
+ `output` can be used to get a nice string representation of the long square root lines.
871
+
872
+ E.g. `print(istr.long_sqrt(123 ** 2).output)` will print
865
873
  ```
866
874
  1 2 3
867
875
  -----
@@ -879,18 +887,26 @@ E.g. `print(istr.long_sqrt(123 ** 2, as_str=True))` will print
879
887
 
880
888
  #### long multiplication
881
889
 
882
- The method `long_multiplication` gives a list of all lines to do a long multiplition.
890
+ The method `long_multiplication` returns an instance of a class with attributes `lines`, `line_lengths` and `output`.
891
+
892
+ `lines` is list of the lines of a long multiplication.
893
+
894
+ E.g. `print(istr.long_multiplication(1234,567).lines` will print
883
895
 
884
- E.g. `print(istr.long_multiplication(1234,567)` will print
885
-
886
896
  `[istr('1234'), istr('567'), istr('8638'), istr('7404'), istr('6170'), istr('699678')]`
887
-
888
- The method has an optional parameter `as_str`, that can be used t0 get a nice representation of the long multiplication lines.
889
-
890
- E.g. `print(istr.long_multiplication(1234,567, as_str=True)` will print
897
+
898
+ `line_lengths` returns a list of the length of the lines (not istr-ed). E.g.:
899
+
900
+ E. g. `print(istr.long_multiplication(1234,567).line_lengths` results in
901
+
902
+ `[4, 3, 4, 4, 4, 6]`
903
+
904
+ `output` can be used to get a nice string representation of the long multiplication lines.
905
+
906
+ E.g. `print(istr.long_multiplication(1234, 567).output)` will print
891
907
 
892
908
  ```
893
- 1234
909
+ 1234
894
910
  567
895
911
  ------ x
896
912
  8638
@@ -902,15 +918,23 @@ E.g. `print(istr.long_multiplication(1234,567, as_str=True)` will print
902
918
 
903
919
  #### long division
904
920
 
905
- The method `long_division` gives a list of all lines to do a long division (note that the divisor and dividend count as separate lines).
921
+ The method `long_division` returns an instance of a class with attributes `lines`, `line_lengths` and `output`.
906
922
 
907
- E.g. `print(istr.long_division(1395, 45)` will print
923
+ `lines` is a list of the lines of a long division.
908
924
 
909
- [istr('31'), istr('45'), istr('1395'), istr('135'), istr('45'), istr('45'), istr('0')]
910
-
911
- The method has an optional parameter `as_str`, that can be used to get a nice representation of the long multiplication lines.
912
-
913
- E.g. `print(istr.long_division(1395, 45, as_str=True)` will print
925
+ E.g. `print(istr.long_division(1395, 45).lines` will print
926
+
927
+ `[istr('31'), istr('45'), istr('1395'), istr('135'), istr('45'), istr('45'), istr('0')]`
928
+
929
+ Note the final `istr('0')` is included to support also long divisions roots that are not perfect.
930
+
931
+ E.g. `print(istr.long_division(1395, 45).line_lengths` will print
932
+
933
+ `[2, 2, 4, 3, 2, 2, 1]`
934
+
935
+ `output` can be used to get a nice string representation of the long divsion lines.
936
+
937
+ E.g. `print(istr.long_division(1395, 45).output` will print
914
938
 
915
939
  ```
916
940
  31
@@ -922,8 +946,8 @@ E.g. `print(istr.long_division(1395, 45, as_str=True)` will print
922
946
  45
923
947
  --
924
948
  0
925
- ```
926
-
949
+ ```
950
+
927
951
  #### generate istr with digits
928
952
 
929
953
  The class method `digits` can be used to return an istr of digits according to a given specification.
@@ -1017,6 +1041,41 @@ Note that `str(istr(":="))` is ":=" and does not assign any value.
1017
1041
 
1018
1042
  Usually, composing and decomposing uses the globals namespace, but this can be overridden with the namespace parameter. See the test suite for details.
1019
1043
 
1044
+ #### Evaluating expressions with one letter variables
1045
+ The `istr.eval` method is essentially the same as `istr.compose`, but (safely) evaluates the result.
1046
+ E.g.
1047
+
1048
+ ```
1049
+ a = 5
1050
+ b = 2
1051
+ print(istr.eval('aab + ba')
1052
+ ```
1053
+ will print `577`
1054
+
1055
+ The operators +, -, * and / are supported.
1056
+ 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.
1057
+
1058
+ ```
1059
+ a = 5
1060
+ b = 2
1061
+ print(istr.eval('a > b')
1062
+ ```
1063
+ will print 1 (meaning True)
1064
+
1065
+ Note that leading zeroes are accepted. So, `istr.eval('03 + 7')` is `10`.
1066
+ Instead of ==, = can be used. So, `istr.eval('12 = 5 + 7')` is `1` (meaning True)
1067
+
1068
+ ##### Alternative method
1069
+
1070
+ 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:
1071
+ ```
1072
+ a = 5
1073
+ b = 2
1074
+ print(istr("?aab + ba':")
1075
+ print(istr("?a > b")
1076
+ ```
1077
+ with output `577` and `1` (meaning True)
1078
+
1020
1079
  #### Subclassing istr
1021
1080
 
1022
1081
  When a class is derived from istr, all methods will return that newly derived class.
@@ -10,7 +10,7 @@ authors = [
10
10
  { name = "Ruud van der Ham", email = "rt.van.der.ham@gmail.com" },
11
11
  ]
12
12
  description = "istr - strings you can count on"
13
- version = "1.1.42"
13
+ version = "1.1.44"
14
14
  readme = "README.md"
15
15
  requires-python = ">=3.10"
16
16
  dependencies = []
@@ -1227,7 +1227,7 @@ def test_compose():
1227
1227
 
1228
1228
 
1229
1229
  def test_long_sqrt():
1230
- assert istr.long_sqrt(1234**2) == [
1230
+ assert istr.long_sqrt(1234**2).lines == [
1231
1231
  istr("1234"),
1232
1232
  istr("1522756"),
1233
1233
  istr("1"),
@@ -1239,7 +1239,7 @@ def test_long_sqrt():
1239
1239
  istr("9856"),
1240
1240
  istr("0"),
1241
1241
  ]
1242
- assert istr.long_sqrt(1234**2 + 1) == [
1242
+ assert istr.long_sqrt(1234**2 + 1).lines == [
1243
1243
  istr("1234"),
1244
1244
  istr("1522757"),
1245
1245
  istr("1"),
@@ -1251,8 +1251,8 @@ def test_long_sqrt():
1251
1251
  istr("9856"),
1252
1252
  istr("1"),
1253
1253
  ]
1254
- lines = istr.long_sqrt(1234**2 + 1)
1255
- assert istr(1234**2).long_sqrt() == [
1254
+
1255
+ assert istr(1234**2).long_sqrt().lines == [
1256
1256
  istr("1234"),
1257
1257
  istr("1522756"),
1258
1258
  istr("1"),
@@ -1264,11 +1264,12 @@ def test_long_sqrt():
1264
1264
  istr("9856"),
1265
1265
  istr("0"),
1266
1266
  ]
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
-
1267
+ assert istr.long_sqrt(34**2).lines == [istr("34"), istr("1156"), istr("9"), istr("256"), istr("256"), istr("0")]
1268
+ assert istr.long_sqrt("001156").lines == [istr("34"), istr("1156"), istr("9"), istr("256"), istr("256"), istr("0")]
1269
+ assert istr.long_sqrt(34**2).line_lengths == [2, 4, 1, 3, 3, 1]
1270
+ assert istr.long_sqrt("001156").line_lengths == [2, 4, 1, 3, 3, 1]
1270
1271
  assert (
1271
- istr.long_sqrt(34**2, as_str=True)
1272
+ istr.long_sqrt(34**2).output
1272
1273
  == """\
1273
1274
  3 4
1274
1275
  ----
@@ -1283,12 +1284,12 @@ def test_long_sqrt():
1283
1284
 
1284
1285
 
1285
1286
  def test_long_multiplication():
1286
- assert istr.long_multiplication(1234, 567) == [istr("1234"), istr("567"), istr("8638"), istr("7404"), istr("6170"), istr("699678")]
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")]
1287
+ assert istr.long_multiplication(1234, 567).lines == [istr("1234"), istr("567"), istr("8638"), istr("7404"), istr("6170"), istr("699678")]
1288
+ assert istr(1234).long_multiplication(567).lines == [istr("1234"), istr("567"), istr("8638"), istr("7404"), istr("6170"), istr("699678")]
1289
+ assert istr("01234").long_multiplication("0567").lines == [istr("1234"), istr("567"), istr("8638"), istr("7404"), istr("6170"), istr("699678")]
1289
1290
 
1290
1291
  assert (
1291
- istr.long_multiplication(1234, 567, as_str=True)
1292
+ istr.long_multiplication(1234, 567).output
1292
1293
  == """\
1293
1294
  1234
1294
1295
  567
@@ -1302,12 +1303,12 @@ def test_long_multiplication():
1302
1303
 
1303
1304
 
1304
1305
  def test_long_division():
1305
- assert istr.long_division(1395, 45) == [istr("31"), istr("45"), istr("1395"), istr("135"), istr("45"), istr("45"), istr("0")]
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
-
1306
+ assert istr.long_division(1395, 45).lines == [istr("31"), istr("45"), istr("1395"), istr("135"), istr("45"), istr("45"), istr("0")]
1307
+ assert istr(1395).long_division(45).lines == [istr("31"), istr("45"), istr("1395"), istr("135"), istr("45"), istr("45"), istr("0")]
1308
+ assert istr.long_division(istr("01395"), istr("045")).lines == [istr("31"), istr("45"), istr("1395"), istr("135"), istr("45"), istr("45"), istr("0")]
1309
+ assert istr.long_division(1395, 45).line_lengths == [2, 2, 4, 3, 2, 2, 1]
1309
1310
  assert (
1310
- istr.long_division(1395, 45, as_str=True)
1311
+ istr.long_division(1395, 45).output
1311
1312
  == """\
1312
1313
  31
1313
1314
  ----
File without changes