moved file, path, and, persistence related tools to a forthcoming filey push. planning to remove types. added magnitudes. minor tweaks and additions throughout

This commit is contained in:
kendfss 2021-06-07 00:21:06 +01:00
parent af907967d8
commit 539c3a702b
22 changed files with 1225 additions and 1197 deletions

266
.gitignore vendored
View File

@ -1,129 +1,139 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/

30
readme.rst Normal file
View File

@ -0,0 +1,30 @@
sl4ng
=====
This package serves as a workflow booster for emailing, iterating, and a
veritable smorgasboard of cases scattered in between.
You want persistant data but Pickle and Dill ask one too many damned
questions? sl4ng.persistance.save sl4ng.persistance.load
You be writing generators so nice that you want to use them twice,
thrice, or indefinitely? We gotchu! sl4ng.types.regenerator
YouΓÇÖre on the brink of the creative breakthrough of the ages but youΓÇÖll
end it all in a fit of rage if you accidentally overwite your projects
ever again? We gotchu! sl4ng.files.paths.namespacer
You want to look at an objectΓÇÖs source code but inspect.getsource makes
a mess of rendering things in your REPL, or perhaps you want to jump
straight into its package-folder or source-file? sl4ng.debug.getsource
sl4ng.debug.pop
You want to want to see if your iterable is the codomain of a constant
function? We gotchu sl4ng.functional.eq
You really like your dictionary, but itΓÇÖs got some duplicate values?
We.. Got.. Chu! sl4ng.iteration.deduplicate
YouΓÇÖve read this far and think ΓÇ£Damn, son, this package looks diggity
fresh, but some of those functions are deeply nestedΓÇ¥? We gotchu
Everything is imported to init

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
psutil
pyperclip

View File

@ -3,13 +3,15 @@ from setuptools import setup, find_packages
with open('README.md', 'r') as fob:
long_description = fob.read()
with open('requirements.txt', 'r') as fob:
requirements = fob.readlines()
setup(
name='sl4ng',
version='0.0.1',
author='Kenneth Elisandro',
author_email='eli2and40@tilde.club',
url='https://tildegit.org/eli2and40/sl4ng',
# packages=find_packages(),
version='0.0.2',
author='Kenneth Sabalo',
author_email='kennethsantanasabalo@tilde.club',
url='https://github.com/kendfss/sl4ng',
packages=['sl4ng'],
classifiers=[
"Programming Language :: Python :: 3",
@ -20,15 +22,6 @@ setup(
long_description_content_type='text/markdown',
keywords='utilities productivity',
license='MIT',
requires=[
'pyperclip',
'dill',
'psutil',
'send2trash',
'tqdm',
'filetype',
],
# py_modules=['sl4ng'],
python_requires='>=3.8',
requires=requirements,
python_requires='>3.9',
)

View File

@ -2,21 +2,22 @@ from typing import Iterable, Any, Iterator, Sequence
import os
from .debug import *
from .files import *
from .functional import *
# from .files import *
# from .functional import *
from .iteration import *
from .maths import *
from .persistance import *
# from .persistence import *
from .stats import *
from .strings import *
from .system import *
from .types import *
# from .types import *
from .web import *
from .magnitudes import *
HERE,THIS = os.path.split(__file__)
HERE, THIS = os.path.split(__file__)

View File

@ -1,30 +1,36 @@
from typing import Any, Iterable
from typing import Any, Iterable, Generator, Callable
import sys, winsound, os, time, inspect
import pyperclip
from .types import SineWave, generator, regurge, function
separator = " ".join(70*'-')
spaceprint = lambda x: print(x, "\n{}".format(separator))
main = '__name__ == "__main__"'
mainame = main = '__name__ == "__main__"'
def tipo(inpt:Any=type(lambda:0), keep_module:bool=False) -> str:
def nome(inpt):
"""
Towards a tool that can get the name of a variable
"""
for k, v in locals().items():
if v == inpt:
return k
def tipo(inpt, keep_module:bool=True) -> str:
"""
Return the name of an object's type
Dependencies: None
In: object
Out: str
params:
keep_module
If set to False, the name of the class will be returned without any explicit reference to package nesting
"""
if isinstance(inpt, type):
inpt = str(inpt)
if keep_module:
return str(type(inpt)).split("'")[1]
return str(type(inpt)).split("'")[1].split('.')[-1]
def beeper(inpt:int) -> SineWave:
def beeper(inpt:int):
"""Produces a series of beeps of increasing frequency
Dependencies: Beep(a function from the winsound module)
In: (number of beeps desired)
@ -90,6 +96,7 @@ def modir(module:type(os), copy:bool=True) -> str:
def printer(iterable:Iterable, indent:int=0) -> None:
"""
Prints each element of an iterable on a new line, features an optional indent argument
ultimately replaced by "show"
"""
assert isinstance(indent, int), "Indentation level must be a positive integer"
for i in iterable:
@ -137,33 +144,26 @@ def aspectRatio(x:int, y:int, w:int=None, h:int=None) -> float:
return w/h==x/y if w and h else (w, (w*(y/x))) if w else ((h*(x/y)), h) if h else x/y
def show(iterable:Iterable[Any], indentation:int=0, enum:bool=False, first:int=1, indentor:str='\t', tail=True, head=False, file=sys.stdout, sep:str='') -> None:
def show(iterable:Iterable[Any], indentation:int=0, enum:bool=False, first:int=1, indentor:str='\t', tail=True, head=True, file=sys.stdout, sep:str='') -> Generator:
"""
Print each element of an array.
Print each element of an iterable.
>>> show(zip('abc','123'))
('a', '1')
('b', '2')
('c', '3')
>>>
"""
# consumable = regurge(iterable)
consumable = iter(regurge(iterable))
if (wasstr:=isinstance(file, str)):
file = open(file)
print('\n', file=file) if head else None
for i, j in enumerate(consumable, first):
for i, j in enumerate(iterable, first):
print(
(
f"{indentation*indentor}{j}",
f"{indentation*indentor}{i}\t{j}"
f"{indentation * indentor}{j}",
f"{indentation * indentor}{i}\t{j}"
)[enum],
sep=sep,
file=file
)
# if sep:
# print(sep)
print('\n', file=file) if tail else None
if wasstr:
file.close()
@ -221,8 +221,9 @@ def getsource(obj:Any, *args, copy:bool=False, **kwargs) -> str:
"""
text = inspect.getsource(obj).splitlines()
if copy:
pyperclip.copy('\n'.join(text))
return
text = '\n'.join(text)
pyperclip.copy(text)
return text
show(text, *args, **kwargs)

View File

@ -1,13 +0,0 @@
from .paths import *
from .operations import *
from .tasks import *
if __name__ == "__main__":
pass

View File

@ -1,57 +0,0 @@
import os, subprocess
from send2trash import send2trash
from tqdm import tqdm
from .paths import nameUpdater
def convert(file:str, format:str='.wav', bitRate:int=450, delete:bool=False, options:str='') -> str:
"""
Convert an audio file
"""
trash = discard
os.chdir(os.path.split(file)[0])
_title = lambda file: file.split(os.sep)[-1].split('.')[0]
_new = lambda file, format: nameUpdater(_title(file)+format)
_name = lambda file: file.split(os.sep)[-1]
format = '.' + format if '.' != format[0] else format
name = _title(file)
new = _new(file, format)
cmd = f'ffmpeg -y -i "{file}" -ab {bitRate*1000} "{new}"' if bitRate != 0 else f'ffmpeg {options} -y -i "{file}" "{new}"'
announcement = f"Converting:\n\t{file} --> {new}\n\t{cmd=}"
print(announcement)
subprocess.run(cmd)
print('Conversion is complete')
if delete:
trash(file)
print(f'Deletion is complete\n\t{new}\n\n\n')
return new
def move(file:str, destination:str) -> None:
"""
Move a file to a given directory
"""
nmxt = os.path.split(file)[-1]
ext = os.path.splitext(nmxt)[-1].replace('.', '')
with open(file, 'rb') as src:
src = tuple(i for i in src)
with open(destination, 'wb') as dst:
print(f'moving {nmxt.split(".")[0]} to the "{ext}" directory')
for datum in tqdm(src):
dst.write(datum)
def discard(path:str, recycle:bool=True) -> None:
"""
Remove an address from the file-system
"""
fb = (os.remove, send2trash)
first, backup = fb if not recycle else fb[::-1]
try:
first(path)
except PermissionError:
backup(path)

View File

@ -1,151 +0,0 @@
from typing import Iterable
import os, sys, time
defaultScriptDirectory = r'e:\projects\monties'
commons = {
'music': os.path.join(os.path.expanduser('~'), 'music', 'collection'),
'images': os.path.join(os.path.expanduser('~'), 'pictures'),
'pictures': os.path.join(os.path.expanduser('~'), 'pictures'),
'pics': os.path.join(os.path.expanduser('~'), 'pictures'),
'videos': os.path.join(os.path.expanduser('~'), 'videos'),
'ytdls': {
'music': {
'singles': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'music', 'singles'),
'mixes': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'music', 'mixes'),
'albums': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'music', 'album'),
},
'graff': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'graff'),
'bullshit': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'bullshitters'),
'bull': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'bullshitters'),
'code': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'cscode'),
'cs': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'cscode'),
'maths': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'maths'),
'math': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'maths'),
'movies': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'movies'),
'other': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'other'),
'physics': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'physics'),
'phys': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'physics'),
'politics': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'politics'),
'pol': os.path.join(os.path.expanduser('~'), 'videos', 'ytdls', 'politics'),
},
'documents': os.path.join(os.path.expanduser('~'), 'documents'),
'docs': os.path.join(os.path.expanduser('~'), 'documents'),
'downloads': os.path.join(os.path.expanduser('~'), 'downloads'),
'desktop': os.path.join(os.path.expanduser('~'), 'desktop'),
'books': os.path.join(os.path.expanduser('~'), 'documents', 'bookes'),
'monties': os.path.join(defaultScriptDirectory, str(time.localtime()[0])),
'scripts': defaultScriptDirectory,
'demos': os.path.join(defaultScriptDirectory, 'demos'),
'site': os.path.join(sys.exec_prefix, 'lib', 'site-packages'),
'home': os.path.expanduser('~'),
'user': os.path.expanduser('~'),
'root': os.path.expanduser('~'),
'~': os.path.expanduser('~'),
}
os.makedirs(commons['monties'], exist_ok=True)
def delevel(path:str, steps:int=1) -> str:
"""
This will climb the given path tree by the given number of steps.
No matter how large the number of steps, it will stop as soon as it reaches the root.
Probably needs revision for paths on systems which hide the root drive.
example
>>> for i in range(4):print(delevel(r'c:/users/admin',i))
c:/users/admin
c:/users
c:/
c:/
dependencies: os.sep
"""
while steps and (len(path.split(os.sep))-1):
path = os.sep.join((path.split(os.sep)[:-1]))
steps -= 1
return path if not path.endswith(':') else path+os.sep
def namespacer(path:str, sep:str='_', start:int=2) -> str:
"""
Returns a unique version of a given string by appending an integer
example:
tree:
/folder
/file.ext
/file_2.ext
>>> nameSpacer('file', sep='-', start=2)
file-2.ext
>>> nameSpacer('file', sep='_', start=2)
file_3.ext
>>> nameSpacer('file', sep='_', start=0)
file_0.ext
"""
id = start
oldPath = path[:]
while os.path.exists(path): ##for general use
newPath = list(os.path.splitext(path))
if sep in newPath[0]:
if newPath[0].split(sep)[-1].isnumeric():
# print('case1a')
id = newPath[0].split(sep)[-1]
newPath[0] = newPath[0].replace(f'{sep}{id}', f'{sep}{str(int(id)+1)}')
path = ''.join(newPath)
else:
# print('case1b')
newPath[0] += f'{sep}{id}'
path = ''.join(newPath)
id += 1
else:
# print('case2')
newPath[0] += f'{sep}{id}'
path = ''.join(newPath)
id += 1
return path
nameUpdater = nameSpacer = name_spacer = namespacer
def mcd(args:Iterable[str], go_back:bool=False, recursive:bool=True, overwrite:bool=False) -> str:
"""
recursively create and enter directories.
go_back:
if set to True, the process will return to the starting directory
recursive:
if set to false, all directories will be created in the starting directory
eg
each of the following calls create the following tree:
dir-1
dir0: starting directory
dir1
dir2
dir3
dir4
dir5
>>> mcd('dir1 dir2 .. dir3 .. .. dir4 .. .. dir5'.split())
>>> mcd('dir1/dir2 ../dir3 ../../dir4 ../../dir5'.split())
"""
home = os.getcwd()
for arg in args:
arg = nameSpacer(arg) if arg!='..' and not overwrite else arg
os.makedirs(arg, exist_ok=True)
os.chdir(arg if recursive else home)
last_stop = home if go_back else os.getcwd()
os.chdir(last_stop)
return last_stop
if __name__ == "__main__":
here, this = os.path.split(__file__)
this_2 = nameSpacer(this)
if not os.path.exists(this_2):
with open(this_2, 'x') as f:
print(nameUpdater(this, '-', 2))
print(nameUpdater(this, '_', 2))
print(nameUpdater(this, '_', 0))
os.remove(this_2)
# [*map(os.remove, (this, this_2))]

