From 51b6dbe5d48f21c69880d1622b868e4410df9c06 Mon Sep 17 00:00:00 2001 From: lda Date: Sat, 3 Oct 2026 03:04:20 +0700 Subject: [PATCH] init --- .gitignore | 10 ++++ .python-version | 1 + README.md | 0 pyproject.toml | 17 +++++++ src/whatcryption/__init__.py | 49 +++++++++++++++++++ src/whatcryption/app.py | 56 +++++++++++++++++++++ src/whatcryption/convert.py | 62 ++++++++++++++++++++++++ src/whatcryption/pairs/bulls.py | 63 ++++++++++++++++++++++++ src/whatcryption/pairs/index_mapper.py | 67 ++++++++++++++++++++++++++ src/whatcryption/pairs/simple.py | 30 ++++++++++++ src/whatcryption/thing.py | 34 +++++++++++++ src/whatcryption/thing2.py | 9 ++++ src/whatcryption/utils.py | 47 ++++++++++++++++++ uv.lock | 8 +++ 14 files changed, 453 insertions(+) create mode 100644 .gitignore create mode 100644 .python-version create mode 100644 README.md create mode 100644 pyproject.toml create mode 100644 src/whatcryption/__init__.py create mode 100644 src/whatcryption/app.py create mode 100644 src/whatcryption/convert.py create mode 100644 src/whatcryption/pairs/bulls.py create mode 100644 src/whatcryption/pairs/index_mapper.py create mode 100644 src/whatcryption/pairs/simple.py create mode 100644 src/whatcryption/thing.py create mode 100644 src/whatcryption/thing2.py create mode 100644 src/whatcryption/utils.py create mode 100644 uv.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..505a3b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..6324d40 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.14 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5aaff77 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "whatcryption" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +authors = [ + { name = "lda", email = "lda@ldlda.com" } +] +requires-python = ">=3.14" +dependencies = [] + +[project.scripts] +whatcryption = "whatcryption:main" + +[build-system] +requires = ["uv_build>=0.12.19,<0.13.0"] +build-backend = "uv_build" diff --git a/src/whatcryption/__init__.py b/src/whatcryption/__init__.py new file mode 100644 index 0000000..e5ea96d --- /dev/null +++ b/src/whatcryption/__init__.py @@ -0,0 +1,49 @@ +from itertools import islice + +from whatcryption.app import App +from whatcryption.pairs.bulls import MORE_bulls +from whatcryption.pairs.index_mapper import index_mapper +from whatcryption.pairs.simple import counter +from whatcryption.thing import thing + + +def main() -> None: + print("whatcryption") + + def app_gen() -> App: + things = [ + thing( + mapper=index_mapper([2, 0, 1, 3]), + counter=counter(4, n, c), + ) + for n, c in [(1, 0), (2, 1), (3, 1), (0, 2)] + ] + + return_mapper = MORE_bulls([1, 0, 3, 2]) + + app = App( + things=things, + return_mapper=return_mapper, + ) + return app + + app = app_gen() + print(f"{app.things = }") + x = [0, 1, 1, 0, 3, 2, 3, 1, 0, 2, 1, 3, 2, 0, 2, 3, 1, 1, 0, 1, 2] + xi = iter(x) + c = [app.run(next(xi))] + print(f"{app.things = }") + print(f"{c = }") + assert app.things[0].can_propagate_rotation() is True + c = [*c, app.run(next(xi))] + assert app.things[0].can_propagate_rotation() is False + assert app.things[1].can_propagate_rotation() is True + print(f"{app.things = }") + c = [*c, *(app.run(i) for i in islice(xi, 3))] + print(f"{app.things = }") + c = [*c, *(app.run(i) for i in xi)] + + app2 = app_gen() + + c2 = [app2.run(i) for i in c] + assert c2 == x, f"c2 = {c2}, x = {x}" diff --git a/src/whatcryption/app.py b/src/whatcryption/app.py new file mode 100644 index 0000000..faa0251 --- /dev/null +++ b/src/whatcryption/app.py @@ -0,0 +1,56 @@ +from whatcryption.pairs.bulls import MORE_bulls, bulls +from whatcryption.thing import thing + + +class App: + def __init__( + self, + things: list[thing], + return_mapper: MORE_bulls, + opt_mapper: bulls | None = None, + ): + superverify(return_mapper, things, opt_mapper) + self.opt_mapper: bulls | None = opt_mapper + self.things: list[thing] = things + self.return_mapper: MORE_bulls = return_mapper + + def __repr__(self) -> str: + return f"App(things={self.things}, return_mapper={self.return_mapper}, opt_mapper={self.opt_mapper})" + + def run(self, i: int) -> int: + if self.opt_mapper: + i = self.opt_mapper.forward(i) + + for this in self.things: + i = this.forward(i) + i = self.return_mapper.forward(i) + for this in reversed(self.things): + # it feels like you put something in the stack then pop it... + i = this.backward(i) + + propagate_rotation = True + for this in self.things: # then IDFK what this shit is + this.rotate_if_(propagate_rotation) + propagate_rotation = propagate_rotation and this.can_propagate_rotation() + if self.opt_mapper: + i = self.opt_mapper.backward(i) + return i + + +def superverify( + return_mapper: MORE_bulls, things: list[thing], opt_mapper: bulls | None +): + "on super g" + if not return_mapper: + raise ValueError("return_mapper must not be None") + if not things: + raise ValueError("things must not be empty") + if opt_mapper is not None and not isinstance(opt_mapper, bulls): + raise ValueError("opt_mapper must be a bulls instance or None") + + if not all(len(this) == len(return_mapper) for this in things): + raise ValueError( + "All things must have the same length, and the same length as return_mapper" + ) + if opt_mapper is not None and len(opt_mapper) != len(return_mapper): + raise ValueError("opt_mapper must have the same length as return_mapper") diff --git a/src/whatcryption/convert.py b/src/whatcryption/convert.py new file mode 100644 index 0000000..2a9fa99 --- /dev/null +++ b/src/whatcryption/convert.py @@ -0,0 +1,62 @@ +from whatcryption.pairs.bulls import bulls + +int2str = "abcdefghijklmnopqrstuvwxyz" +str2int = {c: i for i, c in enumerate(int2str)} + + +def swaps_to_(swaps: list[tuple[int, int]], l: int) -> list[int]: + """ + Convert a list of swaps to a permutation of range(l). + + Arguments: + swaps (list[tuple[int, int]]): a list of swaps + l (int): the length of the permutation + + Returns: + list[int]: a permutation of range(l) + """ + p = list(range(l)) + for a, b in swaps: + p[a], p[b] = p[b], p[a] + return p + + +def verify_swap_list(swaps: list[tuple[int, int]]): + """ + Verify that a list of swaps is valid. + + Arguments: + swaps (list[tuple[int, int]]): a list of swaps + Raises: + ValueError: if there is (a, b) but also (b, c) in the list. + """ + seen = set() + for i, (a, b) in enumerate(swaps): + if a in seen or b in seen: + raise ValueError( + f"Invalid swap list: {swaps} at index {i}, at pair ({a}, {b}); either number has already been swapped in a previous pair." + ) + seen.add(a) + seen.add(b) + + +def swaps_to_BULLS(swaps: list[tuple[int, int]], l: int, cls: type[bulls]) -> list[int]: + """ + Convert a list of swaps to a BULLS permutation of range(l). + + since its bullshit. + + Arguments: + swaps (list[tuple[int, int]]): a list of swaps + l (int): the length of the permutation + cls (type[bulls]): the class of BULLS to use for verification + + Returns: + list[int]: a BULLS permutation of range(l) + """ + if not issubclass(cls, bulls): + raise TypeError(f"cls must be a subclass of bulls, got {cls}") + verify_swap_list(swaps) + p = swaps_to_(swaps, l) + cls.verify(p) + return p diff --git a/src/whatcryption/pairs/bulls.py b/src/whatcryption/pairs/bulls.py new file mode 100644 index 0000000..d7b88dd --- /dev/null +++ b/src/whatcryption/pairs/bulls.py @@ -0,0 +1,63 @@ +"some bullshit" + +from .index_mapper import index_mapper + + +class bulls(index_mapper): + """forward(x) = backward(x) = i[x]. i[i[x]] = x""" + + @staticmethod + def verify(i: list[int]): + super(bulls, bulls).verify(i) + for id, x in enumerate(i): + if i[x] != id: + raise ValueError( + f"bulls: i[i[x]] != x, i[i[{x}]] = i[{i[x]}] = {id} != {x}" + ) + + +class MORE_bulls(bulls): + @staticmethod + def verify(i: list[int]): + super(MORE_bulls, MORE_bulls).verify(i) + for id, x in enumerate(i): + if id == x: + raise ValueError( + f"MORE_bulls: i[x] should not equal x, but i[{x}] == {id}" + ) + + +if __name__ == "__main__": + # test bulls + i = [2, 1, 0, 4, 3] + m = bulls(i) + for x in range(len(i)): + assert m.forward(x) == i[x] + assert m.backward(x) == i[x], "this" + + try: + i = [3, 1, 0, 2] + m = bulls(i) + except ValueError as e: + print(f"caught expected exception: {e}") + # because i[0] = 3, but i[3] = 2 != 0 + + # test MORE_bulls + i = [2, 3, 0, 1] + m = MORE_bulls(i) + for x in range(len(i)): + assert m.forward(x) == i[x] + assert m.backward(x) == i[x] + + try: + i = [3, 1, 4, 0, 2] + m = MORE_bulls(i) + except ValueError as e: + print(f"caught expected exception: {e}") + # because i[1] = 1. + try: + i = [3, 1, 0, 2] + m = MORE_bulls(i) + except ValueError as e: + print(f"caught expected exception: {e}") + # inheritance diff --git a/src/whatcryption/pairs/index_mapper.py b/src/whatcryption/pairs/index_mapper.py new file mode 100644 index 0000000..1e422cb --- /dev/null +++ b/src/whatcryption/pairs/index_mapper.py @@ -0,0 +1,67 @@ +""" +thing <-> index +thing2 <-> index2 + +index <-> index2 + +thing <-> index <-> index2 <-> thing2 +""" + + +class index_mapper: + def __init__(self, i: list[int]): + """ + Arguments: + i (list[int]): a permutation of range(len(i)) + """ + + self.verify(i) + + self._i = [*i] + self._r = {v: k for k, v in enumerate(i)} + self._l = len(i) + + def __repr__(self) -> str: + return f"index_mapper(i={self._i})" + + def __len__(self) -> int: + return self._l + + def forward(self, x: int) -> int: + return self._i[x % self._l] + + def backward(self, x: int) -> int: + return self._r[x % self._l] + + @staticmethod + def verify(i: list[int]): + if not i: + raise ValueError("index_mapper: i must not be empty") + if set(i) != set(range(len(i))): # this also clears uniqueness + raise ValueError("index_mapper: i must be a permutation of range(len(i))") + + +if __name__ == "__main__": + # test index_mapper + i = [4, 2, 0, 1, 3] + m = index_mapper(i) + for x in range(len(i)): + assert m.forward(x) == i[x] + assert m.backward(i[x]) == x + + # usage example + i = [4, 2, 0, 1, 3] + m = index_mapper(i) + l = len(i) + + def _r(o): + print(f"offset = {o}") + for x in range(l): + s = x + o + f = m.forward(s % l) + print(f"forward({x + o} % {l} = {s % l}) = {f}") + b = m.backward(f) + print(f"backward({f}) = {b}") + + _r(2) + _r(3) diff --git a/src/whatcryption/pairs/simple.py b/src/whatcryption/pairs/simple.py new file mode 100644 index 0000000..92256af --- /dev/null +++ b/src/whatcryption/pairs/simple.py @@ -0,0 +1,30 @@ +class counter: + def __init__(self, l: int, n: int, c: int): + self.l = l + self.n = n % l + self.init(c) + + def __len__(self) -> int: + return self.l + + def __repr__(self) -> str: + return f"counter(l={self.l}, n={self.n}, c={self.c})" + + def init(self, c: int): + self.c = c % self.l + + def forward(self, x: int) -> int: + return (x + self.c) % self.l + + def backward(self, x: int) -> int: + return (x - self.c) % self.l + + def can_propagate_rotation(self) -> bool: + return self.c == self.n + + def rotate(self, by: int = 1): + self.init(self.c + by) + + def rotate_if_(self, can_rotate: bool): + if can_rotate: + self.rotate() diff --git a/src/whatcryption/thing.py b/src/whatcryption/thing.py new file mode 100644 index 0000000..129cd2f --- /dev/null +++ b/src/whatcryption/thing.py @@ -0,0 +1,34 @@ +from whatcryption.pairs.index_mapper import index_mapper +from whatcryption.pairs.simple import counter + + +class thing: + "dont switch up the attributes, dont use them after passed. make a new instance" + + def __init__(self, mapper: index_mapper, counter: counter) -> None: + if len(mapper) != counter.l: + raise ValueError( + f"thing: mapper length {len(mapper)} must match counter length {counter.l}" + ) + self.mapper = mapper + self.counter = counter + + def forward(self, x: int) -> int: + x = self.counter.forward(x) + return self.mapper.forward(x) + + def backward(self, x: int) -> int: + x = self.mapper.backward(x) + return self.counter.backward(x) + + def rotate_if_(self, can_rotate: bool): + self.counter.rotate_if_(can_rotate) + + def can_propagate_rotation(self) -> bool: + return self.counter.can_propagate_rotation() + + def __len__(self) -> int: + return len(self.mapper) + + def __repr__(self) -> str: + return f"thing(mapper={self.mapper}, counter={self.counter})" diff --git a/src/whatcryption/thing2.py b/src/whatcryption/thing2.py new file mode 100644 index 0000000..574b327 --- /dev/null +++ b/src/whatcryption/thing2.py @@ -0,0 +1,9 @@ +from typing import Protocol + + +class twowayable(Protocol): + def forward(self, x: int) -> int: + raise NotImplementedError + + def backward(self, x: int) -> int: + raise NotImplementedError diff --git a/src/whatcryption/utils.py b/src/whatcryption/utils.py new file mode 100644 index 0000000..078bf7e --- /dev/null +++ b/src/whatcryption/utils.py @@ -0,0 +1,47 @@ +# thats why elixir is THE language + +from collections.abc import Callable, Sequence + + +def ouoe[T](*c: Callable[[T], T]) -> Callable[[T], T]: + "pipe" + + def f(x: T) -> T: + for c_ in c: + x = c_(x) + return x + + return f + + +def LOL[F, T]( + e: Callable[[F], Callable[[T], T]], + o: Callable[[F], Callable[[T], T]], + f: Sequence[F], + m: Callable[[T], T], +): + """ + for each object in f, apply e(object) to the input, then apply m to the result, then apply o(object) in reverse order to the result of m + """ + + def x(q: T) -> T: + for i in f: + q = e(i)(q) + m(q) + for i in reversed(f): + q = o(i)(q) + return q + + return x + + +if __name__ == "__main__": + + def f(x: int) -> int: + return x + 1 + + def g(x: int) -> int: + return x * 2 + + h = ouoe(f, g) + print(h(3)) # Output: 8 diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..f686d70 --- /dev/null +++ b/uv.lock @@ -0,0 +1,8 @@ +version = 1 +revision = 3 +requires-python = ">=3.14" + +[[package]] +name = "whatcryption" +version = "0.1.0" +source = { editable = "." }