Created
April 11, 2024 23:34
-
-
Save thegamecracks/43e7489d5d760ef7ffea386a3062c60d to your computer and use it in GitHub Desktop.
A collection of some random scripts I wrote on Termux
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
"""Converts resistor parameters into their corrresponding color bands.""" | |
import argparse | |
from decimal import Decimal | |
# list of colors ordered by their corresponding digit (0-9) | |
DIGIT_BANDS = [ | |
"black", | |
"brown", | |
"red", | |
"orange", | |
"yellow", | |
"green", | |
"blue", | |
"purple", | |
"grey", | |
"white", | |
] | |
# maps exponent to corresponding band | |
MULTIPLIER_BANDS = {-2: "silver", -1: "gold", **dict(enumerate(DIGIT_BANDS))} | |
# maps tolerance in hundredths of a percent into corresponding color | |
TOLERANCE_BANDS = { | |
100: "brown", | |
200: "red", | |
50: "green", | |
25: "blue", | |
10: "purple", | |
5: "grey", | |
500: "gold", | |
1000: "silver", | |
} | |
NO_BAND_TOLERANCE = 20.0 | |
def _decimal_frexp10(n: Decimal) -> tuple[list[int], int]: | |
"""Splits a decimal into its fractional part | |
and exponent in base 10. | |
""" | |
# based on https://stackoverflow.com/a/26890567 | |
t = n.normalize().as_tuple() | |
return t.digits, t.exponent | |
def resistor_bands(value: Decimal, n_bands: int, tolerance: float): | |
"""Returns a list of color bands representing the given resistance | |
value and tolerance. | |
Warning: this method does not support the real 6 band standard where | |
the last band represents the temperature coefficient. | |
Note: a tolerance of 0.2 (20%) does not occupy any band. | |
:param value: | |
The resistance to be represented. | |
:param n_bands: | |
The number of bands to use. At least one band has to represent the | |
most significant digit, one band for the multiplier, and one band | |
for the tolerance if it isn't the implied 20%. | |
The more bands given, the more digits that can be displayed. | |
:param tolerance: | |
The tolerance of the resistor as a decimal. | |
:returns: | |
A list of strings stating the color of each band. | |
:raises ValueError: | |
Either the resistance was too small/large to be represented, | |
or there was no corresponding color for the given tolerance. | |
""" | |
# only include tolerance band if it isn't implied 20% | |
i_tol = n_bands | |
if tolerance != NO_BAND_TOLERANCE: | |
i_tol -= 1 | |
n_digits = i_tol - 1 | |
if n_digits < 1: | |
raise ValueError(f"{-n_digits + 1} more bands are required") | |
# convert value into scientific notation | |
digits, exp = _decimal_frexp10(value) | |
excess = n_digits - len(digits) | |
if excess > 0: | |
# pad with leading/trailing zeros if there are an excess number of bands | |
# (trailing zeros are preferred to make exponent zero) | |
trailing = min(max(exp, 0), excess) | |
exp -= trailing | |
leading = excess - trailing | |
digits = (0,) * leading + digits + (0,) * trailing | |
elif excess < 0: | |
# truncate digits and adjust exponent accordingly | |
digits = digits[:n_digits] | |
exp += -excess | |
bands = [] | |
for i in range(n_bands): | |
if i < n_digits: | |
# Value band | |
b = DIGIT_BANDS[digits[i]] | |
elif i == n_digits: | |
# Multiplier band | |
b = MULTIPLIER_BANDS.get(exp) | |
if b is None: | |
min_value = 10 ** min(MULTIPLIER_BANDS) | |
if value < min_value: | |
raise ValueError( | |
"resistances less than 0.01 ohms cannot be represented" | |
) | |
raise ValueError( | |
"resistances greater than 1 teraohms cannot be represented" | |
) | |
else: | |
# Tolerance band | |
b = TOLERANCE_BANDS.get(int(tolerance * 10000)) | |
if b is None: | |
raise ValueError(f"no matching band for tolerance: {tolerance}") | |
bands.append(b) | |
return bands | |
def percent(s: str) -> float: | |
s = s.removesuffix("%") | |
return float(s) / 100 | |
def print_bands(value, n_bands, tolerance): | |
try: | |
bands = resistor_bands(value, n_bands, tolerance) | |
except ValueError as e: | |
print(e) | |
else: | |
print("Resistor:") | |
print(", ".join(bands)) | |
def main(): | |
parser = argparse.ArgumentParser( | |
description=__doc__, | |
formatter_class=argparse.ArgumentDefaultsHelpFormatter, | |
) | |
parser.add_argument( | |
"-t", | |
"--tolerance", | |
type=percent, | |
default="5%", | |
help="the manufacturing tolerance as a percentage (%%)", | |
) | |
parser.add_argument( | |
"-b", | |
"--bands", | |
type=int, | |
default=4, | |
help="the number of color bands", | |
) | |
parser.add_argument( | |
"resistance", | |
type=Decimal, | |
help="the resistance value to convert (ohms)", | |
) | |
args = parser.parse_args() | |
print_bands(args.resistance, args.bands, args.tolerance) | |
if __name__ == "__main__": | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
"""Converts postfix strings into infix. | |
Currently this program only supports binary operators. | |
""" | |
import argparse | |
import textwrap | |
from typing import Any | |
from tree import format_tree | |
def plural(s: str, n: int, suffix: str = "s") -> str: | |
if n != 1: | |
return s + suffix | |
return s | |
def isoperator(token: str) -> bool: | |
"""Determines if a token is an operator.""" | |
return token in "+-*/" | |
def popoperands(stack: list[Any], op: str) -> list[str]: | |
"""Pops the required operands from the stack.""" | |
n = 2 | |
if len(stack) < n: | |
missing = n - len(stack) | |
raise ValueError( | |
"missing {} {} for {!r} operator".format( | |
missing, plural("operand", missing), op | |
) | |
) | |
# NOTE: if implementing unary operators, must somehow implement associativity | |
operands = [stack.pop() for _ in range(n)] | |
operands.reverse() | |
return operands | |
def wrap(s: str) -> str: | |
"""Wraps a string in parentheses.""" | |
return f"({s})" | |
def to_infix_tree(expr: str) -> list[Any]: | |
"""Converts a sequence of tokens in postfix format into a syntax tree.""" | |
stack: list[Any] = [] | |
for token in expr: | |
if isoperator(token): | |
operands = popoperands(stack, token) | |
node = [token] + operands | |
stack.append(node) | |
else: | |
stack.append([token]) | |
if len(stack) > 1: | |
raise ValueError( | |
"missing operator for {!r} and {!r}".format( | |
render_line(stack[0]), render_line(stack[1]) | |
) | |
) | |
elif not stack: | |
return stack | |
return stack[0] | |
def render_tree(infix_tree: list[Any]) -> str: | |
return "\n".join(format_tree(infix_tree)) | |
def render_line(infix_tree: list[Any]) -> str: | |
operator, *operands = infix_tree | |
for i, e in enumerate(operands): | |
if len(e) > 1: | |
operands[i] = render_line(e) | |
else: | |
operands[i] = e[0] | |
return wrap(operands[0] + operator + operands[1]) | |
def main(): | |
# reading from command line | |
parser = argparse.ArgumentParser(description=__doc__) | |
parser.add_argument( | |
"-t", | |
"--tree", | |
action="store_const", | |
dest="render", | |
const=render_tree, | |
default=render_line, | |
help="renders each expression as a syntax tree", | |
) | |
parser.add_argument( | |
"expressions", | |
nargs="*", | |
metavar="expr", | |
help="the postfix expression(s) to evaluate (example: abc-+d/)", | |
) | |
args = parser.parse_args() | |
expressions: list[str] = args.expressions | |
if not expressions: | |
return parser.print_help() | |
# convert each argument | |
for expr in expressions: | |
print(expr) | |
try: | |
infix = to_infix_tree(expr) | |
text = args.render(infix) | |
print(textwrap.indent(text, " ")) | |
except ValueError as e: | |
print(" ", "failed:", e) | |
if __name__ == "__main__": | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
"""Provides tools for formatting tree structures. | |
Trees are represented as nested lists, where the first element is the | |
node's value and each sublist after it is a child node. | |
Example tree: | |
[1, [2, [3], [4]], [5]] | |
""" | |
from typing import Any, Sequence | |
RD = "├── " | |
DO = "│ " | |
RO = "└── " | |
EM = " " | |
def heap_tree(h: Sequence[object]) -> Sequence[Any]: | |
"""Recursively converts a heap list | |
into a tree structure. | |
""" | |
def nodeify(h, i=0): | |
node = [] | |
if i >= length: | |
return node | |
node.append(h[i]) | |
if left := nodeify(h, 2 * i + 1): | |
node.append(left) | |
# right node can only exist when left node exists as well | |
if right := nodeify(h, 2 * i + 2): | |
node.append(right) | |
return node | |
length = len(h) | |
return nodeify(h) | |
def format_tree(t: Sequence[Any]) -> Sequence[str]: | |
"""Returns a list of lines visually | |
representing a tree structure. | |
""" | |
def render(node: Sequence[Any]) -> Sequence[list[str]]: | |
# Rendering the tree can be thought of as creating a table of prefixes, | |
# indentation, and one item per line. Child nodes can be rendered as | |
# subtables and then prefixed with the parent node. | |
table: list[list[str]] = [] | |
for i, child in enumerate(node): | |
if i == 0: | |
# First element is the node's value | |
table.append([str(child)]) | |
continue | |
elif not isinstance(child, list): | |
raise TypeError(f"expected list as child, not {child!r}") | |
# Select appropriate indent sequences | |
if i + 1 == len(node): | |
predent = RO | |
indent = EM | |
else: | |
predent = RD | |
indent = DO | |
# Render child node | |
for ic, line in enumerate(render(child)): | |
line.insert(0, indent if ic else predent) | |
table.append(line) | |
return table | |
final_table = render(t) | |
return ["".join(line) for line in final_table] | |
def print_tree(t): | |
print("\n".join(format_tree(t))) | |
def main(): | |
import argparse, heapq | |
parser = argparse.ArgumentParser() | |
subparsers = parser.add_subparsers(title="sub-commands") | |
parser.set_defaults(func=None) | |
parser_heap = subparsers.add_parser( | |
"heap", | |
help="displays a heap data structure as a tree", | |
) | |
heap_group = parser_heap.add_mutually_exclusive_group() | |
heap_group.add_argument( | |
"-l", | |
"--levels", | |
type=int, | |
default=4, | |
metavar="n", | |
help="the depth of the heap to generate", | |
) | |
heap_group.add_argument( | |
"-n", | |
"--size", | |
type=int, | |
default=None, | |
metavar="n", | |
help="the number of elements to generate", | |
) | |
heap_group.add_argument( | |
"elements", | |
action="extend", | |
nargs="*", | |
default=[], # NOTE: this default is forced | |
help="optional elements to use instead " | |
"of randomized ones; can be integers " | |
"or strings, but not both", | |
) | |
parser_heap.add_argument( | |
"-x", | |
"--max", | |
action="store_const", | |
dest="heapify", | |
const=heapq._heapify_max, | |
default=heapq.heapify, | |
help="creates max-heap instead of a min-heap", | |
) | |
parser_heap.set_defaults(func=main_heap) | |
args = parser.parse_args() | |
if args.func is None: | |
parser.print_help() | |
else: | |
args.func(args) | |
def main_heap(args): | |
import random | |
no_negatives = "{} cannot be negative" | |
# Interpreting arguments | |
if args.elements: | |
h: Sequence[str | int] = args.elements | |
# Convert to integers if possible | |
nums: list[int] = [] | |
err_mixed = ValueError("elements cannot have strings and integers mixed") | |
for i, e in enumerate(h): | |
try: | |
n = int(e) | |
except ValueError: | |
if nums: | |
raise err_mixed from None | |
else: | |
if i > 0 and not nums: | |
raise err_mixed | |
nums.append(n) | |
if nums: | |
h = nums | |
elif args.size is not None: | |
size = args.size | |
if size < 0: | |
raise ValueError(no_negatives.format("size")) | |
h = list(range(size)) | |
random.shuffle(h) | |
else: | |
levels = args.levels | |
if levels < 0: | |
raise ValueError(no_negatives.format("levels")) | |
size = sum(2**n for n in range(levels)) | |
h = list(range(size)) | |
random.shuffle(h) | |
args.heapify(h) | |
tree = heap_tree(h) | |
print("Heap:") | |
print(h) | |
print("Tree:") | |
print_tree(tree) | |
if __name__ == "__main__": | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import functools | |
import inspect | |
import re | |
DUNDER_PATTERN = re.compile(r"__.+__") | |
def typecheck(use_default_type=True): | |
"""Returns a decorator that applies type checking | |
to given arguments based on the function's | |
annotations. | |
:param use_default_type: | |
If there are parameters with only a default | |
value, the type of that value will be used | |
in-place of missing annotations. | |
""" | |
def _check_type(param, value): | |
if param.annotation is not param.empty: | |
expected = param.annotation | |
elif use_default_type and param.default is not param.empty: | |
expected = type(param.default) | |
else: | |
return | |
# PEP 563 support (may be a string literal) | |
if isinstance(expected, str): | |
expected = eval(expected, func.__globals__) | |
# NOTE: this type checking does not handle | |
# many cases like generics | |
if not isinstance(value, expected): | |
raise TypeError( | |
'expected type {} for parameter "{}", got ' | |
"type {} instead".format( | |
expected.__name__, param.name, type(value).__name__ | |
) | |
) | |
def decorator(func): | |
# Unwrap any other decorators | |
while wrapped := getattr(func, "__wrapped__", None): | |
func = wrapped | |
signature = inspect.signature(func) | |
@functools.wraps(func) | |
def wrapper(*args, **kwargs): | |
bound = signature.bind(*args, **kwargs) | |
# Typecheck each argument | |
for name, value in bound.arguments.items(): | |
param = signature.parameters[name] | |
_check_type(param, value) | |
return func(*args, **kwargs) | |
return wrapper | |
return decorator | |
class MethodTypeCheckerMeta(type): | |
"""A metaclass that adds runtime type checking | |
to its methods. | |
Extra keyword arguments passed to the subclass | |
are routed to the typecheck() decorator. | |
:param check_dunder: | |
If True, dunder methods will also be | |
type checked. | |
""" | |
def __new__(cls, name, bases, namespace, check_dunder=False, **kwargs): | |
# look for methods and apply typechecking | |
for k, v in namespace.items(): | |
if not inspect.isroutine(v): | |
continue | |
elif not check_dunder and DUNDER_PATTERN.fullmatch(k): | |
continue | |
namespace[k] = typecheck(**kwargs)(v) | |
return super().__new__(cls, name, bases, namespace) | |
class MethodTypeChecker(metaclass=MethodTypeCheckerMeta): | |
pass | |
class Foo(MethodTypeChecker, check_dunder=True): | |
def __init__(self, n=0): | |
self.n = n | |
def __call__(self, m: int): | |
return self.n + m | |
def mask(self, m: int): | |
return self.n & m | |
@classmethod | |
def from_string(cls, s: str): | |
return cls(int(s)) | |
x = Foo(45) | |
print(x(4)) | |
print(x.mask(0b00001111)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment