istr-python 0.2.0.post0__tar.gz → 1.0.0__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.
@@ -0,0 +1,644 @@
1
+ Metadata-Version: 2.1
2
+ Name: istr-python
3
+ Version: 1.0.0
4
+ Summary: istr - strings you can count on
5
+ Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
6
+ Project-URL: Homepage, https://github.com/salabim/istr
7
+ Project-URL: Repository, https://github.com/salabim/istr
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Requires-Python: >=3.7
12
+ Description-Content-Type: text/markdown
13
+
14
+ <img src="https://www.salabim.org/istr_logo.png" width=500>
15
+
16
+ ### Introduction
17
+
18
+ The istr module has exactly one class: istr.
19
+
20
+ With this it is possible to interpret strings as if they were integers.
21
+
22
+ This can be very handy for solving puzzles, but also for other purposes.
23
+ For instance the famous send more money puzzle, where each letter has to be replaced by a unique digit (0-9)
24
+
25
+ ```
26
+ S E N D
27
+ M O R E
28
+ --------- +
29
+ M O N E Y
30
+ ```
31
+ can be nicely, albeit not very efficient, coded as:
32
+ ```
33
+ import itertools
34
+ from istr import istr
35
+
36
+ for s, e, n, d, m, o, r, y in istr(itertools.permutations(range(10), 8)):
37
+ if m and ((s|e|n|d) + (m|o|r|e) == (m|o|n|e|y)):
38
+ print(f' {s|e|n|d}')
39
+ print(f' {m|o|r|e}')
40
+ print('-----')
41
+ print(f'{m|o|n|e|y}')
42
+ ```
43
+
44
+ Of, if we want to add all the digits in a string:
45
+
46
+ ```
47
+ sum_digits = sum(istr('9282334')) # answer 31
48
+ ```
49
+
50
+ And the module is a demonstration of extending a class (str) with extra and changed functionality.
51
+
52
+ ### Installation
53
+ Installing istr with pip is easy.
54
+ ```
55
+ $ pip install istr-python
56
+ ```
57
+ or when you want to upgrade,
58
+ ```
59
+ $ pip install istr-python --upgrade
60
+ ```
61
+ Alternatively, istr.py can be just copied into you current work directory from GitHub (https://github.com/salabim/istr).
62
+
63
+ No dependencies!
64
+
65
+ ### Usage
66
+ #### Start
67
+
68
+ Just start with
69
+
70
+ ```
71
+ from istr import istr
72
+ ```
73
+
74
+ #### Use istrs as if the were int
75
+
76
+ We can define an istr:
77
+ ```
78
+ four = istr('4')
79
+ five = istr('5')
80
+ ```
81
+ The variables `four` and `five` can now be used as if they were int:
82
+
83
+ ```
84
+ twenty = four * five
85
+ ```
86
+ , after which x is `istr('20')`
87
+
88
+ The same can be done with
89
+
90
+ ```
91
+ twenty = 4 * five
92
+ ```
93
+
94
+ or
95
+
96
+ ```
97
+ twenty = four * 5
98
+ ```
99
+
100
+ And now `twenty` can be used as if it was an int as well. So:
101
+
102
+ ```
103
+ twenty - four
104
+ ```
105
+
106
+ is `istr('16')`
107
+
108
+ We can do all the usual arithmetic operations on istrs, e.g.
109
+
110
+ ```
111
+ -four + (twenty / 2)
112
+ ```
113
+
114
+ is `istr('6')`
115
+
116
+ And we can test for equality, So:
117
+
118
+ ```
119
+ twenty == 20
120
+ ```
121
+ is True.
122
+
123
+ But as istrs are also strings. So
124
+
125
+ ```
126
+ twenty == '20'
127
+ ```
128
+
129
+ is also True!
130
+
131
+ For the order comparisons (<=, <, >, >=), the istr is always interpreted as an int:
132
+
133
+ That means that both
134
+ ```
135
+ twenty < 30
136
+ twenty >= '10' # here '10' is converted to 10 for the comparison
137
+ ```
138
+ are `True`.
139
+
140
+ In contrast to an ordinary string
141
+ ```
142
+ print(four + five)
143
+ ```
144
+ prints `9`, as istr are treated as ints.
145
+
146
+ Please note that `four` could have also been initialized with
147
+ ```
148
+ four = istr(4)
149
+ ```
150
+ or even
151
+ ```
152
+ four, five = istr(4, 5)
153
+ ```
154
+
155
+ > [!NOTE]
156
+ >
157
+ > All calculations are strictly integer calculations. That means that if a float variale is ever produced it will be converted to an int.
158
+ > Also divisions are always floor divisions!
159
+
160
+ #### Use istrs as string
161
+
162
+ We should realize that istrs are in fact strings.
163
+
164
+ In order to concatenate two istrs (or an istr and a str), we cannot use the `four + five` operator as
165
+
166
+ that would be `istr(9)`.
167
+
168
+ In order to concatenate istrs, we use the or operator (`|`). So
169
+
170
+ ```
171
+ four | five
172
+ ```
173
+ will be `istr(`45`).
174
+
175
+ That means that
176
+ ```
177
+ (four | five) / 3
178
+ ```
179
+ is `istr('9')`.
180
+
181
+ In order to repeat a string in the usual sense, you cannot use `3 * four`, as that would be `12`.
182
+
183
+ In order to repeat we use the matrix multiplication operator (`@`). So
184
+
185
+ `3 @ four`
186
+
187
+ is istr(`444`). As is `four @ 3`.
188
+
189
+ > [!NOTE]
190
+ >
191
+ > It is not allowed to use the `@` operator for two istrs. So, `four @ five` raises a TypeError.
192
+
193
+ #### istrs that can't be interpreted as an int
194
+
195
+ Although usualy, istrs are to be interpreted as an int, that's not a requirement.
196
+
197
+ So
198
+
199
+ ```
200
+ istr('abc')
201
+ ```
202
+
203
+ or
204
+
205
+ ```
206
+ istr(1,2,3)
207
+ ```
208
+
209
+ are accepted.
210
+
211
+ But, we can't do any arithmetic with them.
212
+
213
+ If we try
214
+
215
+ ```
216
+ istr('abc') + 5
217
+ ```
218
+
219
+ a TypeError will be raised.
220
+
221
+ That holds for any arithmetic we try.
222
+
223
+ If we want to test if an istr can be interpreted (and thus used in an arithmetic expression). we can use the `is_int()` method. So
224
+
225
+ ```ìstr(20).is_int()```
226
+
227
+ is `True`, wherea
228
+
229
+ ```ìstr('abc').is_int()```
230
+
231
+ is `False`.
232
+
233
+
234
+
235
+ The bool operator works normally on the integer value of an istr. So
236
+
237
+ `bool(istr('0'))` ==> `False`
238
+ `bool(istr('1'))` ==> `True`
239
+
240
+ If the istr can't be interpreted as an int, the string will be used to test. So
241
+
242
+ `bool(istr('abc'))` ==> `False`
243
+ `bool(istr(''))` ==> `True`
244
+
245
+ #### Other operators
246
+
247
+ For the `in` operator, an istr is treated as an ordinary string, although it is possible to use ints as well:
248
+
249
+ ```
250
+ '34' in istr(1234)
251
+ 34 in istr(1234)
252
+ ```
253
+ On the left hand side an istr is always treated as a string:
254
+ ```
255
+ istr(1234) in '01234566890ABCDEF'
256
+ ```
257
+
258
+ Sorting a list of istrs is based on the integer value, not the string. So
259
+
260
+ ```
261
+ ' '.join(sorted('1 3 2 4 5 6 11 7 9 8 10 12 0'.split()))
262
+ ```
263
+
264
+ is
265
+
266
+ ```
267
+ '0 1 10 11 2 3 4 5 6 7 8 9'
268
+ ```
269
+
270
+ ,whereas
271
+
272
+ ```
273
+ ' '.join(sorted(istr('1 3 2 4 5 6 11 7 9 8 10 12 0'.split()))
274
+ ```
275
+
276
+ is
277
+
278
+ ```
279
+ '0 1 2 3 4 5 6 7 8 9 10 11'
280
+ ```
281
+
282
+ #### Using other values for istr than numeric value or str
283
+ Apart from with simple numeric (to be interpreted as an int) or str, istr can be initialized with
284
+ several other types:
285
+
286
+ - if a dict (or subtype of dict), the same type dict will be returned with all *values* istr'ed
287
+
288
+ ```
289
+ istr({0: 0, 1: 1, 2: 4}) ==> {0: istr('0'), 1: istr('1'), 2: istr('4')}
290
+ ```
291
+
292
+ - if an iterator, the iterator will be mapped with istr
293
+
294
+ ```
295
+ istr(i * i for i in range(3)) ==> <map object>
296
+ list(istr(i * i for i in range(3))) ==> [istr('0'), istr('1'), istr('4')]
297
+ ```
298
+
299
+ - if an iterable, the same type will be returned with all elements istr'ed
300
+
301
+ ```
302
+ istr([0, 1, 4]) ==> [istr('0'), istr('1'), istr('4')]
303
+ istr((0, 1, 4)) ==> (istr('0'), istr('1'), istr('4'))
304
+ istr({0, 1, 4}) ==> `{istr('4'), istr('0'), istr('1')} # or similar
305
+ ```
306
+
307
+ - if a range, an istr.range instance will be returned
308
+
309
+ ```
310
+ istr(range(3))` ==> `istr.range(3)
311
+ list(istr(range(3)))` ==> `[istr('0'), istr('1'), istr('2')]
312
+ len(istr(range(3)))` ==> `3
313
+ ```
314
+
315
+ - if an istr.range instance, the same istr.range will be returned
316
+
317
+ - if an istr, the same istr will be used
318
+
319
+ ```
320
+ istr(istr('4')) ==> istr ('4')
321
+ ```
322
+
323
+ #### More than one parameter for istr
324
+ It is possible to give more than one parameter, in which case a tuple
325
+ of the istrs of the parameters will be returned, which can be handy
326
+ to unpack multiple values, e.g.
327
+
328
+ ```
329
+ a, b, c = istr(5, 6, 7) ==> a=istr('5') , b=istr('6'), c=istr('7')
330
+ ```
331
+
332
+ #### test for even/odd
333
+ It is possible to test for even/odd (provided the istt can be interpreted as an int) with the
334
+
335
+ `is_even` and `is_odd` method, e.g.
336
+
337
+ ```
338
+ istr(4).is_even()) ==> True
339
+ istr(5).is_odd()) ==> True
340
+ ```
341
+ #### reverse an istr
342
+ The method `istr.reversed()` will return the an istr with the reversed content:
343
+ ```
344
+ istr(456).reversed() ==> istr('654')
345
+ istr('0456').reversed() ==> istr('6540')
346
+ ```
347
+ The same can -of course- be achieved with
348
+ ```
349
+ istr(456)[::-1] ==> istr('654')
350
+ istr('0456')[::-1] ==> istr('6540')
351
+ ```
352
+ > [!NOTE]
353
+ >
354
+ > It is possible to reverse a negative istr, but the result can't be interpreted as an int anymore.
355
+ >
356
+ > ```
357
+ > istr(-456) ==> TypeError
358
+ > ```
359
+
360
+ #### enumerate with istrs
361
+
362
+ The `istr.enumerate` method can be used just as the builtin enumerate function.
363
+ The iteration counter however is an istr rather than an int. E.g.
364
+
365
+ ```
366
+ for i,c in istr.enumerate('abc'):
367
+ print(f'{repr(i)} {c}')
368
+ ```
369
+ prints
370
+ ```
371
+ istr('0') a
372
+ istr('1') b
373
+ istr('2') c
374
+ ```
375
+
376
+ #### concatenate an iterable
377
+
378
+ The `istr.concat` method can be useful to map all items of an iterable
379
+ to `istr` and then concatenate these.
380
+
381
+ `
382
+
383
+ ```
384
+ list(istr.concat(((1,2),(3,4))) ==> istr([12,34])
385
+ list(istr.concat(itertools.permutations(range(3),2))) ==>
386
+ [istr('01'), istr('02'), istr('10'), istr('12'), istr('20'), istr('21')]
387
+ ```
388
+
389
+ #### generate istr with digits
390
+
391
+ The class method `digits` can be used to return an istr of digits according to a given specification.
392
+ The method takes either no or a number of arguments.
393
+
394
+ If no arguments are given, the result will be istr('0123456789').
395
+
396
+ The given argument(s) result in a range of digits.
397
+
398
+ - `<n>` ==> n
399
+ - `<n-m>` ==> n, n+1, ..., m
400
+ - `-n>` ==> 0, 1, ... n
401
+ - `n->` ==> n, n+1, ..., 9
402
+ - `''` ==> 0, 1, ..., 9
403
+
404
+ (n and m must be digits between 0 and 9)
405
+
406
+ The final result is an istr composed of the given range(s).
407
+
408
+ Here are some examples:
409
+
410
+ ```
411
+ istr.digits() ==> istr('0123456789')
412
+ istr.digits('') ==> istr('0123456789')
413
+ istr.digits('1') ==> istr('1')
414
+ istr.digits('3-') ==> istr('3456789')
415
+ istr.digits('-3') ==> istr('0123')
416
+ istr('1-4', '6', '8-9') ==> istr('1234689')
417
+ istr('1', '1-2', '1-3') ==> istr('11213')
418
+ ```
419
+
420
+
421
+ #### Subclassing istr
422
+ When a class is derived from istr, all methods will return that newly derived class.
423
+
424
+ E.g.
425
+ ```
426
+ class jstr(istr):
427
+ ...
428
+
429
+ print(repr(jstr(4) * jstr(5)))
430
+ ```
431
+ will print `jstr('20')`
432
+
433
+ #### Changing the way repr works
434
+
435
+ It is possible to control the way an `istr` instance will be repr'ed.
436
+
437
+ By default, the `istr('5')` is represented as `istr('5')`.
438
+
439
+ With the istr.repr_mode() context manager, that can be changed:
440
+ ```
441
+ with istr.repr_mode('str'):
442
+ five = istr('5')
443
+ print(repr(five))
444
+ with istr.repr_mode('int'):
445
+ five = istr('5')
446
+ print(repr(five))
447
+ with istr.repr_mode('istr'):
448
+ five = istr('5')
449
+ print(repr(five))
450
+ ```
451
+ This will print
452
+ ```
453
+ '5'
454
+ 5
455
+ istr('5')
456
+ ```
457
+ If the repr_mode is `'int'` and the istr can't be interpreted as an int the string `nan` (not a number) will be returned:
458
+
459
+ ```
460
+ with istr.repr_mode('int'):
461
+ abc = istr('abc')
462
+ print(repr(abc))
463
+ ```
464
+
465
+ This will print
466
+
467
+ ```
468
+ nan
469
+ ```
470
+
471
+ > [!NOTE]
472
+ >
473
+ > The way an `istr` is represented is determined at initialization.
474
+
475
+ It is also possible to set the repr mode without a context manager:
476
+
477
+ ```
478
+ istr.repr_mode('str')
479
+ five = istr('5')
480
+ print(repr(five))
481
+ ```
482
+ This will print
483
+ ```
484
+ '5'
485
+ ```
486
+ Finally, the current repr mode can be queried with `istr.repr_mode()`. So upon start:
487
+ ```
488
+ print(repr(istr.repr_mode()))
489
+ ```
490
+ will output `istr`.
491
+
492
+ #### Changing the base system
493
+
494
+ By default, `istr` works in base 10. However it is possible to change the base system with the `istr.base()` context manager / method.
495
+
496
+ Any base between 2 and 36 may be used.
497
+
498
+ Note that the integer is **always** stored in base 10 mode, but the string
499
+ representation will reflect the chosen base (at time of initialization).
500
+
501
+ Some examples:
502
+ ```
503
+ with istr.base(16):
504
+ a = istr('7fff')
505
+ print(int(a))
506
+
507
+ b = istr(15)
508
+ print(repr(b))
509
+ ```
510
+ This will result in
511
+ ```
512
+ 32767
513
+ istr('F')
514
+ ```
515
+ All calculations are done in the decimal 10 system.
516
+
517
+ Note that the way an `istr` is interpreted is determined at initialization.
518
+
519
+ It is also possible to set the repr mode without a context manager:
520
+ ```
521
+ istr.base(16)
522
+ print(int(istr('7fff')))
523
+ ```
524
+ This will print
525
+ ```
526
+ 32767
527
+ ```
528
+ Finally, the current base can be queried with `istr.base()`, so upon start:
529
+ ```
530
+ print(istr.base())
531
+ ```
532
+ will result in `10`.
533
+
534
+ #### Changing the format of the string
535
+
536
+ When an istr is initialized with a string the istr will be just stored as such.
537
+
538
+ ```
539
+ repr('4')) ==> istr('4')
540
+ repr(' 4')) ==> istr(' 4')
541
+ repr('4 ')) ==> istr('4 ')
542
+ ```
543
+
544
+ For initializing with an int (or other numeric) value, the string is by default simply the str representation
545
+
546
+ ```
547
+ repr(4)) ==> istr('4')
548
+ ```
549
+
550
+ With the `istr.int_format()` context manager this behavior can be changed.
551
+ If the format specifier is a number, most likely a single digit, that
552
+ will be the minimum number of characters in the string:
553
+
554
+ ```
555
+ with istr.int_format('3'):
556
+ print(repr(istr(1)))
557
+ print(repr(istr(12)))
558
+ print(repr(istr(123)))
559
+ print(repr(istr(1234)))
560
+ ```
561
+ will print
562
+ ```
563
+ istr(' 1')
564
+ istr(' 12')
565
+ istr('123')
566
+ istr('1234')
567
+ ```
568
+ If the string starts with a `0`, the string will be zero filled:
569
+ ```
570
+ with istr.int_format('03'):
571
+ print(repr(istr(1)))
572
+ print(repr(istr(12)))
573
+ print(repr(istr(123)))
574
+ print(repr(istr(1234)))
575
+ ```
576
+ will print
577
+ ```
578
+ istr('001')
579
+ istr('012')
580
+ istr('123')
581
+ istr('1234')
582
+ ```
583
+ > [!NOTE]
584
+ >
585
+ > if a string is used to initialize an istr AND that string can be interpreted as an int. the string will reformatted:
586
+ >
587
+ > ```
588
+ > with istr.int_format('03'):
589
+ > print(repr(istr(' 12 ')))
590
+ > ```
591
+ >
592
+ > will result in
593
+ >
594
+ > ```
595
+ > istr('0012')
596
+ > ```
597
+
598
+ > [!NOTE]
599
+ >
600
+ > For bases other than 10, the string will never be reformatted!
601
+
602
+ ### Overview of operations
603
+
604
+ The table below shows whether the string or the int version of istr is applied.
605
+
606
+ ```
607
+ operator/function int str Example
608
+ -----------------------------------------------------------------------------------------
609
+ + x istr(20) + 3 ==> istr('23')
610
+ _ x istr(20) - 3 ==> istr('17')
611
+ * x istr(20) * 3 ==> istr('60')
612
+ / x istr(20) / 3 ==> istr('6')
613
+ // x istr(20) // 3 ==> istr('6')
614
+ % x istr(20) % 3 ==> istr('2')
615
+ divmod x divmod(istr(20), 3) ==> (istr('6'), istr('2'))
616
+ ** x istr(2) ** 3 ==> istr('8')
617
+ @ x istr(20) @ 3 ==> istr('202020')
618
+ == x x istr(20) == 20 ==> True | istr(20) == '20' ==> True
619
+ | x istr(20) | 5 ==> istr('205')
620
+ abs x abs(istr(-20)) ==> istr('20')
621
+ bool x x *) bool(istr(' 0 ')) ==> False | istr('') ==> False
622
+ <=, <, >, >= x istr('100') > istr('2') ==> True
623
+ slicing x istr(12345)[1:3] ==> istr('23')
624
+ iterate x [x for x in istr(20)] ==> [istr('2'), istr('0')
625
+ len x len(istr(' 20 ')) ==> 4
626
+ count x istr(100),count('0') ==> 2
627
+ index x istr(' 100 ').index('0') ==> 2
628
+ split x istr('1 2').split() ==> (istr('1'), istr('2'))
629
+ other string methods x istr('aAbBcC').lower() ==> istr('aabbcc')
630
+ istr('aAbBcC').islower() ==> False
631
+ istr(' abc ').strip() ==> istr('abc')
632
+ -----------------------------------------------------------------------------------------
633
+ *) str is applied if is_int() is False
634
+ ```
635
+ ### Test script
636
+ There's an extensive pytest script in the `\tests` directory.
637
+
638
+ This script also shows clearly the ways istr can be used, including several edge cases. Highly recommended to have a look at.
639
+
640
+ ### Badges
641
+ ![PyPI](https://img.shields.io/pypi/v/istr-python) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/istr-python) ![PyPI - Implementation](https://img.shields.io/pypi/implementation/istr-python)
642
+
643
+ ![PyPI - License](https://img.shields.io/pypi/l/istr-python) ![Black](https://img.shields.io/badge/code%20style-black-000000.svg)
644
+ ![GitHub last commit](https://img.shields.io/github/last-commit/salabim/istr)