View File

@ -1,185 +0,0 @@
from typing import Sequence, Iterable
import re, os, subprocess
import filetype as ft
from ..types import generator
from ..iteration import shuffle
def search(keyword:str, directory:str, extension:str='', walk=True, **kwargs) -> generator:
"""
Find files matching the given keyword within a directory
"""
if re.search('\s', keyword):
keyword = map(lambda x: x.strip(), keyword.split())
if not isinstance(keyword, str):
keyword = '|'.join(map(re.escape, keyword))
else:
keyword = re.escape(keyword)
return filter(
lambda file:re.search(keyword, file.split(os.sep)[-1], re.I),
gather(directory, ext=extension, names=False, walk=walk, **kwargs)
)
def sortbysize(files:Iterable[str]=None) -> list:
"""
Sort a collection of file paths by the size of the corresponding files (largest to smallest)
"""
files = [os.getcwd(), list(files)][bool(files)]
size = lambda file: os.stat(file).st_size
out = []
while len(files)>0:
sizes = set(size(file) for file in files)
for file in files:
if size(file) == max(sizes):
out.append(file)
files.remove(file)
return out
def gather(folder:str=None, names:bool=False, walk:bool=True, ext:str=None) -> generator:
"""
Generate an iterable of the files rooted in a given folder
"""
folder = [os.getcwd(), folder][bool(folder)]
if walk:
if ext:
ext = ext.replace('.', '')
pred = [i for i in ',`* ' if i in ext]
pattern = '|'.join(f'\.{i}$' for i in ext.split(pred[0] if pred else None))
pat = re.compile(pattern, re.I)
for root, folders, files in os.walk(folder):
for file in files:
if os.path.isfile(p:=os.path.join(root, file)) and pat.search(file) and file!='NTUSER.DAT':
yield file if names else p
else:
for root, folders, titles in os.walk(folder):
for t in titles:
if os.path.exists(p:=os.path.join(root, t)):
yield t if names else p
else:
if ext:
ext = ext.replace('.', '')
pred = [i for i in ',`* ' if i in ext]
pattern = '|'.join(f'\.{i}$' for i in ext.split(pred[0] if pred else None))
pat = re.compile(pattern, re.I)
for file in os.listdir(folder):
if os.path.isfile(p:=os.path.join(folder, file)) and pat.search(file) and file!='NTUSER.DAT':
yield file if names else p
else:
for file in os.listdir(folder):
if os.path.isfile(p:=os.path.join(folder, file)):
yield file if names else p
def straw(path:str, text:bool=True, lines:bool=False) -> [str, list]:
"""
Extract the text, or bytes if the keyword is set to false, from a file
::text::
text or bytes?
::lines::
split text by line or return raw?
"""
if os.path.isfile(path):
mode = "rb r".split()[text]
with open(path, mode) as f:
return f.readlines() if lines else f.read()
def unarchive(path:str, destination:str=None, app:str='rar') -> None:
"""
Extract an archive to a chosen destination, or one generated based on the name of the archive
App refers to the comandlet you wish to invoke via subprocess.run
"""
options = {
'tar':'-x -f',
'rar':'e -or -r',
'winrar':'',
}
if destination != None:
os.makedirs(destination, exist_ok=True)
os.chdir(destination)
subprocess.run(f'{app} {options[app]} "{path}" ')
else:
destination = os.path.splitext(path)[0]
src = os.path.join('.', os.path.split(path)[1])
print(destination, path, src)
os.makedirs(destination, exist_ok=True)
os.chdir(destination)
subprocess.run(f'{app} {options[app]} "{src}" ')
extractRar = unarchive
def empty(path:str, make:bool=False) -> bool:
"""
Check if a given file or folder is empty or not with the option to create it if it doesn't exit
"""
if os.path.exists(path):
if os.path.isfile(path):
with open(path, 'rb') as f:
return not bool(len(tuple(i for i in f)))
elif os.path.isdir(path):
return not bool(len(os.listdir(file)))
elif make:
if os.path.splitext(path)[-1]:
x = open(path, 'x')
x.close()
else:
os.makedirs(path, exist_ok=True)
return True
def ffplay(files:Sequence[str], hide:bool=True, fullscreen:bool=True, loop:bool=True, quiet:bool=True, randomize:bool=True, silent:bool=False) -> None:
"""
Play a collection of files using ffmpeg's "ffplay" cli
If entering files as a string, separate each path by an asterisk (*), othewise feel free to use any iterator
-loop {f"-loop {loop}" if loop else ""}
"""
# fullscreen = False if hide else fullscreen
namext = lambda file: os.path.split(file)[1]
nome = lambda file: os.path.splitext(namext(file))[0]
ext = lambda file: os.path.splitext(file)[1]
isvid = lambda file: ft.match(file) in ft.video_matchers
vidtitle = lambda vid: '-'.join(i.strip() for i in vid.split('-')[:-1])
albumtrack = lambda file: bool(re.search(f'\d+\s.+{ext(file)}', file, re.I))
attitle = lambda file: ' '.join(i.strip() for i in nome(file).split(' ')[1:])
aov = lambda file: albumtrack(file) or isvid(file)
title = lambda file: ''.join(i for i in os.path.splitext(namext(file)[1])[0] if i not in '0123456789').strip()
windowtitle = lambda file: [namext(file), [attitle(file), vidtitle(file)][isvid(file)]][aov(file)]
play = lambda file: subprocess.run(f'ffplay {("", "-nodisp")[hide]} -window_title "{windowtitle(file)}" -autoexit {"-fs" if fullscreen else ""} {"-v error" if quiet else ""} "{file}"')
files = files.split('*') if isinstance(files,str) else files
if loop:
while (1 if loop==True else loop+1):
files = shuffle(files) if randomize else files
for i,f in enumerate(files, 1):
if os.path.isdir(f):
fls = [os.path.join(f, i) for i in gather(f, names=False)]
for j,file in enumerate(fls, 1):
name = os.path.split(file)[1]
print(f'{j} of {len(fls)}:\t{name}') if not silent else None
ffplay(file, hide, fullscreen, False, quiet, randomize, True)
else:
folder,name = os.path.split(f)
print(f'{i} of {len(files)}:\t{name}') if not silent else None
play(f)
loop -= 1
else:
files = shuffle(files) if randomize else files
for i, f in enumerate(files, 1):
if os.path.isdir(f):
fls = [os.path.join(f, i) for i in gather(f, names=False)]
for j, file in enumerate(fls, 1):
name = os.path.split(file)[1]
print(f'{j} of {len(fls)}:\t{name}') if not silent else None
ffplay(file, hide, fullscreen, False, quiet, randomize, True)
else:
print(f'{i} of {len(files)}:\t{title(f)}') if not silent else None
play(f)
size = lambda file: os.stat(file).st_size

View File

