sl4ng/sl4ng/magnitudes.py

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))