215 lines
6.2 KiB
Python
215 lines
6.2 KiB
Python
"""
|
|
Making numbers readable and dimensional one order of magnitude at a time
|
|
"""
|
|
__all__ = 'nice_size nice_duration MemorySize Duration'.split()
|
|
|
|
from .debug import show
|
|
from .iteration import sample, nopes
|
|
|
|
|
|
orders = {
|
|
0: 'mono',
|
|
1: 'deca',
|
|
2: 'hecto',
|
|
3: 'kilo',
|
|
6: 'mega',
|
|
9: 'giga',
|
|
12: 'tera',
|
|
15: 'peta',
|
|
18: 'exa',
|
|
21: 'zetta',
|
|
24: 'yotta',
|
|
}
|
|
|
|
sredro = {v:k for k, v in orders.items()}
|
|
|
|
def lasso(number:complex, unit:str='') -> str:
|
|
"""
|
|
Make a large number more readable by inserting commas before every third power of 10 and adding units
|
|
"""
|
|
return f'{number:,} {unit}'.strip()
|
|
|
|
def set_case(unit:str, lower:bool=False) -> str:
|
|
"""
|
|
Set the case of a number's unit string
|
|
"""
|
|
return [unit.upper().strip(), unit.lower().strip()][lower]
|
|
|
|
def set_length(mag:str, unit:str, long:bool=False, sep:str='-') -> str:
|
|
"""
|
|
Set the length of a number's unit string
|
|
"""
|
|
return ('', sep)[long].join(((mag[0], unit[0]), (mag, unit))[long])
|
|
|
|
def magnitude(value:complex, omissions:list='mono deca hecto'.split()):
|
|
"""
|
|
Determine the best order of magnitude for a given number
|
|
"""
|
|
mags = tuple(sorted(i for i in orders.keys() if not orders[i] in omissions))
|
|
booly = lambda i: len(str(int(value))) < len(str(10**mags[i+1]))
|
|
fits = tuple(nopes((booly(i) for i in range(len(mags)-1)),True))
|
|
fit = mags[min(fits) if fits else len(mags)-1]
|
|
return orders[fit]
|
|
|
|
def nice_size(self:complex, unit:str='bytes', long:bool=False, lower:bool=False, precision:int=2, sep:str='-', omissions:list='mono deca hecto'.split()):
|
|
"""
|
|
This should behave well on int subclasses
|
|
"""
|
|
mag = magnitude(self, omissions)
|
|
precision = sredro[mag] if self<5 else precision
|
|
unit = set_case(set_length(mag, unit, long, sep), lower)
|
|
val = round(self*10**-(sredro[mag]), precision)
|
|
return lasso(val, unit)
|
|
|
|
class MemorySize(int):
|
|
"""
|
|
Why should you have to sacrifice utility for readability?
|
|
"""
|
|
def __repr__(self):
|
|
return nice_size(self)
|
|
|
|
|
|
|
|
second = 1
|
|
minute = 60 * second
|
|
hour = 60 * minute
|
|
day = 24 * hour
|
|
week = 7 * day
|
|
feb = 4 * week
|
|
year = 365.25 * day
|
|
# year = 13.044642857142858 * feb
|
|
|
|
|
|
def time_dict(duration):
|
|
periods = {
|
|
# 'years': int(duration // year),
|
|
'febs': int(duration // feb),
|
|
# 'febs': int(duration % year // feb),
|
|
'weeks': int(duration % feb // week),
|
|
# 'weeks': int(duration % feb // week),
|
|
'days': int(duration % week // day),
|
|
# 'days': int(duration % week // day),
|
|
'hours': int(duration % day // hour),
|
|
# 'hours': int(duration % day // hour),
|
|
'minutes': int(duration % hour // minute),
|
|
# 'minutes': int(duration % hour // minute),
|
|
|
|
'seconds': round(duration % minute),
|
|
}
|
|
return periods
|
|
|
|
|
|
def length(period):
|
|
return eval(period[:-1])
|
|
|
|
|
|
def timer_duration(periods, peak, base, watch):
|
|
string = ''
|
|
if watch:
|
|
peak = 'hours'
|
|
base = 'minutes'
|
|
elif length(base) > second:
|
|
base = 'seconds'
|
|
for k,v in periods.items():
|
|
if length(peak) >= length(k) >= length(base):
|
|
if k == peak:
|
|
string += f'{v:02d}'
|
|
else:
|
|
string += f':{v:02d}'
|
|
return string
|
|
|
|
|
|
def verbose_duration(periods, peak, base, short=False):
|
|
string = ''
|
|
for k, v in periods.items():
|
|
if v:
|
|
if length(peak) >= length(k) >= length(base):
|
|
if k == peak:
|
|
string += f'{v} {k[:1 if short else None]}'
|
|
else:
|
|
string += f', {v} {k[:1 if short else None]}'
|
|
return string
|
|
|
|
|
|
def nice_duration(duration, verbose=False, short=False, watch=False):
|
|
"""
|
|
Given a number of seconds, return a string which indicates the amount of time in:
|
|
Months (28 days), weeks, days, hours, minutes, seconds
|
|
|
|
parameters:
|
|
duration
|
|
number of seconds
|
|
verbose
|
|
include units in result
|
|
short
|
|
abbreviate units
|
|
watch
|
|
only "hours:minutes"
|
|
|
|
examples:
|
|
>>> humanize_duration(62)
|
|
'01:02'
|
|
|
|
>>> t = 12532510
|
|
|
|
>>> humanize_duration(t)
|
|
20:05:01:15:10
|
|
|
|
>>> humanize_duration(t, verbose=True)
|
|
20 weeks, 5 days, 1 hours, 15 months, 10 seconds
|
|
|
|
>>> humanize_duration(t,verbose=True, short=True)
|
|
20 w, 5 d, 1 h, 15 m, 10 s
|
|
|
|
>>> humanize_duration(t, watch=True) # equates to "what time will it be after this many seconds have passed"
|
|
01:15
|
|
"""
|
|
periods = time_dict(duration)
|
|
significant = [i for i in periods.keys() if periods.get(i)]
|
|
base = min(significant, key=length)
|
|
peak = max(significant, key=length)
|
|
if not verbose:
|
|
return timer_duration(periods, peak, base, watch)
|
|
return verbose_duration(periods, peak, base, short)
|
|
|
|
class Duration(float):
|
|
"""
|
|
Why should you have to sacrifice utility for readability?
|
|
"""
|
|
def __repr__(self):
|
|
return nice_duration(self)
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
band = lambda level, base=10, shift=0, root=1: tuple(root+i+shift for i in ((level+1)*base, level*base))[::-1]
|
|
format = lambda selection: int(''.join(str(i) for i in selection))
|
|
|
|
digits = range(*band(0))
|
|
|
|
l = 0
|
|
s = 0
|
|
b = 30
|
|
r = 1
|
|
x,y = band(l, b, s, r)
|
|
# print(x,y)
|
|
val = sample(digits,y)
|
|
sizes = [format(val[:i][::-1]) for i in range(x, y)]
|
|
# show(sizes)
|
|
# sizes = [44259260028,315436]
|
|
|
|
for size in sizes[::-1]:
|
|
string = f"""{(len(lasso(max(sizes)))-len(lasso(size)))*' '+lasso(size)}
|
|
{size = }
|
|
{len(str(size)) = }
|
|
{nice_size(size) = }
|
|
{magnitude(size) = }
|
|
|
|
|
|
""".splitlines()
|
|
print(*((x.strip(), x)[i<1] for i, x in enumerate(string)), sep='\n\t', end='\n\n')
|
|
print(lasso(sizes[-1]))
|
|
print(band(l,s,b,r))
|
|
print(x,y)
|
|
print(f'{format(digits):,}')
|
|
# print(all(rep(size)==rep2(size) for size in sizes)) |