tablinum 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tablinum/__about__.py +4 -0
- tablinum/__init__.py +4 -0
- tablinum/tab_fun_dates.py +301 -0
- tablinum/tab_fun_maths.py +266 -0
- tablinum/tab_fun_useful.py +101 -0
- tablinum/tablinum.py +1814 -0
- tablinum-0.1.0.dist-info/METADATA +1618 -0
- tablinum-0.1.0.dist-info/RECORD +11 -0
- tablinum-0.1.0.dist-info/WHEEL +4 -0
- tablinum-0.1.0.dist-info/entry_points.txt +2 -0
- tablinum-0.1.0.dist-info/licenses/LICENSE +21 -0
tablinum/__about__.py
ADDED
tablinum/__init__.py
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
'''Date functions to use in tabulate `arr` expressions
|
|
3
|
+
|
|
4
|
+
Toby Thurston -- 10 Feb 2021
|
|
5
|
+
|
|
6
|
+
"dow": tab_fun_dates.dow,
|
|
7
|
+
"base": tab_fun_dates.base,
|
|
8
|
+
"date": tab_fun_dates.date,
|
|
9
|
+
|
|
10
|
+
"hms": tab_fun_dates.hms,
|
|
11
|
+
"hr": tab_fun_dates.hr,
|
|
12
|
+
"mins": tab_fun_dates.mins,
|
|
13
|
+
"secs": tab_fun_dates.secs,
|
|
14
|
+
|
|
15
|
+
'''
|
|
16
|
+
import datetime
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def parse_date(sss, today=None):
|
|
20
|
+
'''Try to parse a date
|
|
21
|
+
>>> parse_date(730486).isoformat()
|
|
22
|
+
'2001-01-01'
|
|
23
|
+
>>> parse_date("1 January 2001").isoformat()
|
|
24
|
+
'2001-01-01'
|
|
25
|
+
>>> parse_date(base("1 January 2001")).isoformat()
|
|
26
|
+
'2001-01-01'
|
|
27
|
+
>>> parse_date("6/7/95").isoformat()
|
|
28
|
+
'1995-07-06'
|
|
29
|
+
>>> parse_date("2022-W47-2").isoformat()
|
|
30
|
+
'2022-11-22'
|
|
31
|
+
>>> parse_date("Fri 1st Apr 2022").isoformat()
|
|
32
|
+
'2022-04-01'
|
|
33
|
+
>>> parse_date("Sat 29th July 2023").isoformat()
|
|
34
|
+
'2023-07-29'
|
|
35
|
+
>>> parse_date("Fri 29th Sept 2023").isoformat()
|
|
36
|
+
'2023-09-29'
|
|
37
|
+
>>> parse_date("Sat 19th Mar 2022").isoformat()
|
|
38
|
+
'2022-03-19'
|
|
39
|
+
>>> parse_date("Sunday", today='2022-03-17').isoformat()
|
|
40
|
+
'2022-03-20'
|
|
41
|
+
'''
|
|
42
|
+
try:
|
|
43
|
+
if 0 < int(sss) < 900000:
|
|
44
|
+
return datetime.date.fromordinal(sss)
|
|
45
|
+
except (TypeError, ValueError) as _:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
days = "Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split()
|
|
49
|
+
try:
|
|
50
|
+
iso_dow = 1 + days.index(str(sss).capitalize())
|
|
51
|
+
except ValueError:
|
|
52
|
+
pass
|
|
53
|
+
else:
|
|
54
|
+
if 1 <= iso_dow <= 7:
|
|
55
|
+
if today is None:
|
|
56
|
+
today = datetime.datetime.today()
|
|
57
|
+
else:
|
|
58
|
+
today = datetime.datetime.strptime(today, '%Y-%m-%d')
|
|
59
|
+
year, week = today.strftime("%G-%V").split("-")
|
|
60
|
+
return datetime.datetime.strptime(f'{year}-W{week}-{iso_dow}', "%G-W%V-%u").date()
|
|
61
|
+
|
|
62
|
+
sss = str(sss).replace('Sept ', 'Sep ')
|
|
63
|
+
|
|
64
|
+
for fmt in ('%Y-%m-%d', '%Y%m%d', '%d %B %Y', '%d %b %Y', '%G-W%V-%u', '%d-%b-%Y',
|
|
65
|
+
'%d %b %y', '%d %B %y', '%d/%m/%Y', '%d/%m/%y', '%B %d, %Y',
|
|
66
|
+
'%A %d %B %Y',
|
|
67
|
+
'%a %dth %b %Y', '%a %dst %b %Y', '%a %dnd %b %Y', '%a %drd %b %Y',
|
|
68
|
+
'%a %dth %B %Y', '%a %dst %B %Y', '%a %dnd %B %Y', '%a %drd %B %Y',
|
|
69
|
+
'%m/%d/%Y',
|
|
70
|
+
'%c', '%x'):
|
|
71
|
+
try:
|
|
72
|
+
return datetime.datetime.strptime(str(sss), fmt).date()
|
|
73
|
+
except ValueError:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
raise ValueError
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def dow(sss, date_format="%a"):
|
|
80
|
+
'''Is it Friday yet?
|
|
81
|
+
>>> dow("1 January 2001")
|
|
82
|
+
'Mon'
|
|
83
|
+
>>> dow(base("1 January 2001"))
|
|
84
|
+
'Mon'
|
|
85
|
+
>>> dow("31/12/2021")
|
|
86
|
+
'Fri'
|
|
87
|
+
>>> dow("1 January 2001", "%A")
|
|
88
|
+
'Monday'
|
|
89
|
+
>>> dow("date")
|
|
90
|
+
'%a'
|
|
91
|
+
|
|
92
|
+
You can actually use this to produce any strftime format...
|
|
93
|
+
|
|
94
|
+
>>> dow("25 Dec 2001", "%c")
|
|
95
|
+
'Tue Dec 25 00:00:00 2001'
|
|
96
|
+
|
|
97
|
+
>>> dow("25 Dec 2001", "%u")
|
|
98
|
+
'2'
|
|
99
|
+
|
|
100
|
+
'''
|
|
101
|
+
try:
|
|
102
|
+
return parse_date(sss).strftime(date_format)
|
|
103
|
+
except (TypeError, ValueError):
|
|
104
|
+
return date_format
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def base(sss=None):
|
|
108
|
+
'''Get today's date as "base" number, or whatever date you give
|
|
109
|
+
>>> base("1 January 2001")
|
|
110
|
+
730486
|
|
111
|
+
>>> base("1 January 1901")
|
|
112
|
+
693961
|
|
113
|
+
>>> base("01/01/01")
|
|
114
|
+
730486
|
|
115
|
+
>>> base("20010102")
|
|
116
|
+
730487
|
|
117
|
+
>>> base(20010102)
|
|
118
|
+
730487
|
|
119
|
+
>>> base("2001-01-03")
|
|
120
|
+
730488
|
|
121
|
+
>>> base("03-jan-2001")
|
|
122
|
+
730488
|
|
123
|
+
>>> base("31 Dec 2000")-base("1 Jan 1901")
|
|
124
|
+
36524
|
|
125
|
+
>>> base() - datetime.date.today().toordinal()
|
|
126
|
+
0
|
|
127
|
+
>>> datetime.date.today().toordinal() - base(-4)
|
|
128
|
+
4
|
|
129
|
+
>>> base('Date')
|
|
130
|
+
'base(Date)'
|
|
131
|
+
'''
|
|
132
|
+
if sss is None:
|
|
133
|
+
return datetime.date.today().toordinal()
|
|
134
|
+
|
|
135
|
+
if isinstance(sss, int) and abs(sss) < 1000:
|
|
136
|
+
return datetime.date.today().toordinal() + sss
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
return parse_date(sss).toordinal()
|
|
140
|
+
except (TypeError, ValueError):
|
|
141
|
+
return f'base({sss})'
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def date(ordinal=0):
|
|
145
|
+
'''Turn a base number (or an epoch time or a millisecond epoch time) into a date
|
|
146
|
+
>>> date(716257)
|
|
147
|
+
'1962-01-17'
|
|
148
|
+
>>> date(3652059)
|
|
149
|
+
'9999-12-31'
|
|
150
|
+
>>> date(3652060)
|
|
151
|
+
'1970-02-12T07:27:40'
|
|
152
|
+
>>> date(100000000000)
|
|
153
|
+
'5138-11-16T09:46:40'
|
|
154
|
+
>>> date(100000000001)
|
|
155
|
+
'1973-03-03T09:46:40.001000'
|
|
156
|
+
>>> date(10) == (datetime.date.today() + datetime.timedelta(days=10)).isoformat()
|
|
157
|
+
True
|
|
158
|
+
>>> date('ts')
|
|
159
|
+
'date(ts)'
|
|
160
|
+
>>> date(-10000000000000000000000000) == date(0)
|
|
161
|
+
True
|
|
162
|
+
>>> date(-2000) == date(0)
|
|
163
|
+
True
|
|
164
|
+
>>> date(base(730486))
|
|
165
|
+
'2001-01-01'
|
|
166
|
+
'''
|
|
167
|
+
try:
|
|
168
|
+
ordinal = int(ordinal)
|
|
169
|
+
except (TypeError, ValueError):
|
|
170
|
+
ordinal = base(ordinal)
|
|
171
|
+
if ordinal.startswith('base'):
|
|
172
|
+
return ordinal.replace('base', 'date')
|
|
173
|
+
|
|
174
|
+
if abs(ordinal) < 1000:
|
|
175
|
+
dt = datetime.date.today() + datetime.timedelta(days=ordinal)
|
|
176
|
+
elif ordinal > 100000000000: # about 5000 AD as an epoch, so assume epoch ms
|
|
177
|
+
dt = datetime.datetime.fromtimestamp(ordinal / 1000)
|
|
178
|
+
elif ordinal > datetime.date.max.toordinal(): # > max date, so assume epoch seconds
|
|
179
|
+
dt = datetime.datetime.fromtimestamp(ordinal)
|
|
180
|
+
else:
|
|
181
|
+
try:
|
|
182
|
+
dt = datetime.date.fromordinal(ordinal)
|
|
183
|
+
except (TypeError, ValueError, OverflowError):
|
|
184
|
+
dt = datetime.date.today()
|
|
185
|
+
|
|
186
|
+
return dt.isoformat()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def hms(fractional_things):
|
|
190
|
+
'''Turn decimal hours/degrees into h/d:mm:ss.fff
|
|
191
|
+
|
|
192
|
+
>>> hms(57.2957795)
|
|
193
|
+
'57:17:44.806'
|
|
194
|
+
|
|
195
|
+
>>> hms(10801/3600)
|
|
196
|
+
'3:00:01.000'
|
|
197
|
+
'''
|
|
198
|
+
hh, r = divmod(fractional_things, 1)
|
|
199
|
+
mm, r = divmod(r * 60, 1)
|
|
200
|
+
return "{}:{:02d}:{:06.3f}".format(int(hh), int(mm), r * 60)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def hr(hms_word, s=0):
|
|
204
|
+
'''Turn hh:mm:ss.sss into fractional hours
|
|
205
|
+
|
|
206
|
+
>>> hr("12:34:56")
|
|
207
|
+
12.582222222222223
|
|
208
|
+
'''
|
|
209
|
+
hours = 0
|
|
210
|
+
for i, p in enumerate(hms_word.split(':')):
|
|
211
|
+
hours += float(p) * 60 ** (s - i)
|
|
212
|
+
return hours
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def mins(hms_word):
|
|
216
|
+
'''Turn hh:mm:ss.sss into fractional minutes
|
|
217
|
+
>>> mins('1:23:45')
|
|
218
|
+
83.75
|
|
219
|
+
'''
|
|
220
|
+
return hr(hms_word, 1)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def secs(hms_word):
|
|
224
|
+
'''Turn hh:mm:ss.sss into fractional seconds
|
|
225
|
+
>>> secs('1:23:45.67')
|
|
226
|
+
5025.67
|
|
227
|
+
'''
|
|
228
|
+
return hr(hms_word, 2)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def as_time(time_string):
|
|
232
|
+
'''Turn a possible time into hh:mm
|
|
233
|
+
|
|
234
|
+
>>> a = ['12 midnight', '12.00 a.m.', '12:01 am', '1:00a.m.', '9am', '11.00am', '11:59 a.m.']
|
|
235
|
+
>>> a += ['Noon', '12 noon', '12:00 pm', '12:01 p.m.', '1:00pm', '4 p.m.', '11:00p.m.', '11:59 pm', '12:00']
|
|
236
|
+
>>> ' '.join(as_time(x) for x in a)
|
|
237
|
+
'00:00 00:00 00:01 01:00 09:00 11:00 11:59 12:00 12:00 12:00 12:01 13:00 16:00 23:00 23:59 12:00'
|
|
238
|
+
'''
|
|
239
|
+
ts = ''.join(time_string.lower().split()).replace('.', '').replace(':', '')
|
|
240
|
+
if ts in ('noon', '12noon', '12pm', '1200pm'):
|
|
241
|
+
return '12:00'
|
|
242
|
+
if ts in ('midnight', '12midnight', '12am', '1200am'):
|
|
243
|
+
return '00:00'
|
|
244
|
+
if ts.endswith('am'):
|
|
245
|
+
if len(ts) <= 4:
|
|
246
|
+
hh = int(ts[:-2]) % 12
|
|
247
|
+
mm = 0
|
|
248
|
+
else:
|
|
249
|
+
hh = int(ts[:-4]) % 12
|
|
250
|
+
mm = int(ts[-4:-2])
|
|
251
|
+
return f'{hh:02d}:{mm:02d}'
|
|
252
|
+
if ts.endswith('pm'):
|
|
253
|
+
if len(ts) <= 4:
|
|
254
|
+
hh = int(ts[:-2]) % 12 + 12
|
|
255
|
+
mm = 0
|
|
256
|
+
else:
|
|
257
|
+
hh = int(ts[:-4]) % 12 + 12
|
|
258
|
+
mm = int(ts[-4:-2])
|
|
259
|
+
return f'{hh:02d}:{mm:02d}'
|
|
260
|
+
|
|
261
|
+
return time_string
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def epoch(date_time_string):
|
|
265
|
+
'''Turn date time into epoch secs
|
|
266
|
+
|
|
267
|
+
>>> epoch("01/01/1970 00:00")
|
|
268
|
+
'0'
|
|
269
|
+
>>> epoch("2000-12-31 23:59:59")
|
|
270
|
+
'978307199'
|
|
271
|
+
>>> epoch("Thursday")
|
|
272
|
+
'Thursday'
|
|
273
|
+
'''
|
|
274
|
+
for fd in ('%Y-%m-%d', '%d/%m/%Y', '%d-%b-%Y', '%Y%m%d'):
|
|
275
|
+
for ft in ('%H:%M:%S', '%H:%M', '%H%M%S'):
|
|
276
|
+
try:
|
|
277
|
+
dt = datetime.datetime.strptime(date_time_string, f'{fd} {ft}').replace(tzinfo=datetime.timezone.utc)
|
|
278
|
+
except ValueError:
|
|
279
|
+
pass
|
|
280
|
+
else:
|
|
281
|
+
return f'{dt.timestamp():.0f}'
|
|
282
|
+
return date_time_string
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def UK_tax_year(sss):
|
|
286
|
+
'''Return the UK tax year
|
|
287
|
+
|
|
288
|
+
>>> UK_tax_year('1962-01-17')
|
|
289
|
+
'FY61/62'
|
|
290
|
+
>>> UK_tax_year('2023-09-17')
|
|
291
|
+
'FY23/24'
|
|
292
|
+
|
|
293
|
+
'''
|
|
294
|
+
b = base(sss)
|
|
295
|
+
year = int(date(b)[:4])
|
|
296
|
+
c = base(f'{year}-04-05')
|
|
297
|
+
if c > b:
|
|
298
|
+
year -= 1
|
|
299
|
+
|
|
300
|
+
return f'FY{year % 100}/{(year+1) % 100}'
|
|
301
|
+
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
'''Mathematical functions for tabulate's decimal numbers
|
|
2
|
+
'''
|
|
3
|
+
import decimal
|
|
4
|
+
import itertools
|
|
5
|
+
import math
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
# pylint: disable=C0103
|
|
9
|
+
|
|
10
|
+
decimal.getcontext().prec = 12
|
|
11
|
+
|
|
12
|
+
PIC = ''.join('''3.1415926535 8979323846 2643383279 5028841971
|
|
13
|
+
6939937510 5820974944 5923078164 0628620899 8628034825 3421170679'''.split())
|
|
14
|
+
PI = 0 + decimal.Decimal(PIC)
|
|
15
|
+
TAU = PI + PI
|
|
16
|
+
ONE = decimal.Decimal('1')
|
|
17
|
+
ZERO = decimal.Decimal('0')
|
|
18
|
+
one = decimal.Decimal('1.0000000000')
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def degrees(x):
|
|
22
|
+
'''return radians -> degrees
|
|
23
|
+
>>> degrees(1)
|
|
24
|
+
Decimal('57.2957795131')
|
|
25
|
+
>>> degrees(TAU)
|
|
26
|
+
Decimal('360')
|
|
27
|
+
>>> degrees(0)
|
|
28
|
+
Decimal('0')
|
|
29
|
+
'''
|
|
30
|
+
if not x:
|
|
31
|
+
return ZERO
|
|
32
|
+
return x / PI * 180
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def radians(x):
|
|
36
|
+
''' return degrees -> radians
|
|
37
|
+
>>> radians(90)
|
|
38
|
+
Decimal('1.57079632679')
|
|
39
|
+
>>> radians(0)
|
|
40
|
+
Decimal('0')
|
|
41
|
+
'''
|
|
42
|
+
if not x:
|
|
43
|
+
return ZERO
|
|
44
|
+
return x * PI / 180
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def sin(x):
|
|
48
|
+
'''sine
|
|
49
|
+
|
|
50
|
+
>>> sin(0)
|
|
51
|
+
Decimal('0')
|
|
52
|
+
>>> sin(PI/6)
|
|
53
|
+
Decimal('0.5')
|
|
54
|
+
>>> sin(PI/2)
|
|
55
|
+
Decimal('1')
|
|
56
|
+
'''
|
|
57
|
+
return decimal.Decimal(math.sin(x)).quantize(one).normalize()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def cos(x):
|
|
61
|
+
'''cosine
|
|
62
|
+
|
|
63
|
+
>>> cos(PI/2)
|
|
64
|
+
Decimal('-0')
|
|
65
|
+
>>> cos(PI/3)
|
|
66
|
+
Decimal('0.5')
|
|
67
|
+
>>> cos(0)
|
|
68
|
+
Decimal('1')
|
|
69
|
+
'''
|
|
70
|
+
return decimal.Decimal(math.cos(x)).quantize(one).normalize()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def tan(x):
|
|
74
|
+
'''tangent
|
|
75
|
+
>>> tan(PI/4)
|
|
76
|
+
Decimal('1')
|
|
77
|
+
'''
|
|
78
|
+
return decimal.Decimal(math.tan(x)).quantize(one).normalize()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def sind(x):
|
|
82
|
+
'''sin in degrees
|
|
83
|
+
|
|
84
|
+
>>> sind(0)
|
|
85
|
+
Decimal('0')
|
|
86
|
+
>>> sind(30)
|
|
87
|
+
Decimal('0.5')
|
|
88
|
+
>>> sind(90)
|
|
89
|
+
Decimal('1')
|
|
90
|
+
'''
|
|
91
|
+
return decimal.Decimal(math.sin(math.radians(x))).quantize(one).normalize()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def cosd(x):
|
|
95
|
+
'''cosine in degrees
|
|
96
|
+
|
|
97
|
+
>>> cosd(90)
|
|
98
|
+
Decimal('0')
|
|
99
|
+
>>> cosd(60)
|
|
100
|
+
Decimal('0.5')
|
|
101
|
+
>>> cosd(0)
|
|
102
|
+
Decimal('1')
|
|
103
|
+
'''
|
|
104
|
+
return decimal.Decimal(math.cos(math.radians(x))).quantize(one).normalize()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def tand(x):
|
|
108
|
+
'''tan in degrees
|
|
109
|
+
>>> tand(90)
|
|
110
|
+
Decimal('Infinity')
|
|
111
|
+
>>> tand(45)
|
|
112
|
+
Decimal('1')
|
|
113
|
+
>>> tand(0)
|
|
114
|
+
Decimal('0')
|
|
115
|
+
'''
|
|
116
|
+
if x % 360 in (90, 270):
|
|
117
|
+
return decimal.Decimal('Inf')
|
|
118
|
+
|
|
119
|
+
return decimal.Decimal(math.tan(math.radians(x))).quantize(one).normalize()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def pyth_add(a, b):
|
|
123
|
+
'''Pythagorean addition
|
|
124
|
+
|
|
125
|
+
>>> pyth_add(5, 12)
|
|
126
|
+
Decimal('13')
|
|
127
|
+
'''
|
|
128
|
+
return (ONE * a * a + b * b).sqrt()
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def angle(a, b):
|
|
132
|
+
'''Like MP
|
|
133
|
+
>>> angle(4,3)
|
|
134
|
+
Decimal('36.8698976462')
|
|
135
|
+
'''
|
|
136
|
+
return degrees(decimal.Decimal(math.atan2(b, a)).quantize(one).normalize())
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def dir(t):
|
|
140
|
+
'''from MP
|
|
141
|
+
>>> dir(0)
|
|
142
|
+
(Decimal('1'), Decimal('0'))
|
|
143
|
+
>>> dir(45)
|
|
144
|
+
(Decimal('0.7071067812'), Decimal('0.7071067812'))
|
|
145
|
+
'''
|
|
146
|
+
return (cosd(t), sind(t))
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def mexp(x):
|
|
150
|
+
'''from MP = exp(x/256)
|
|
151
|
+
>>> mexp(0)
|
|
152
|
+
Decimal('1')
|
|
153
|
+
>>> mexp(256)
|
|
154
|
+
Decimal('2.71828182846')
|
|
155
|
+
'''
|
|
156
|
+
return (decimal.Decimal(x)/256).exp()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def mlog(x):
|
|
160
|
+
''' from MP = 256 log(x)
|
|
161
|
+
>>> mlog(10)
|
|
162
|
+
Decimal('589.461783805')
|
|
163
|
+
'''
|
|
164
|
+
return 256 * decimal.Decimal(x).ln()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def decimal_to_hex(d):
|
|
168
|
+
'''decimal to hexadecimal...
|
|
169
|
+
|
|
170
|
+
>>> decimal_to_hex(decimal.Decimal('0'))
|
|
171
|
+
'0x0'
|
|
172
|
+
>>> decimal_to_hex(decimal.Decimal('3.14'))
|
|
173
|
+
'0x3.23d70a3d70'
|
|
174
|
+
>>> decimal_to_hex(decimal.Decimal('100'))
|
|
175
|
+
'0x64'
|
|
176
|
+
|
|
177
|
+
'''
|
|
178
|
+
|
|
179
|
+
digits = '0123456789abcdef'
|
|
180
|
+
i, d = divmod(d, 1)
|
|
181
|
+
a = hex(int(i))
|
|
182
|
+
if d > 0:
|
|
183
|
+
a += '.'
|
|
184
|
+
while d > 0 and len(a) < 2 + decimal.getcontext().prec:
|
|
185
|
+
d *= 16
|
|
186
|
+
i, d = divmod(d, 1)
|
|
187
|
+
a += digits[int(i)]
|
|
188
|
+
return a
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def decimal_to_oct(d):
|
|
192
|
+
'''decimal to octal...
|
|
193
|
+
|
|
194
|
+
>>> decimal_to_oct(decimal.Decimal('0'))
|
|
195
|
+
'0o0'
|
|
196
|
+
>>> decimal_to_oct(decimal.Decimal('3.14'))
|
|
197
|
+
'0o3.1075341217'
|
|
198
|
+
>>> decimal_to_oct(decimal.Decimal('100'))
|
|
199
|
+
'0o144'
|
|
200
|
+
|
|
201
|
+
'''
|
|
202
|
+
|
|
203
|
+
digits = '01234567'
|
|
204
|
+
i, d = divmod(d, 1)
|
|
205
|
+
a = oct(int(i))
|
|
206
|
+
if d > 0:
|
|
207
|
+
a += '.'
|
|
208
|
+
while d > 0 and len(a) < 2 + decimal.getcontext().prec:
|
|
209
|
+
d *= 8
|
|
210
|
+
i, d = divmod(d, 1)
|
|
211
|
+
a += digits[int(i)]
|
|
212
|
+
return a
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def si(amount):
|
|
216
|
+
"""If amount is a number, add largest possible SI suffix,
|
|
217
|
+
otherwise try to remove the suffix and return a value
|
|
218
|
+
>>> si('10M')
|
|
219
|
+
Decimal('10000000')
|
|
220
|
+
>>> si(12315350)
|
|
221
|
+
'12.315 M'
|
|
222
|
+
>>> si(10)
|
|
223
|
+
'10.000'
|
|
224
|
+
>>> si('.2 k')
|
|
225
|
+
Decimal('200.0')
|
|
226
|
+
>>> si('Heading')
|
|
227
|
+
'Heading'
|
|
228
|
+
|
|
229
|
+
"""
|
|
230
|
+
sips = ' kMGTPE'
|
|
231
|
+
m = re.match(rf'([-+]?(?:\d+\.\d*|\.\d+|0|[1-9]\d*))\s*([{sips}])\Z', str(amount))
|
|
232
|
+
if m is not None:
|
|
233
|
+
return decimal.Decimal(m.group(1)) * 10 ** (3 * sips.index(m.group(2)))
|
|
234
|
+
try:
|
|
235
|
+
n = decimal.Decimal(amount)
|
|
236
|
+
except decimal.InvalidOperation:
|
|
237
|
+
return amount
|
|
238
|
+
else:
|
|
239
|
+
e = min(int(n.log10() / 3), len(sips) - 1)
|
|
240
|
+
return '{:7.3f} {}'.format(n / (10 ** (3 * e)), sips[e]).strip()
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def ifactors(n):
|
|
244
|
+
"Iterate through the factors"
|
|
245
|
+
f = 2
|
|
246
|
+
f_cycle = itertools.cycle([4, 2, 4, 2, 4, 6, 2, 6])
|
|
247
|
+
increments = itertools.chain([1, 2, 2], f_cycle)
|
|
248
|
+
for incr in increments:
|
|
249
|
+
if f * f > n:
|
|
250
|
+
break
|
|
251
|
+
while n % f == 0:
|
|
252
|
+
yield f
|
|
253
|
+
n //= f
|
|
254
|
+
f += incr
|
|
255
|
+
if n > 1:
|
|
256
|
+
yield n
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def factors(n):
|
|
260
|
+
'''find the factors of n and return a list... very slowly
|
|
261
|
+
>>> factors(12345)
|
|
262
|
+
[3, 5, 823]
|
|
263
|
+
>>> factors(128)
|
|
264
|
+
[2, 2, 2, 2, 2, 2, 2]
|
|
265
|
+
'''
|
|
266
|
+
return list(ifactors(n))
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#! /usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
def t_sorted(*args, reverse=False):
|
|
4
|
+
'''sorted, allowing list of args or a single iterable
|
|
5
|
+
and returning a tuple not a list
|
|
6
|
+
|
|
7
|
+
>>> t_sorted((3,1,2))
|
|
8
|
+
(1, 2, 3)
|
|
9
|
+
|
|
10
|
+
>>> t_sorted(3,4,5,1,0)
|
|
11
|
+
(0, 1, 3, 4, 5)
|
|
12
|
+
|
|
13
|
+
>>> t_sorted(3,4,5,1,0, reverse=True)
|
|
14
|
+
(5, 4, 3, 1, 0)
|
|
15
|
+
|
|
16
|
+
'''
|
|
17
|
+
try:
|
|
18
|
+
it = iter(*args)
|
|
19
|
+
except TypeError:
|
|
20
|
+
return tuple(sorted(args, reverse=reverse))
|
|
21
|
+
else:
|
|
22
|
+
return tuple(sorted(it, reverse=reverse))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def t_apply(*args, fun=all):
|
|
26
|
+
'''Apply fun allowing args to be separate or a tuple
|
|
27
|
+
|
|
28
|
+
>>> t_apply(0,1,0,1)
|
|
29
|
+
False
|
|
30
|
+
>>> t_apply((True, True, True))
|
|
31
|
+
True
|
|
32
|
+
>>> t_apply(0,1,0,1, fun=sum)
|
|
33
|
+
2
|
|
34
|
+
>>> t_apply((37, 73, 99), fun=sum)
|
|
35
|
+
209
|
|
36
|
+
|
|
37
|
+
'''
|
|
38
|
+
try:
|
|
39
|
+
it = iter(*args)
|
|
40
|
+
except TypeError:
|
|
41
|
+
return fun(args)
|
|
42
|
+
else:
|
|
43
|
+
return fun(it)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def t_all(*args):
|
|
47
|
+
'''
|
|
48
|
+
>>> t_all(0,1,1)
|
|
49
|
+
False
|
|
50
|
+
'''
|
|
51
|
+
return t_apply(args)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def t_any(*args):
|
|
55
|
+
'''
|
|
56
|
+
>>> t_any(0,1,1)
|
|
57
|
+
True
|
|
58
|
+
'''
|
|
59
|
+
return t_apply(args, fun=any)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def t_min(*args):
|
|
63
|
+
'''
|
|
64
|
+
>>> t_min(0,1,1)
|
|
65
|
+
0
|
|
66
|
+
'''
|
|
67
|
+
return t_apply(args, fun=min)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def t_minp(*args):
|
|
71
|
+
'''
|
|
72
|
+
>>> t_minp(0,1,5)
|
|
73
|
+
1
|
|
74
|
+
'''
|
|
75
|
+
return min(x for x in args if x)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def t_max(*args):
|
|
79
|
+
'''
|
|
80
|
+
>>> t_max(0,1,1)
|
|
81
|
+
1
|
|
82
|
+
'''
|
|
83
|
+
return t_apply(args, fun=max)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def t_sum(*args):
|
|
87
|
+
'''
|
|
88
|
+
>>> t_sum(0,1,1)
|
|
89
|
+
2
|
|
90
|
+
'''
|
|
91
|
+
return t_apply(args, fun=sum)
|
|
92
|
+
|
|
93
|
+
def length(s):
|
|
94
|
+
'''Length of strings *or* integers
|
|
95
|
+
>>> length("This")
|
|
96
|
+
4
|
|
97
|
+
>>> length(987613)
|
|
98
|
+
6
|
|
99
|
+
|
|
100
|
+
'''
|
|
101
|
+
return len(str(s))
|