@ -1,19 +1,211 @@
# Statisticals checked against: http://www.alcula.com/calculators/statistics/
from itertools import tee, islice, chain, combinations
from typing import Iterable, Any, Sequence, List, Callable
from copy import deepcopy
from itertools import tee, _tee, islice, chain, combinations
from numbers import Number, Real
from typing import Iterable, Any, Sequence, List, Callable, Generator, Hashable
from math import log
import random
import random, time
from .types import generator, regurge, regenerator
from .maths import binomial
from .functional import eq
def xrange(stop:complex, start:complex=0, step:complex=1, reverse:bool=False) -> generator:
class __regen:
"""
A namespace for utilities used in creating regenerators
"""
@staticmethod
def choose(iterable:Iterable[Any], *indices:Sequence[int]) -> Generator:
"""
Yield specific elements from an iterable by index:
>>> [*choose(range(1, 10), 0, 3)]
[1, 4]
>>> [*choose(range(1, 10), (0, 3))]
[1, 4]
"""
indices = [*flatten(indices)]
yielded = []
for i, e in enumerate(iterable):
if i in indices:
yield e
yielded.append(e)
if len(yielded) == len(indices):
break
@staticmethod
def tipo(inpt:Any=type(lambda:0), keep_module:bool=False) -> str:
"""
Return the name of an object's type
Dependencies: None
In: object
Out: str
"""
if keep_module:
return str(type(inpt)).split("'")[1]
return str(type(inpt)).split("'")[1].split('.')[-1]
@staticmethod
def flatten(iterable:Iterable) -> Generator:
"""
Flatten a 2d iterable
Example:
>>> list(flatten([[1, 2], [3, 4]]))
[1, 2, 3, 4]
based on:
https://pythonprinciples.com/challenges/Flatten-a-list/
"""
for i in iterable:
if hasattr(i, '__iter__') or hasattr(i, '__next__'):
yield from i
else:
yield i
class regenerator:
"""
A self-replenishing (or non-consumable) iterator. All methods whose return value is iterable return regenerators
:args & kwargs:
Any arguments needed to initialize the Generator-type/function. Will not be used unless hasattr(iterable, '__call__') and iterable is not a regenerator.
eg:
>>> x = regurge(i for i in range(2))
>>> [*x]
[0, 1]
>>> bytes(x)
b'\x00\x01'
>>> [*x]
[0, 1]
"""
def __init__(self, iterable, *args, **kwargs):
if hasattr(iterable, '__call__') and not isinstance(iterable, type(self)):
self.active, self._inert = tee(iterable(*args, **kwargs))
else:
self.active, self._inert = tee(iterable)
def __next__(self):
return next(self.active)
def __iter__(self):
self.active, self._inert = tee(self._inert)
return self.active
def __getitem__(self, index:int):
if isinstance(index, int):
index = index if index >= 0 else len(self) + index
for i, e in enumerate(self):
if i == index:
return e
else:
raise
raise IndexError(f'{__regen.tipo(self)} contains fewer than {index} elements')
def __call__(self, *indices:Iterable[int]):
"""
Access particular indices of the underlying iterator
eg
>>> x = regen(range(3))
>>> [*x(1)]
[1]
>>> [*x(1,2)]
[1, 2]
>>> [*x(1,2,3)]
[1, 2]
"""
return type(self)(__regen.choose(self.active, *__regen.flatten(indices)))
def __bool__(self):
"""
Returns True iff. the underlying iterator is non-empty. False otherwise.
"""
tmp, self._inert = tee(self._inert)
try:
next(tmp)
return True
except StopIteration:
return False
def __matmul__(self, other:Iterable):
if hasattr(other, '__iter__'):
return type(self)(product(self, other))
raise TypeError(f'Matrix-multiplication is not defined between {__regen.tipo(self, True)} and "{__regen.tipo(other, True)}"-type. It must have an "__iter__" or "__index__" method.')
def __len__(self):
return sum(1 for i in self)
def __add__(self, other:Iterable):
"""
Create a new regenerator whose first elements come from self and remaining element(s) is/come-from other.
If other is not iterable it shall be added as the last element.
If you want to add an iterable as a single element, use self.append
eg
>>> x = regenerator(range(2))
>>> y = x + 10
>>> [*y]
[0, 1, 10]
>>> y += x
>>> [*y]
[0, 1, 10, 0, 1]
"""
other = other if hasattr(other, '__iter__') else [other]
return type(self)([*self, *other])
def __radd__(self, other:Iterable):
"""
Swap the order of __add__
"""
other = other if hasattr(other, '__iter__') else [other]
return type(self)(chain(other, self))
def __mul__(self, value:int):
"""
Replicate the behaviour of multiplying lists by integers
"""
if hasattr(value, '__int__'):
return type(self)(chain.from_iterable(self for i in range(int(value))))
raise TypeError(f'Multiplication is not defined for "{__regen.tipo(other, True)}". It must have an "__int__"')
def __rmul__(self, value:int):
"""Commutative multiplication"""
return self.__mul__(value)
def __pow__(self, value:int):
"""
value-dimensional Cartesian product of self with itself
"""
if hasattr(value, '__int__'):
return type(self)(product(self, repeat=int(value)))
raise TypeError(f'Exponentiation is not defined for {type(other)}. It must have an "__int__" method.')
def count(self, value:Any):
"""
how many copies of value?
"""
return sum(1 for i in self if i==value)
def scale(self, value:Any):
"""
Multiply every element of self by value. Done in place.
"""
self._inert = (i*value for i in self)
return self
def boost(self, value:Any):
"""
Add value to every element of self. Done in place.
"""
self._inert = (i+value for i in self)
return self
def indices(self, value:Any, shift:int=0):
"""
Similar to a list's index method, except that it returns every index whose element is a match
Works by calling enumerate and selecting at equivalent elements.
:shift:
kwarg for the "enumerate" call.
"""
return type(self)(i for i, e in enumerate(self, shift) if e == value)
def append(self, value:Any):
"""
add "value" as the last element of the array
"""
other = [value]
self._inert = chain(self._inert, other)
return self
def inject(self, value:Any):
"""
If "value" is an iterable: its elements will be added to the end of "self"
Otherwise: it is the same as append
"""
other = value if hasattr(value, '__iter__') else [value]
self._inert = chain(self._inert, *other)
return self
regen = regenerator
def xrange(stop:Number, start:Number=0, step:Number=1, reverse:bool=False) -> Generator:
"""
xrange(start, stop, step)
An implementation of the old xrange generator
An implementation of the old xrange Generator
examples:
>>> [*xrange(2)]
[0, 1]
@ -39,21 +231,20 @@ def xrange(stop:complex, start:complex=0, step:complex=1, reverse:bool=False) ->
start += step
def tight(iterable:Iterable[Any]) -> generator:
def tight(iterable:Iterable[Any], yielded:list=None) -> Generator:
"""
Produce a new iterator of unique elements from a given array
will consume a generator
will consume a Generator
"""
consumable = list(regurge(iterable))
# consumable = list(consumable)
yielded = []
yielded = yielded if not isinstance(yielded, type(None)) else []
for i in iterable:
if not i in yielded:
yield i
yielded.append(i)
unique = tight
def walks(iterable:Iterable[Any], length:int=2) -> generator:
def walks(iterable:Iterable[Any], length:int=2) -> Generator:
"""
Break an iterable into len(iterable)-length steps of the given length, with each step's starting point one after its predecessor
example
@ -66,22 +257,21 @@ def walks(iterable:Iterable[Any], length:int=2) -> generator:
Extended to generators with cues from more_itertools' "stagger"
Extended to infinite generators by pedantry
"""
consumable = regurge(iterable)
consumable = regenerator(iterable)
t = tee(consumable, length)
yield from zip(*(it if n==0 else islice(it, n, None) for n, it in enumerate(t)))
def flatten(iterable:Iterable) -> generator:
def flatten(iterable:Iterable) -> Generator:
"""
Flatten a 2d iterable
Transform an N-dimensional array into a N-1-dimensional array.
Example:
>>> list(flatten([[1, 2], [3, 4]]))
[1, 2, 3, 4]
based on:
https://pythonprinciples.com/challenges/Flatten-a-list/
"""
consumable = regurge(iterable)
for i in consumable:
for i in iterable:
if hasattr(i, '__iter__') or hasattr(i, '__next__'):
for j in i:
yield j
@ -89,48 +279,76 @@ def flatten(iterable:Iterable) -> generator:
yield i
def nopes(iterable:Iterable[Any], yeps:bool=False) -> generator:
def flat(iterable:Iterable, dict_keys:bool=False, strings:bool=False) -> Generator:
"""
Create a completely flat version of an iterable.
params:
strings
yield each character from each string if set to True
dict_keys
yield the keys instead of the values if set to True
"""
# if isinstance(iterable, fo;):
# yield iterable
if isinstance(iterable, str):
yield from iterable if strings else [iterable]
elif isinstance(iterable, dict):
itr = iterable.keys() if dict_keys else iterable.values()
for val in itr:
yield from flat(val, dict_keys, strings)
elif isinstance(iterable, (list, tuple, map, filter)):
for val in iterable:
if hasattr(val, '__iter__'):
yield from flat(val, dict_keys, strings)
else:
yield val
elif hasattr(iterable, '__iter__'):
yield from iterable
else:
yield iterable
def nopes(iterable:Iterable[Any], yeps:bool=False) -> Generator:
"""
if yeps==False
Return the indices of all false-like boolean values of an iterable
Return indices of all true-like boolean values of an iterable
example
t = (0,1,0,0,1)
>>> t = (0, 1, 0, 0, 1)
>>> tuple(nopes(t))
(0,2,3)
>>> tuple(nopes(t,True))
(1,4)
(0, 2, 3)
>>> tuple(nopes(t, True))
(1, 4)
"""
consumable = regurge(iterable)
for i, j in enumerate(consumable):
for i, j in enumerate(iterable):
if (not j, j)[yeps]:
yield i
def pull(iterable:Iterable, stop:int, start:int=0, step:int=1) -> tuple:
def roll(iterable:Iterable, stop:int, start:int=0, step:int=1) -> tuple:
"""
Return 'stop' elements from an effective cycle of the iterable, using only those whose modulus is greater than 'start'
Example:
pull('boris',5)
('b', 'o', 'r', 'i', 's')
pull('boris',6)
('b', 'o', 'r', 'i', 's', 'b')
pull('boris',6,1)
('o', 'r', 'i', 's', 'b', 'o')
pull('boris',6,1,2)
('r', 's', 'o')
>>> roll('boris', 5)
('b', 'o', 'r', 'i', 's')
>>> roll('boris', 6)
('b', 'o', 'r', 'i', 's', 'b')
>>> roll('boris', 6, 1)
('o', 'r', 'i', 's', 'b', 'o')
>>> roll('boris', 6, 1, 2)
('r', 's', 'o')
"""
consumable = regurge(iterable)
consumable = list(consumable)
return tuple(consumable[i%len(consumable)] for i in range(0, stop+start, step) if i>=start)
consumable = regenerator(iterable)
return tuple(consumable[(i + 0) % len(consumable)] for i in range(0, stop + start, step) if i >= start)
def deduplicate(unhashable:Iterable) -> dict:
"""
Because dictionaries seem to be less hashable than lists, which are also formally unhashable
This will consume a generator
This will consume a Generator
"""
if isinstance(unhashable, dict):
trimmed = {}
@ -142,63 +360,55 @@ def deduplicate(unhashable:Iterable) -> dict:
raise TypeError(f'Protocol for your {tipo(unhashable)} is pending')
def band(iterable:Iterable) -> float:
def band(iterable:Iterable[Real]) -> Real:
"""
Returns the extrema of the given iterable
"""
consumable = regurge(iterable)
consumable = list(consumable)
consumable = regenerator(iterable)
return min(consumable), max(consumable)
def bandGap(iterable:Iterable) -> float:
def bandgap(iterable:Iterable[Real]) -> Real:
"""
Returns the breadth of a given iterable of elements overwhich subtraction, max, and min, are well defined
"""
consumable = regurge(iterable)
consumable = list(consumable)
consumable = regenerator(iterable)
return max(consumable) - min(consumable)
def options(iterable:Iterable) -> int:
"""
Returns the number of ways to choose elements from the given iterable
This will consume a generator
"""
consumable = regurge(iterable)
consumable = list(consumable)
length = len(consumable)
return sum(binomial(length, i) for i in range(length))
def lispart(lis:Iterable, depth:int) -> list:
"""
Returns a collection of n*m elements as a list of m lists with n elements each
devised by David C. Ullrich from stack exchange
"""
consumable = regurge(iterable)
consumable = list(consumable)
consumable = regenerator(consumable)
assert(len(consumable)%depth == 0), f'The iterable cannot be factored into "{depth}" arrays'
return [consumable[j*depth:(j+1)*depth] for j in range(int(len(consumable)/depth))]
def sigma(iterable:Iterable[complex], v0:Any=0) -> complex:
def cast(x:int, y:int, iterable:Iterable=None) -> list:
"""
Return a YxX matrix (y lists of length x) whose elements are from slices of the iterable
"""
base = regenerator(iterable) if iterable else tuple(range(x * y))
assert x*y==len(list(tee(base)[0])), f"Iterable does not cast to (x, y) = {(x, y)}"
# return [base[slice(i*x, x+i*x)] for i in range(y)]
return [base[i * x: x + i * x] for i in range(y)]
def sigma(iterable:Iterable[Any], v0:Any=0) -> Any:
"""
Returns the sum of a iterable
"""
consumable = regurge(iterable)
consumable = list(consumable)
for i in consumable:
for i in iterable:
v0 += i
return v0
def pipe(iterable:Iterable[complex]) -> complex:
def pipe(iterable:Iterable[Number]) -> Number:
"""
Returns the multiplicative product of the elements of a collection
"""
consumable = regurge(iterable)
consumable = list(consumable)
consumable = regenerator(iterable)
if len(consumable) < 1:
return "this list is empty"
else:
@ -208,30 +418,29 @@ def pipe(iterable:Iterable[complex]) -> complex:
return v0
def powerset(iterable:Iterable) -> regenerator:
def powerset(iterable:Iterable[Any]) -> regenerator:
"""
Returns the powerset of an iterable as a list
"""
consumable = regenerator(iterable)
return chain.from_iterable(combinations(consumable, r) for r in range(sum(1 for i in consumable)+1))
return regenerator(chain.from_iterable(combinations(consumable, r) for r in range(sum(1 for i in consumable)+1)))
def sample(iterable:Iterable, size:int) -> tuple:
def sample(iterable:Iterable[Any], size:int) -> tuple:
"""
Obtains a random sample of any length from any iterable and returns it as a tuple
Unlike random.sample, this may yield the same element several times.
"""
consumable = regurge(iterable)
consumable = list(consumable)
consumable = regenerator(iterable)
choiceIndices = tuple(random.randint(0, len(consumable)-1) for i in range(size))
return tuple(consumable[i] for i in choiceIndices)
def shuffle(iterable:Iterable) -> tuple:
def shuffle(iterable:Iterable[Any]) -> tuple:
"""
Given an iterable, this function returns a new tuple of its elements in a new order
"""
consumable = regurge(iterable)
consumable = list(consumable)
consumable = regenerator(iterable)
cache = []
pot = []
while len(cache) < len(consumable):
@ -249,13 +458,13 @@ def unzip(iterable:Iterable[Sequence[Any]]) -> List[list]:
This is about the same as a clockwise rotation of a matrix by 90 degrees
This will omit empty arrays
examples
>>> m3ta.show(unzip(range(3) for i in range(3)))
>>> sl4ng.show(unzip(range(3) for i in range(3)))
[0, 0, 0]
[1, 1, 1]
[2, 2, 2]
>>> m3ta.show(unzip(range(j) for j in range(10)))
>>> sl4ng.show(unzip(range(j) for j in range(10)))
[0, 0, 0, 0, 0, 0, 0, 0, 0]
[1, 1, 1, 1, 1, 1, 1, 1]
[2, 2, 2, 2, 2, 2, 2]
@ -268,8 +477,8 @@ def unzip(iterable:Iterable[Sequence[Any]]) -> List[list]:
"""
consumable = regurge(iterable)
str_escape = lambda string: string.replace("'", "\'")
consumable = regenerator(iterable)
str_escape = lambda string: string.replace("'", "\'").replace("\\", "\\\\")
length = 0
racks = []
for i in consumable:
@ -278,11 +487,11 @@ def unzip(iterable:Iterable[Sequence[Any]]) -> List[list]:
exec(f"x{j} = []")
racks.append(eval(f'x{j}'))
length += 1
app_elem = f"x{j}.append('{str_escape(k)}')" if isinstance(k, str) else f"x{j}.append({k})"
app_elem = f"""x{j}.append('{(k, str_escape(k))[isinstance(k, str)]}')"""
eval(app_elem)
return racks
def diffs(iterable:Iterable[complex], flip:bool=False) -> generator:
def diffs(iterable:Iterable[Number], flip:bool=False) -> Generator:
"""
Yield the difference between each element of an iterable and its predecessor
example:
@ -291,17 +500,17 @@ def diffs(iterable:Iterable[complex], flip:bool=False) -> generator:
>>> [*diffs(range(3), True)]
[-1, -1]
"""
consumable = iter(regurge(iterable))
consumable = iter(iterable)
last = next(consumable)
for i in consumable:
yield i - last if not flip else last - i
last = i
def discontinuities(iterable:Iterable[complex], delta:complex=1) -> generator:
def discontinuities(iterable:Iterable[Number], delta:Number=1) -> Generator:
"""
Obtain the ordinal positions of any elements who do not differ from their successors by delta
Iterable must, at least, be a semigroupoid with subtraction
Not generator safe.
Not Generator safe.
example:
>>> [*discontinuities(range(3))]
[]
@ -315,7 +524,7 @@ def discontinuities(iterable:Iterable[complex], delta:complex=1) -> generator:
yield i
last = e
def stationaries(iterable:Iterable[complex]) -> generator:
def stationaries(iterable:Iterable[Number]) -> Generator:
"""
Generate the indices of the stationary points of an iterable
example:
@ -331,7 +540,7 @@ def stationaries(iterable:Iterable[complex]) -> generator:
yield i
last = e
def discontinuous(iterable:Iterable[complex], differential:complex=1) -> generator:
def discontinuous(iterable:Iterable[Number], differential:Number=1) -> Generator:
"""
Check if an additive semigroupoid has any jumps which are inequivalent to a given differential
example:
@ -352,7 +561,7 @@ def discontinuous(iterable:Iterable[complex], differential:complex=1) -> generat
def sums(iterable:Iterable[complex], flip:bool=False) -> generator:
def sums(iterable:Iterable[Number], flip:bool=False) -> Generator:
"""
Yield the sum of each element of an iterable and its predecessor
example:
@ -361,13 +570,13 @@ def sums(iterable:Iterable[complex], flip:bool=False) -> generator:
>>> [*sums('abc', True)]
['ab', 'bc']
"""
consumable = iter(regurge(iterable))
consumable = iter(iterable)
last = next(consumable)
for i in consumable:
yield i + last if not flip else last + i
last = i
def quots(iterable:Iterable[complex], flip:bool=False) -> generator:
def quots(iterable:Iterable[Number], flip:bool=False) -> Generator:
"""
Yield the quotient of each element of an iterable by its predecessor
example:
@ -376,13 +585,13 @@ def quots(iterable:Iterable[complex], flip:bool=False) -> generator:
>>> [*quots(range(1, 4), True)]
[0.5, 0.6666666666666666]
"""
consumable = iter(regurge(iterable))
consumable = iter(iterable)
last = next(consumable)
for i in consumable:
yield i / last if not flip else last / i
last = i
def prods(iterable:Iterable[complex], flip:bool=False) -> generator:
def prods(iterable:Iterable[Number], flip:bool=False) -> Generator:
"""
Yield the product of each element of an iterable by its predecessor
example:
@ -391,13 +600,13 @@ def prods(iterable:Iterable[complex], flip:bool=False) -> generator:
>>> [*prods(range(1, 4), True)]
[2, 6]
"""
consumable = iter(regurge(iterable))
consumable = iter(iterable)
last = next(consumable)
for i in consumable:
yield i * last if not flip else last * i
last = i
def logs(iterable:Iterable[complex], flip:bool=False) -> generator:
def logs(iterable:Iterable[Number], flip:bool=False) -> Generator:
"""
Yield the log of each element of an iterable in the base of its predecessor
example:
@ -406,13 +615,13 @@ def logs(iterable:Iterable[complex], flip:bool=False) -> generator:
>>> [*logs(range(2, 5), True)]
[0.6309297535714574, 0.7924812503605781]
"""
consumable = iter(regurge(iterable))
consumable = iter(iterable)
last = next(consumable)
for i in consumable:
yield log(i, last) if not flip else log(last, i)
last = i
def cumsum(iterable:Iterable[complex], first:bool=True) -> generator:
def cumsum(iterable:Iterable[Number], first:bool=True) -> Generator:
"""
Yield the log of each element of an iterable in the base of its predecessor
example:
@ -421,7 +630,7 @@ def cumsum(iterable:Iterable[complex], first:bool=True) -> generator:
>>> [*cumsum(range(4), False)]
[1, 3, 6]
"""
consumable = iter(regurge(iterable))
consumable = iter(iterable)
last = 0
if first:
yield last
@ -429,7 +638,7 @@ def cumsum(iterable:Iterable[complex], first:bool=True) -> generator:
last += i
yield last
def cumdif(iterable:Iterable[complex], first:bool=True) -> generator:
def cumdif(iterable:Iterable[Number], first:bool=True) -> Generator:
"""
Yield the log of each element of an iterable in the base of its predecessor
example:
@ -438,7 +647,7 @@ def cumdif(iterable:Iterable[complex], first:bool=True) -> generator:
>>> [*cumdif(range(4), False)]
[-1, -3, -6]
"""
consumable = iter(regurge(iterable))
consumable = iter(iterable)
last = next(consumable)
if first:
yield last
@ -446,7 +655,7 @@ def cumdif(iterable:Iterable[complex], first:bool=True) -> generator:
last -= i
yield last
def cumprod(iterable:Iterable[complex], first:bool=True) -> generator:
def cumprod(iterable:Iterable[Number], first:bool=True) -> Generator:
"""
Yield the cumulative product of the elemements of an iterable
example:
@ -455,7 +664,7 @@ def cumprod(iterable:Iterable[complex], first:bool=True) -> generator:
>>> [*cumprod(range(1, 4), False)]
[2, 6]
"""
consumable = iter(regurge(iterable))
consumable = iter(iterable)
last = next(consumable)
if first:
yield last
@ -463,7 +672,7 @@ def cumprod(iterable:Iterable[complex], first:bool=True) -> generator:
last *= i
yield last
def cumquot(iterable:Iterable[complex], first:bool=True) -> generator:
def cumquot(iterable:Iterable[Number], first:bool=True) -> Generator:
"""
Yield the cumulative quotient of the elemements of an iterable
example:
@ -472,7 +681,7 @@ def cumquot(iterable:Iterable[complex], first:bool=True) -> generator:
>>> [*cumquot(range(1, 4), False)]
[0.5, 0.16666666666666666]
"""
consumable = iter(regurge(iterable))
consumable = iter(iterable)
last = next(consumable)
if first:
yield last
@ -481,7 +690,7 @@ def cumquot(iterable:Iterable[complex], first:bool=True) -> generator:
yield last
def choose(iterable:Iterable[Any], *indices:Sequence[int]) -> generator:
def choose(iterable:Iterable[Any], *indices:Iterable[int]) -> Generator:
"""
Yield specific elements from an iterable by index:
>>> [*choose(range(1, 10), 0, 3)]
@ -490,7 +699,7 @@ def choose(iterable:Iterable[Any], *indices:Sequence[int]) -> generator:
[1, 4]
"""
indices = [*flatten(indices)]
indices = regenerator(flat(indices))
yielded = []
for i, e in enumerate(iterable):
if i in indices:
@ -499,11 +708,16 @@ def choose(iterable:Iterable[Any], *indices:Sequence[int]) -> generator:
if len(yielded) == len(indices):
break
def skip(iterable:Iterable[Any], *indices:Iterable[int]) -> Generator:
"""
Skip specific elements from an iterable by index
"""
indices = tuple(flat(indices))
for i, e in enumerate(iterable):
if not i in indices:
yield e
# def chooser(iterable:Iterable, *)
def dupes(array:Iterable, once:bool=True, key:Callable=lambda x:x) -> generator:
def dupers(array:Iterable, once:bool=True, key:Callable=lambda x:x) -> Generator:
"""
Yield the elements of a finite iterable whose frequency is greater than one
:once:
@ -529,7 +743,7 @@ def dupes(array:Iterable, once:bool=True, key:Callable=lambda x:x) -> generator:
last_yield = i
last_seen = i
def slices(iterable:Iterable, length:int, fill:Any=None) -> generator:
def slices(iterable:Iterable, length:int, fill:Any=None) -> Generator:
"""
Yield the adjacent slices of a given length for the given iterable. Trailing values will be padded by copies of 'fill'
use filter(all, slices(iterable, length)) to discard remainders
@ -546,8 +760,227 @@ def slices(iterable:Iterable, length:int, fill:Any=None) -> generator:
main += [fill for i in range(length-len(main))]
yield tuple(main)
def repeat(func:Callable, inpt:Any, times:int=2, **kwargs):
"""
Get the output of recursive calls of a function some number of times
Dependencies: None
In: function,input,times[=1]
Out: Function call
repeat(lambda x: x+1,0)
2
repeat(lambda x: x+1,0,1)
1
"""
for i in range(times):
inpt = func(inpt, **kwargs)
return inpt
recursor = repeat
def eq(*args:Iterable[Any]) -> bool:
"""
Check if arguments are equal
Will always return True if the only argument given is not an iterable
"""
if len(args) == 1:
if hasattr(arg:=args[0], '__iter__'):
return eq(*arg)
return True
args = iter(args)
arg0 = next(args)
for i in args:
if i != arg0:
return False
return True
def clock(func:Callable, value:bool=False, name:bool=False, verbose:bool=False) -> Callable:
"""
A decorator for benchmarking functions
:name:
option to print the name of the function with the time taken to call it
:value:
return the value of the function instead of the period
taken from
Mari Wahl, mari.wahl9@gmail.com
University of New York at Stony Brook
"""
def wrapper(*args:Any, **kwargs:Any) -> Any:
t = time.perf_counter()
res = func(*args, **kwargs)
delta = time.perf_counter()-t
if verbose:
if name:
print(f"{func.__name__}\n\t{delta}")
else:
print(f"d={delta}")
return res if value else delta
return wrapper
benchmark = clock
def imap(argument:Any, *functions:Callable) -> Generator:
"""
Converse of a map. Yield calls of the functions with the object as an argument
Generator safe
"""
iterates = hasattr(object, '__iter__')
for func in functions:
yield func((argument, regenerator(argument))[iterates])
def rmap(argument:Any, *functions:Callable) -> Any:
"""
Recursively call functions on an argument. Return the result of the last call
Generator safe
"""
functions = iter(functions)
iterates = hasattr(object, '__iter__')
result = next(functions)((argument, regenerator(argument))[iterates])
for func in functions:
result = func((result, regenerator(result))[iterates])
return result
def dictate(dictionary:dict, *omissions:Hashable, include:bool=False):
"""
Create a selective clone of a dictionary
params:
omissions
the hashable values corresponding to keys you would like to remove
include
If true, only keys specified in "omissions" will persist in the final dictionary
>>> dictate(dict(zip('abc', range(3))), 'a')
{'b': 1, 'c': 2}
>>> dictate(dict(zip('abc', range(3))), 'a', include=True)
{'a': 0}
>>> dictate(dict(zip('abc', range(3))), 'b c'.split(), include=True)
{'b': 1, 'c': 2}
"""
if include:
return {key: dictionary.get(key) for key in dictionary if key in flat(omissions)}
return {key: dictionary.get(key) for key in dictionary if not key in flat(omissions)}
def itersplit(iterable:Iterable[Any], *indices:int, cumulative:bool=False):
"""
Split an iterable at the given indices or their cumulative sums
examples:
>>> sl4ng.show(itersplit('gravitation', 3, 6, 8))
('g', 'r', 'a')
('v', 'i', 't')
('a', 't')
('i', 'o', 'n')
>>> sl4ng.show(itersplit('gravitation', 3, 3, 2, cumulative=True))
('g', 'r', 'a')
('v', 'i', 't')
('a', 't')
('i', 'o', 'n')
"""
indices = flat(indices)
indices = regenerator(cumsum(indices, first=False)) if cumulative else sorted(indices)
assert all(isinstance(i, int) for i in indices), "Some indices aren't integers"
boxes = tuple([] for i in indices)
index = 0
iterable = iter(iterable)
for i, e in enumerate(iterable):
if i >= indices[index]:
yield tuple(boxes[index])
index += 1
try:
boxes[index].append(e)
except IndexError:
break
if (last := tuple([e, *iterable])):
yield tuple(last)
def interval(start:float, stop:float, step:float, reverse=False) -> Generator:
"""
Compute an [x0, x1, dx)-interval and its cardinality
Params
start:
least element of the interval
stop:
cutoff element
step:
difference between consecutive elements
reverse:
traverse the interval in reverse order
"""
# ctr = 0
# itr = []
while start + step < stop:
# itr.append(start)
yield start
start += step
# ctr += 1
# return ctr, itr
class Interval:
def __init__(self, start:float, stop:float, step:float):
"""
Compute a yielding [)-interval and access useful properties such as its cardinality.
This is essentially equivalent to "range", for non-integer aritmetics
Params
start:
least element of the interval
stop:
cutoff element
step:
difference between consecutive elements
reverse:
traverse the interval in reverse order
"""
self.start = start
self.stop = stop
self.step = step
def __iter__(self):
start = self.start
stop = self.stop
step = self.step
while start + step < stop:
yield start
start += step
def __len__(self):
return sum(1 for i in self)
def __reversed__(self):
start = self.start
stop = self.stop
step = self.step
while stop - step > start:
yield start
stop -= step
def __getitem__(self, key:int):
for index, item in enumerate(self):
if index==key:
return item
raise IndexError
if __name__ == "__main__":
from statistics import median
# pass
for i in range(1,10):
print([*range(i)])
print(median(range(i)))
# for i in range(1,10):
# print([*range(i)])
# print(median(range(i)))
n = 6
for i in range(n):
print(roll(range(3), i))
for i in range(n):
print(roll(range(3), 3, i))

215
sl4ng/magnitudes.py Normal file
View File

@ -0,0 +1,215 @@
"""
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))

View File

@ -3,78 +3,72 @@
from functools import lru_cache, reduce
from itertools import chain, combinations, count
from math import pi, ceil
from typing import Iterable
from typing import Iterable, Generator
from numbers import Number, Real, Complex, Integral
from .types import generator
# from .types import regenerator
from .iteration import flat, regenerator
def pyramid(length:int, shift:int=0) -> float:
def sign(number:Real) -> str:
return '-+'[number >= 0]
def pyramid(length:Integral, shift:Integral=0) -> Real:
"""
Returns the product of dividing 1 by each intger in the range(2+shift,2+int(length)+shift) (aka - the number of poops in your pocket)
if the length is not an integer it will be converted to one
Dependencies: N/A
In: length, shift=0 (integers)
Out: N/A
"""
val = 1
for i in range(2+shift, 2+int(length)+shift):
for i in range(2 + shift, 2 + int(length) + shift):
val /= i
return val
def primes1(n:int) -> list:
"""Generates a list of the first n primes. A cast will be used if the input is not an integer
Dependencies: N/A
In: (number)
Out: list"""
# pwimes = list(x for x in range(n) if x > 1 and all(x%y for y in range(2, min(x, 11))))
def first_primes(n:Integral) -> list:
"""
Generates a list of the first n primes. A cast will be used if the input is not an integer
"""
n = int(n) - 1
bank = []
track = 2
while len(bank)<n+1:
if all(track%y for y in range(2, min(track,11))):
while len(bank)< n + 1:
if all(track % y for y in range(2, min(track, 11))):
bank.append(track)
track += 1
return sorted(set(bank))
def primes2(n:int) -> list:
"""Generates a list of primes with value lower than the input integer
Dependencies: N/A
In: (integer)
Out: list"""
pwimes = list({x for x in range(n) if x > 1 and all(x%y for y in range(2, min(x, 11)))})
return list(pwimes)
def primeslt(n:Integral) -> Generator:
"""
Generates a list of primes with value lower than the input integer
"""
return (x for x in range(n) if x > 1 and all(x % y for y in range(2, min(x, 11))))
primes_lower_than = primeslt
def succ(n:Integral) -> Integral:
"""
Returns the successor of the input number
"""
return n + 1
def succ(n:int) -> int:
"""Returns the successor of the input number
Dependencies: N/A
In: (number)
Out: number"""
return n+1
def pred(n:Integral) -> Integral:
"""
Returns the predecessor of the input number
"""
return n - 1
def pred(n:int) -> int:
"""Returns the predecessor of the input number
Dependencies: N/A
In: (number)
Out: number"""
return n-1
def rt(x:Integral, n:Complex=2) -> Complex:
"""
Returns the n-th root of the input number, x
"""
return x ** (1 / n)
def rt(x:int, n:complex) -> complex:
"""Returns the nth root of the input number, x
Dependencies: N/A
In: (x, n)
Out: float(number)"""
return x**(1/n)
def gcd0(a:int, b:int) -> int:
"""The famous euclidean algorithm for computing the greatest common divisor of a pair of numbers a and b
Dependencies: N/A
In: (a: first number, b: second number)
Out: int"""
def gcd0(a:Integral, b:Integral) -> Integral:
"""
The famous euclidean algorithm for computing the greatest common divisor of a pair of numbers a and b
"""
while a != b:
if a > b:
a -= b
@ -83,30 +77,24 @@ def gcd0(a:int, b:int) -> int:
return a
def gcd(*args:[int, tuple]) -> int:
def gcd(*args:[int, tuple]) -> Integral:
"""
Compute the gcd for more than two integers at a time. Returns input if only one argument is given and it is greater than zero
Dependencies: itertools.combinations
In: subscriptable
Out: int
"""
if any(i<=0 for i in args):
if any(i <= 0 for i in args):
return None
if len(args)>1:
gcds = {d for pair in combinations(args, 2) if all(i%(d:=gcd0(*pair))==0 for i in args)}
return max(gcds)
elif sum(args)>0:
return args[0]
if len(args) > 1:
gcds = {d for pair in combinations(args, 2) if all(i % (d := gcd0(*pair)) == 0 for i in args)}
return max(gcds) if gcds else 1
elif sum(args) > 0:
return max(args)
def eratosthenes(n:int, imaginarypart:bool=False) -> generator:
def eratosthenes(n:Integral, imaginarypart:bool=False) -> Generator:
"""
Implements eratothenes' sieve as a generator.
Implements eratothenes' sieve as a Generator.
If the input is not an int it will be rounded to one.
Imaginary-part-based rounding optionable
Dependencies: None
In: int
Out: generator
"""
iscomp = isinstance(n, complex) or issubclass(type(n), complex)
n = round(n.imag) if imaginarypart and iscomp else round(n.real) if iscomp else round(n)
@ -115,18 +103,15 @@ def eratosthenes(n:int, imaginarypart:bool=False) -> generator:
primes = set()
for i in rack:
if i not in marked:
multiples = {j for j in rack if j%i==0 and j>i}
multiples = {j for j in rack if j % i == 0 and j > i}
marked.update(multiples)
yield i
def factors0(n:int) -> generator:
'''
def _factors(n:Integral) -> Generator:
"""
Compute the factors of an integer
Dependencies: itertools.(chain, combinations)
In: int, or sequence of ints
Out: generator
'''
"""
pipe = lambda array: reduce(lambda x, y: x*y, array, 1)
primes = tuple(eratosthenes(n))
facts = {n, 1} if n!= 0 else {}
@ -145,34 +130,26 @@ def factors0(n:int) -> generator:
yield from facts
def factors(*args:[int, tuple]) -> generator:
'''
def factors(*args:[int, tuple]) -> Generator:
"""
Compute the common factors of any finite number of integers
Dependencies: m3ta.factors0
In: int, or sequence of ints
Out: generator
'''
if len(args) == 1 and hasattr(args[0], '__iter__'):
args = tuple(args[0])
"""
args = regenerator(flat(args))
if all(isinstance(i, int) or i==int(i) for i in args):
# facs = []
yielded = set()
for i in args:
for j in factors0(i):
# facs.append(j)
if all(not arg%j for arg in args):
yield j
# for i in facs:
# if freq(i, facs)==len(args):
# yield i
if not i in yielded:
for j in _factors(i):
if not j in yielded:
yielded.add(j)
if all(not arg%j for arg in args):
yield j
@lru_cache(maxsize = 500)
def factorial(n:int) -> int:
def factorial(n:Integral) -> Integral:
"""
Return n! for any integer
Dependencies: lru_cache(from functools)
In: (int)
Out: int
"""
if n>=0:
k = 1
@ -184,124 +161,133 @@ def factorial(n:int) -> int:
return -factorial(abs(n))
def binomial(n:int, k:int) -> int:
'''Returns the n choose k for any k in range(n)
Dependencies: factorial
In: (integer)
Out: float'''
return round(factorial(n)/(factorial(n-k)*factorial(k)))
def binomial(n:Integral, k:Integral) -> Integral:
"""
Returns the n choose k for any k in range(n)
"""
return int(factorial(n)/(factorial(n-k)*factorial(k)))
def isHarmoDiv(n:int) -> bool:
"""Computes a boolean whose value corresponds to the statement 'the number n is a Harmonic Divisor Number'
Dependencies: harmean (from meta)
In: Number
Out: Boolean"""
def options(iterable:Iterable) -> Integral:
"""
Returns the number of ways to choose elements from the given iterable
This will consume a Generator
"""
consumable = regenerator(iterable)
length = sum(1 for i in consumable)
return sum(binomial(length, i) for i in range(length))
def isHarmoDiv(n:Integral) -> bool:
"""
Computes a boolean whose value corresponds to the statement 'the number n is a Harmonic Divisor Number'
"""
facts = factors(n)
return int(harmean(facts)) == harmean(facts)
def isFactor(divisor:int, predicate:int) -> bool:
"""Determines if a Number is a multiple of a Divisor
Dependencies: N/A
In: (Divisor, Number)
Out: Boolean"""
return predicate % divisor == 0
def isfactor(divisor:Integral, divisee:Integral) -> bool:
"""
Determines if a Number is a multiple of a Divisor
"""
return divisee % divisor == 0
def isprime(n:int) -> bool:
"""Determines if a Number is a multiple of a Divisor
Dependencies: N/A
In: (Divisor, Number)
Out: Boolean"""
return n in eratosthenes(n+1)
def isprime(n:Integral) -> bool:
"""
Confirm that an integer has no factors other than 1 and itself
"""
if n < 2:
return False
tried = []
for i in range(2, int(n/2)+1):
if any(not i%j for j in tried):
continue
else:
tried.append(i) if i > 1 else None
if not n%i:
return False
return True
def isPerfect(n:int) -> bool:
"""Returns a Boolean evaluation of an integer's Arithmetic Perfection
Dependencies: sigma, factors (both from meta)
In: Integer
Out: Boolean"""
def isperfect(n:Integral) -> bool:
"""
Check if an integer is equal to the sum of its factors
"""
return n == sum(factors(n))
def isAbundant(n:int) -> bool:
"""Returns a Boolean evaluation of an integer's Arithmetic Abundance
Dependencies: sigma, factors (both from meta)
In: Integer
Out: Boolean"""
def isabundant(n:Integral) -> bool:
"""
Check if an integer is greater than the sum of its factors
"""
return n > sigma(factors(n))
def isDeficient(n:int) -> bool:
"""Returns a Boolean evaluation of an integer's Arithmetic Deficience
Dependencies: sigma, factors (both from meta)
In: Integer
Out: Boolean"""
def isdeficient(n:Integral) -> bool:
"""
Check if an integer is smaller than the sum of its factors
"""
return n < sigma(factors(n))
def isFilial(n:int) -> bool:
fctrs = factors(n)
lace = [int(i) for i in str(n)]
return sigma(lace) in fctrs
def isfilial(n:Integral) -> bool:
"""
Check if an integer is divisible by the sum of its digits
"""
return not n % sum(eval(i) for i in str(n))
def mulPer(n:int) -> int:
"""Computes the Multiplicative Persistence of an int or float in base-10 positional notation
Dependencies: pipe (from meta)
In: Integer
Out: Integer"""
def mulper(n:Integral) -> Integral:
"""
Computes the Multiplicative Persistence of an int or float in base-10 positional notation
If the number is a float the decimal will be removed
"""
# Exclusions
if len(str(n)) == 1:
return 0
elif (str(0) in str(n)) or ((len(str(n)) == 2) and (str(1) in str(n))):
return 1
else:
cache = []
ctr = 0
while len(str(n)) > 1:
digitList = [int(i) for i in "".join(str(n).split('.'))]
n = pipe(digitList)
cache.append(n)
return len(cache)
# digitList = [int(i) for i in "".join(str(n).split('.'))]
digitList = map(int, str(i).replace('.', ''))
n = reduce(lambda x, y: x*y, digitList, 1)
ctr += 1
return ctr
def addPer(n:int) -> int:
"""Computes the Additive Persistence of an int or float in base-10 positional notation
Dependencies: sigma (from meta)
In: Integer
Out: Integer"""
def addper(n:Integral) -> Integral:
"""
Computes the Additive Persistence of an int or float in base-10 positional notation
"""
if len(str(n)) == 1:
return 0
elif len(str(n)) < 1:
return ValueError("Your number of choice has less than one digit")
else:
cache = []
ctr = 0
while len(str(n)) > 1:
digitList = [int(i) for i in "".join(str(n).split('.'))]
n = sigma(digitList)
cache.append(n)
return len(cache)
n = sum(digitList)
ctr += 1
return ctr
def triangular(a:int, b:int) -> int:
"""Returns the triangular number of an interval [a,b]
dependencies: none
In: integers a and b
Out: integer"""
def triangular(a:Integral, b:Integral) -> Integral:
"""
Returns the triangular number of an interval [a,b]
"""
interval = [i for i in range(b) if i > a]
for num in interval:
a += num
return a+b
def rationability(v:complex) -> complex:
def rationability(v:Complex) -> Complex:
"""
Get the subtractive series of the digits of a float, or that of an integer's reciprocal, measured from its math.ceil value
Dependencies: math.ceil
In: int/float
Out: float
rationability(math.pi)
0.8584073464102069
rationability(3)
@ -315,7 +301,7 @@ def rationability(v:complex) -> complex:
return n
def root(value:complex, power:complex=2) -> complex:
def root(value:Complex, power:Complex=2) -> Complex:
"""
Get the root of a number
@ -327,7 +313,7 @@ def root(value:complex, power:complex=2) -> complex:
return value**(1/power)
def odds(n:int=-1) -> generator:
def odds(n:Integral=-1) -> Generator:
"""
Yield the first n odd numbers, use a negative value for all of them
"""
@ -338,7 +324,7 @@ def odds(n:int=-1) -> generator:
yield m
n -= 1
def evens(n:int=-1) -> generator:
def evens(n:Integral=-1) -> Generator:
"""
Yield the first n even numbers, use a negative value for all of them
"""
@ -349,14 +335,14 @@ def evens(n:int=-1) -> generator:
yield m
n -= 1
def congrues(n:int, modulus:int=6, cls:int=1) -> bool:
def congrues(n:Integral, modulus:Integral=6, cls:Integral=1) -> bool:
"""
Check if n is equal to +-cls modulo modulus
"""
return n % modulus in {cls, modulus-cls}
def mulseq(root:int, base:int=2, terms:int=-1, start:int=0, step:int=1) -> generator:
def mulseq(root:Integral, base:Integral=2, terms:Integral=-1, start:Integral=0, step:Integral=1) -> Generator:
"""
Generate a sequence of multiples of a root and a base. By default it will yield the doubles sequence of the root.
"""

View File

@ -1,71 +0,0 @@
from typing import Any
import os, pickle, re
import dill
from .iteration import deduplicate
from .debug import tryimport
from .files import nameSpacer
def dillsave(obj:Any, filename:str, overwrite:bool=True) -> Any:
"""
Pickles the given object to a file with the given path/name.
If the object cannot be serialized with pickle, we shall use dill instead
Returns the object
"""
if overwrite:
with open(filename, 'wb') as fob:
dill.dump(obj, fob, protocol=dill.HIGHEST_PROTOCOL)
else:
with open(nameSpacer(filename), 'wb') as fob:
dill.dump(obj, fob, protocol=dill.HIGHEST_PROTOCOL)
def save(obj:Any, filename:str, overwrite:bool=True) -> Any:
"""
Pickles the given object to a file with the given path/name.
If the object cannot be serialized with pickle, we shall use dill instead
Returns the object
"""
try:
if overwrite:
with open(filename, 'wb') as fob:
pickle.dump(obj, fob, protocol=pickle.HIGHEST_PROTOCOL)
else:
with open(nameSpacer(filename), 'wb') as fob:
pickle.dump(obj, fob, protocol=pickle.HIGHEST_PROTOCOL)
except pickle.PicklingError:
dillsave(obj, filename, overwrite)
except TypeError:
dillsave(obj, filename, overwrite)
return obj
def load(filename:str) -> Any:
"""
Return unpickled data from a chosen file
If the file doesn't exist it will be created
"""
if os.path.exists(filename):
with open(filename, 'rb') as fob:
var = pickle.load(fob)
return var
else:
x = open(filename)
x.close()
return
def jar(file, duplicates:bool=False, flags:int=re.I):
"""
Consolidate your pickles and eat them, discard the clones by default though
"""
trash = tryimport('send2trash', 'send2trash', 'remove', 'os')
name, ext = os.path.splitext(file)
pattern = f'^{name}|{name}_\d+\.{ext}$'
p = re.compile(pattern)
folder = None if not os.sep in file else os.path.split(file)[0]
os.chdir(folder) if folder else None
matches = deduplicate({f: load(f) for f in os.listdir(folder) if p.search(f, flags)})
results = list(matches.values())[:]
for i in matches: print(i)
[trash(file) for file in matches]
save(results, file)

View File

@ -4,14 +4,14 @@ from functools import lru_cache, reduce
from itertools import tee
from .strings import alphabet
from .types import regurge
from .iteration import regenerator
# from .types import regurge
def shannonEntropy(iterable:Iterable[Any]) -> float:
"""
Returns the Information, or Shannon, Entropy of an iterable
"""
consumable = regurge(iterable)
consumable = list(consumable)
consumable = regenerator(iterable)
weights = {j: freq(j, iterable)/len(consumable) for j in set(consumable)}
return -sigma([val*log(val, 2) for val in weights.values()])
@ -26,7 +26,7 @@ def entropy(iterable:Iterable[Any], base:int=2, mode:str='kbdUS', space:str=None
digits = ''.join(i for i in abc if i.isnumeric() or i=='.' or i=='-')
prob = lambda x,y: freq(x, y)/len(y)
consumable = regurge(iterable)
consumable = regenerator(iterable)
iterates = hasattr(consumable,'__iter__')
@ -60,8 +60,7 @@ def probability(item:Any, iterable:Iterable) -> float:
Returns the quotient of the frequency with which an item
occurs in an iterable by the length of said iterable
"""
consumable = regurge(iterable)
consumable = list(consumable)
consumable = regenerator(iterable)
return freq(item, consumable) / len(consumable)
@ -73,8 +72,7 @@ def freq(element:Any, iterable:Iterable[Any], overlap:bool=False) -> int:
say you're looking for 00 and there's a "000"
you may only get one of what could be two matches
"""
consumable = regurge(iterable)
consumable = list(consumable)
consumable = regenerator(iterable)
if type(element)==type(consumable)==str:
if not overlap:
return len(consumable.split(element))-1
@ -87,8 +85,7 @@ def expectation(iterable:Iterable[complex]) -> complex:
"""
Returns the expectation value of a collection
"""
consumable = regurge(iterable)
consumable = list(consumable)
consumable = regenerator(iterable)
exVal = 0
for i in consumable:
exVal += (i*(freq(i, consumable))/len(consumable))
@ -99,8 +96,7 @@ def mean(iterable:Iterable[complex]) -> complex:
"""
Returns the mean value of a collection
"""
consumable = regurge(iterable)
consumable = list(consumable)
consumable = regenerator(iterable)
meanVal = sum(consumable)
meanVal /= len(consumable)
return meanVal
@ -111,8 +107,8 @@ def median(iterable:Iterable[complex]) -> complex:
Returns the median value of a collection
Will avoid consuming a generator/map/filter
"""
consumable = regurge(iterable)
consumable = sorted(consumable)
# consumable = regurge(iterable)
consumable = regenerator(sorted(iterable))
if len(consumable)%2:
index = round((len(consumable)-1)/2)
middle = consumable[index]
@ -127,8 +123,7 @@ def midpoint(iterable:Iterable[complex]) -> complex:
Returns the midpoint of a collection
Will avoid consuming a generator/map/filter
"""
consumable = regurge(iterable)
consumable = sorted(consumable)
consumable = regenerator(iterable)
return sum(max(consumable), min(consumable))/2
@ -137,8 +132,7 @@ def central_deviation(iterable:Iterable[complex]) -> complex:
Returns the difference between an iterable's midpoint and its median
Will avoid consuming a generator/map/filter
"""
consumable = regurge(iterable)
consumable = (consumable)
consumable = regenerator(iterable)
return abs(median(consumable) - midpoint(consumable))
@ -146,8 +140,7 @@ def geomean(iterable:Iterable[complex]) -> complex:
"""
Returns the geometric mean of a collection
"""
consumable = regurge(iterable)
consumable = list(consumable)
consumable = regenerator(iterable)
geoMean = (sigma(iterable))**(1/len(iterable))
return geoMean
@ -156,8 +149,7 @@ def harmean(iterable:Iterable[complex]) -> complex:
"""
Returns the harmonic mean of a collection
"""
consumable = regurge(iterable)
consumable = list(consumable)
consumable = regenerator(iterable)
assert 0 not in [i for i in consumable], "Input contains a zero, try a different one."
reciprocals = [1/i for i in consumable]
return len(consumable)/sigma(reciprocals)
@ -167,8 +159,7 @@ def popdev(iterable:Iterable[complex]) -> complex:
"""
Returns the population standard deviation of a collection
"""
consumable = regurge(iterable)
consumable = list(consumable)
consumable = regenerator(iterable)
avg = mean(consumable)
v1 = [(i-avg)**2 for i in consumable]
v2 = ((reduce(lambda x, y: x+y, v1))/len(v1))**(1/2)
@ -179,8 +170,7 @@ def samdev(iterable:Iterable[complex]) -> complex:
"""
Returns the sample standard deviation of a collection
"""
consumable = regurge(iterable)
consumable = list(consumable)
consumable = regenerator(iterable)
avg = mean(consumable)
v1 = [(i-avg)**2 for i in consumable]
v2 = ((sigma(v1))/(len(v1)-1))**(1/2)
@ -191,8 +181,7 @@ def popvar(iterable:Iterable[complex]) -> complex:
"""
Returns the population variance for a collection
"""
consumable = regurge(iterable)
consumable = list(consumable)
consumable = regenerator(iterable)
avg = mean(consumable)
deviations = [(i-avg)**2 for i in consumable]
pv = sum(deviations)/len(deviations)
@ -203,8 +192,7 @@ def samvar(iterable:Iterable[complex]) -> complex:
"""
Returns the sample variance for a collection
"""
consumable = regurge(iterable)
consumable = list(consumable)
consumable = regenerator(iterable)
avg = mean(consumable)
v2 = [(i-v1)**2 for i in consumable]
v3 = sigma(v2)/len(v2)

View File

@ -1,15 +1,18 @@
from typing import Iterable, Any
from typing import Iterable, Any, Generator, Callable, Iterator
from itertools import chain
import string, random
import pyperclip
from .types import generator, regurge
from .iteration import regenerator
# from .types import function
from .debug import tipo
def join(iterable:Iterable[Any]=None, sep:str='', head:str='', tail:str='') -> str:
"""
Cast elements of an array to string and concatenate them.
This will consume a generator
This will consume a Generator
Examples:
>>> m3ta.show(
map(
@ -34,7 +37,7 @@ def join(iterable:Iterable[Any]=None, sep:str='', head:str='', tail:str='') -> s
[0, 1, 2]
[0, 1, 2, 3]
"""
if iterable:
if not isinstance(iterable, type(None)):
return head + sep.join(map(str, iterable)) + tail
else:
def wrapper(iterable:Iterable[Any]):
@ -42,7 +45,7 @@ def join(iterable:Iterable[Any]=None, sep:str='', head:str='', tail:str='') -> s
return head + sep.join(iterable) + tail
return wrapper
def ascii(omissions:str='', include:bool=False) -> str:
def ascii(omissions:str='w', include:bool=False) -> str:
"""
Return the ascii character base excluding the given omissions:
"p" -> ' ' + punctuation
@ -58,8 +61,9 @@ def ascii(omissions:str='', include:bool=False) -> str:
"u":string.ascii_uppercase,
"l":string.ascii_lowercase,
"d":string.digits,
"w":string.whitespace,
}
return "".join(d[k] for k in d if k in omissions) if include else "".join(d[k] for k in d if not k in omissions)
return "".join(d[key] for key in d if key in omissions) if include else "".join(d[key] for key in d if not key in omissions)
asciis = kbd = abc = ascii
@ -106,28 +110,34 @@ def monoalphabetic(message:str, shift:int, alphabet:str=kbd(), space:str=None) -
caesar = monoalphabetic
def multisplit(splitters:Iterable[str], target:str=None) -> generator:
"""
Split a string by a the elements of an iterable
>>> splitter = multisplit('word1 word2 word3'.split())
>>> map(splitter, texts)
- OR -
>>> [*multisplit('breakfast', 'green eggs and ham')]
['g', 'n ', 'gg', ' ', 'nd h', 'm']
- OR -
>>> [*multisplit('breakfast'.split(), 'green eggs and ham')]
['green eggs and ham']
"""
def wrapper(target):
nonlocal splitters
splitters = iter(splitters)
result = target.split(next(splitters))
for splitter in splitters:
result = [*chain.from_iterable(i.split(splitter) for i in result)]
yield from filter(None, result)
return wrapper(target) if target else wrapper
def splitall(splitters:Iterable[str], target:str) -> regenerator:
"""
>>> [*splitall('-_.', 'author-file_name.ext')] == 'author file name ext'.split()
True
"""
splitters = iter(splitters)
result = target.split(next(splitters))
for splitter in splitters:
result = [*chain.from_iterable(i.split(splitter) for i in result)]
yield from filter(None, result)
class splitter:
"""
Callable which splits a string by a the elements of an iterable. Ignore any empty strings.
>>> [*splitter('-_.')('author-file_name.ext')] == 'author file name ext'.split()
True
"""
def __init__(self, splitters:Iterable[str]):
self.splitters = regenerator(splitters)
def __call__(self, argument) -> regenerator:
return splitall(self.splitters, argument)
def __repr__(self):
return f"{tipo(self)}{tuple(self.splitters)}"
def multisplit(splitters:Iterable[str], target:str=None) -> (Callable, regenerator):
"""
Wrapper on sl4ng.strings.splitall and sl4ng.strings.splitter
"""
return splitall(splitters, target) if not isinstance(target, type(None)) else splitter(splitters)
def memespace(string:str, spaces:int=1, keep_spaces:bool=False, copy:bool=False):
"""
@ -172,18 +182,36 @@ def sinusize(string:str, copy:bool=False):
"""
mat = [[] for i in range(3)]
for i, j in enumerate(string):
if not i%2:
mat[1].append(j)
if not i % 2:
mat[0].append(' ')
mat[1].append(j)
mat[2].append(' ')
elif not (i%4) - 1:
elif not (i % 4) - 1:
mat[0].append(j)
mat[1].append(' ')
mat[2].append(' ')
else:
mat[2].append(j)
mat[0].append(' ')
mat[1].append(' ')
mat[2].append(j)
out = join([join(line) for line in mat], '\n')
pyperclip.copy(out) if copy else None
return out
def clean_url(url, root=True, protocol='http'):
"""
Remove trailing/leading slashes from a url
params
root
if the url starts at the domain level
protocol
ignored if root=False
what protocol should be added to the start of a url
"""
out = '/'.join(filter(None, url.split('/')))
if root:
if ':/' in out:
out = out.replace(':/', '://')
else:
out = '://'.join((protocol, out))
return out

View File

@ -1,12 +1,12 @@
import time, random, string
import pyperclip, psutil
from .iteration import shuffle
def agora(string:bool=False):
def agora(string:bool=False) -> str | tuple:
"""
Here's the local time, mr wolf!
Here's the local time, Mrs. Wolf!
Dependencies: time
In: string=False
Out: tuple/string(HH:MM:SS)
@ -15,9 +15,9 @@ def agora(string:bool=False):
return now if not string else ':'.join(str(i) for i in now)
def kill(process:str, casefold=True):
def kill(process:str, casefold:bool=True):
"""
Kill all processes of the argued process name
Kill all instances of the argued process name
Dependencies: psutil
In: "process_name.exe"[str],casefold=True[bool]
Out: None
@ -31,15 +31,17 @@ def kill(process:str, casefold=True):
[p.kill() for p in psutil.process_iter() if p.name()==process+('.exe', '')[process.endswith('.exe')]]
def guid(format:tuple=[8, 4, 4, 4, 12], sep:str='-', copy:bool=True) -> str:
def guid(format:tuple=[8, 4, 4, 4, 12], sep:str='-', copy:bool=True, braces:bool=True) -> str:
"""
Generate a GUID for different resources
Dependencies: random,string,pyperclip.copy,m3ta.shuffle
In: format{length of the intervals of the guid you want}
Out: str
"""
chars = ''.join(i for i in shuffle(string.ascii_uppercase + string.digits+string.ascii_lowercase) if i!=sep)
# chars = ''.join(i for i in shuffle(string.ascii_uppercase + string.digits+string.ascii_lowercase) if i!=sep)
chars = ''.join(i for i in shuffle(string.hexdigits) if i != sep)
ranstr = [random.choice(chars) for i in range(sum(format))]
[ranstr.insert(i + sum(format[:i+1]), sep) for i, j in enumerate(format[:-1])]
pyperclip.copy(''.join(ranstr)) if copy else None
return ''.join(ranstr)
result = "{%s}" % (s := ''.join(ranstr)) if braces else s
pyperclip.copy(result) if copy else None
return result

View File

@ -1,217 +1,33 @@
__all__ = 'generator module function SineWave defaultdict'.split()
__all__ = 'generator module function DDict defaultdict'.split()
from collections import defaultdict
from typing import Iterable, Any, Iterator, Sequence
from copy import deepcopy
from itertools import tee, _tee
from typing import Tuple
from warnings import warn
import os
from .iteration import unique, flat
# from .persistance import save, load
from .debug import tipo
generator = type(i for i in range(0))
module = type(os)
function = type(lambda x: x)
class _regen:
@staticmethod
def choose(iterable:Iterable[Any], *indices:Sequence[int]) -> generator:
"""
Yield specific elements from an iterable by index:
>>> [*choose(range(1, 10), 0, 3)]
[1, 4]
>>> [*choose(range(1, 10), (0, 3))]
[1, 4]
"""
indices = [*flatten(indices)]
yielded = []
for i, e in enumerate(iterable):
if i in indices:
yield e
yielded.append(e)
if len(yielded) == len(indices):
break
@staticmethod
def tipo(inpt:Any=type(lambda:0), keep_module:bool=False) -> str:
"""
Return the name of an object's type
Dependencies: None
In: object
Out: str
"""
if keep_module:
return str(type(inpt)).split("'")[1]
return str(type(inpt)).split("'")[1].split('.')[-1]
@staticmethod
def flatten(iterable:Iterable) -> generator:
"""
Flatten a 2d iterable
Example:
>>> list(flatten([[1, 2], [3, 4]]))
[1, 2, 3, 4]
based on:
https://pythonprinciples.com/challenges/Flatten-a-list/
"""
consumable = regurge(iterable)
for i in consumable:
if hasattr(i, '__iter__') or hasattr(i, '__next__'):
for j in i:
yield j
else:
yield i
class regenerator:
class DDict(defaultdict):
"""
A self-replenishing (or non-consumable) iterator. All methods whose return value is iterable return regenerators
:args & kwargs:
Any arguments needed to initialize the generator-type/function. Will not be used unless hasattr(iterable, '__call__') and iterable is not a regenerator.
eg:
>>> x = regurge(i for i in range(2))
>>> [*x]
[0, 1]
>>> bytes(x)
b'\x00\x01'
>>> [*x]
[0, 1]
the standard collections.defaultdict with a less obscure __repr__ method
"""
def __init__(self, iterable, *args, **kwargs):
if hasattr(iterable, '__call__') and not isinstance(iterable, type(self)):
self.active, self._inert = tee(iterable(*args, **kwargs))
else:
self.active, self._inert = tee(iterable)
def __next__(self):
return next(self.active)
def __iter__(self):
self.active, self._inert = tee(self._inert)
return self.active
def __call__(self, *indices:Iterable[int]):
"""
Access particular indices of the underlying iterator
eg
>>> x = regen(range(3))
>>> [*x(1)]
[1]
>>> [*x(1,2)]
[1, 2]
>>> [*x(1,2,3)]
[1, 2]
"""
return type(self)(_regen.choose(self.active, *_regen.flatten(indices)))
def __bool__(self):
"""
Returns True iff. the underlying iterator is non-empty. False otherwise.
"""
tmp, self._inert = tee(self._inert)
try:
next(tmp)
return True
except StopIteration:
return False
def __matmul__(self, other:Iterable):
if hasattr(other, '__iter__'):
return type(self)(product(self, other))
raise TypeError(f'Matrix-multiplication is not defined for "{_regen.tipo(other, True)}". It must have an "__iter__" or "__index__" method.')
def __len__(self):
return sum(1 for i in self)
def __add__(self, other:Any):
"""
Create a new regenerator whose first elements come from self and remaining element(s) is/come-from other.
If other is not iterable it shall be added as the last element.
If you want to add an iterable as a single element, use self.append
eg
>>> x = regenerator(range(2))
>>> y = x + 10
>>> [*y]
[0, 1, 10]
>>> y += x
>>> [*y]
[0, 1, 10, 0, 1]
"""
other = other if hasattr(other, '__iter__') else [other]
return type(self)([*self, *other])
def __radd__(self, other):
"""
Swap the order of __add__
"""
other = other if hasattr(other, '__iter__') else [other]
return type(self)(chain(other, self))
def __mul__(self, value):
"""
Replicate the behaviour of multiplying lists by integers
"""
if hasattr(value, '__int__'):
return type(self)(chain.from_iterable(self for i in range(int(value))))
raise TypeError(f'Multiplication is not defined for "{_regen.tipo(other, True)}". It must have an "__int__"')
def __rmul__(self, value):
"""Commutative multiplication"""
return self.__mul__(value)
def __pow__(self, value):
"""
value-dimensional Cartesian product of self with itself
"""
if hasattr(value, '__int__'):
return type(self)(product(self, repeat=int(value)))
raise TypeError(f'Exponentiation is not defined for {type(other)}. It must have an "__int__" method.')
def scale(self, value):
"""
Multiply every element of self by value. Done in place.
"""
self._inert = (i*value for i in self)
return self
def boost(self, value):
"""
Add value to every element of self. Done in place.
"""
self._inert = (i+value for i in self)
return self
def indices(self, value, shift:int=0):
"""
Similar to a list's index method, except that it returns every index whose element is a match
Works by calling enumerate and selecting at equivalent elements.
:shift:
kwarg for the "enumerate" call.
"""
return type(self)(i for i, e in enumerate(self, shift) if e == value)
def append(self, value):
"""
add "value" as the last element of the array
"""
other = [value]
self._inert = chain(self._inert, other)
return self
def inject(self, value):
"""
If "value" is an iterable: its elements will be added to the end of "self"
Otherwise: it is the same as append
"""
other = value if hasattr(value, '__iter__') else [value]
self._inert = chain(self._inert, *other)
return self
regen = regenerator
def regurge(iterable:Iterable[Any]) -> Iterable:
"""
Secure your generators by passing them through this function in order to produce a copy.
Your generator will be replaced with a tee object.
If your iterable is not a generator the function shall return a copy.deepcopy of it
"""
if issubclass(type(iterable), (map, filter, generator, _tee)):
iterable, consumable = tee(iterable)
else:
consumable = iterable
return consumable
class defaultdict(defaultdict):
""""""
@property
def __rack(self):
pairs = (': '.join(map(repr, pair)) for pair in self.items())
r = ',\n '.join(pairs)
return '{\n ' + r + '\n}'
def __repr__(self):
pairs = (': '.join(map(str, pair)) for pair in self.items())
rack = ',\n '.join(pairs)
return "{ " + rack + "\n}"
return tipo(self) + self.__rack
def __str__(self):
return self.__repr__()
return ' '.join(self.__rack.split())
defaultdict = DDict
class SineWave:
pass