diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c63cdae --- /dev/null +++ b/.gitignore @@ -0,0 +1,180 @@ +# Dom's manually added things +bad apple/factorio-draftsman +typing-puzzles/ +heartbound OCR/ +signals/ + +# 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 + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__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/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc \ No newline at end of file diff --git a/1-2-3-digit-addend-662288000.py b/1-2-3-digit-addend-662288000.py new file mode 100644 index 0000000..5914d0b --- /dev/null +++ b/1-2-3-digit-addend-662288000.py @@ -0,0 +1,152 @@ +from functools import cache +from collections import Counter, defaultdict + +# pool = Counter(map(int, "000226688")) +# counted_digits = (0, 2, 6, 8) +# counts = (3, 2, 2, 2) + +# 268 + +# 208 +# 60 +# 0 + + +@cache +def choose_n_unique(counts, n): + if sum(counts) < n: + raise "you done fucked up" + if sum(counts) == n: + return set([counts]) + out = set([]) + for i, count in enumerate(counts): + if count <= 0: + continue + out.update( + choose_n_unique(counts[:i] + (count - 1,) + counts[i + 1 :], n) + ) + return out + + +def sub(a, b): # a-b + return tuple(x - y for x, y in zip(a, b)) + + +# print(choose_n_unique(counts, 3)) +# print(find_sums(counts)) +# print(possible_sums((3, 2, 2, 2))) + + +def has_len_digit(tup, length): + for t in tup: + if len(str(t)) == length: + return True + return False + + +# tup_0, tup_1 = (826,), (0, 0, 6, 820) + +# print(has_len_digit(tup_0, 2)) +# print(has_len_digit(tup_1, 2)) +# exit() + + +def find_solutions(counted_digits, counts): + + @cache + def possible_sums(counts): # sum -> set(tuple(addend)) + if sum(counts) == 0: + return {0: set()} + if sum(counts) == 1: + for i, count in enumerate(counts): + if count == 0: + continue + digit = counted_digits[i] + out = {digit: set([(digit,)])} + return out + + out = defaultdict(set) + + for i, count in enumerate(counts): + if count <= 0: + continue + digit = counted_digits[i] + sub = possible_sums(counts[:i] + (count - 1,) + counts[i + 1 :]) + for s, tups in sub.items(): + for tup in tups: + out[s + digit].add(tuple(sorted((digit,) + tup))) + # try appending the digit to all sub subms + for tups in sub.values(): + for tup in tups: + for j, val in enumerate(tup): + if j > 0 and val == tup[j - 1]: + continue + new_tup = tuple( + sorted((val * 10 + digit,) + tup[:j] + tup[j + 1 :]) + ) + out[sum(new_tup)].add(new_tup) + return out + + out = [] + for counts_0 in choose_n_unique(counts, 3): + counts_1 = sub(counts, counts_0) + # print(counts_1) + possible_0 = possible_sums(counts_0) + possible_1 = possible_sums(counts_1) + # print(possible_0.get(268, "nope")) + # print(possible_1.get(268, "nope")) + # print() + for total in possible_0.keys() & possible_1.keys(): + tups_0 = possible_0[total] + tups_1 = possible_1[total] + for tup_0 in tups_0: + for tup_1 in tups_1: + if not (has_len_digit(tup_0, 1) or has_len_digit(tup_1, 1)): + continue + if not (has_len_digit(tup_0, 2) or has_len_digit(tup_1, 2)): + continue + if not (has_len_digit(tup_0, 3) or has_len_digit(tup_1, 3)): + continue + out.append((tup_0, tup_1)) + return out + + +# print(find_solutions(counted_digits=(0, 2, 6, 8), counts=(3, 2, 2, 2))) +import random + +rng = random.Random(4) +# best = 100 +# for i in range(1000): + +# # rng.randint() +# def r(radius): +# return rng.randint(-radius, radius) + +# candidate = find_solutions( +# counted_digits=(1 + r(1), 2 + r(1), 6 + r(1), 8 + r(1)), +# counts=(3 + r(1), 2 + r(1), 2 + r(1), 2 + r(1)), +# ) +# if not candidate: +# continue + +# if len(candidate) <= best: +# best = len(candidate) +# print("yay") +# for tup in candidate: +# print(" ", tup) + + +def counted_digits_and_counts_from_solution(solution): + s = str(solution) + for to_remove in "(), ": + s = s.replace(to_remove, "") + print(s) + print("".join(sorted(s))) + c = Counter(s) + counted_digits = tuple(int(k) for k in sorted(c)) + counts = tuple(int(c[k]) for k in sorted(c)) + return counted_digits, counts + + +print(counted_digits_and_counts_from_solution("((261,), (7, 27, 227)))")) +print(find_solutions((1, 2, 6, 7), (1, 4, 1, 3))) diff --git a/12345_combination_sum.py b/12345_combination_sum.py new file mode 100644 index 0000000..1e130cf --- /dev/null +++ b/12345_combination_sum.py @@ -0,0 +1,10 @@ +import math +import itertools + +# print(list)) + +start_digits = [1,2,3,4,5] + +print(sum(int(''.join(str(digit) for digit in digits)) for digits in itertools.permutations(start_digits))) + +print(math.factorial(len(start_digits)) * (sum(start_digits)/len(start_digits)) * 11111) diff --git a/3b1b cessboard encoding puzzle.py b/3b1b cessboard encoding puzzle.py new file mode 100644 index 0000000..f285b12 --- /dev/null +++ b/3b1b cessboard encoding puzzle.py @@ -0,0 +1,180 @@ +# https://youtu.be/wTJI_WuZSwE + +from functools import reduce, cache +from typing import List, Tuple, Callable, Iterable +from math import sqrt, log2 +from random import Random + +def get_flip_pos_2x2(board: List[int], key: int) -> int: + assert all(x in [0,1] for x in board) + assert len(board) == 4 + s = sum(board) + if s==0: + return key + if s==1: + pos = board.index(1) + if key == 0: + return pos # empty + if key == 1: + return [1,0,3,2][pos] # horizontal + if key == 2: + return [2,3,0,1][pos] # vertical + if key == 3: + return [3,2,1,0][pos] # diagonal + assert False + if s==2: + # ensure ``sum(board) in [1,3]``, pointing to key + if board[key] == 0: # sum(flipped board) == 3 + possibilities = [i for i in range(len(board)) if i != key and board[i]==0] + else: # sum(flipped board) == 3 + possibilities = [i for i in range(len(board)) if i != key and board[i]==1] + assert len(possibilities) == 1 + return possibilities[0] + if s==3: + return get_flip_pos_2x2(all_flipped(board), key) + if s==4: + return get_flip_pos_2x2(all_flipped(board), key) + assert False + +def all_flipped(board: List[int]) -> List[int]: + return [1-x for x in board] + +def guess_key_2x2(board: List[int]) -> int: + s = sum(board) + if s==0: + return 0 + if s==1: + return board.index(1) + if s==2: + if board==[1,1,0,0] or board==[0,0,1,1]: + return 1 # horizontal + if board==[1,0,1,0] or board==[0,1,0,1]: + return 2 # vertical + if board==[1,0,0,1] or board==[0,1,1,0]: + return 3 # diagonal + if s==3: + return guess_key_2x2(all_flipped(board)) + if s==4: + return guess_key_2x2(all_flipped(board)) + assert False + + +def flip_mutate(board: List[int], flip_pos: int): + board[flip_pos] = 1-board[flip_pos] + +def flip_copy(board: List[int], flip_pos: int) -> List[int]: + board = board[:] + flip_mutate(board, flip_pos) + return board + +def print_board(board: List[int]): + sidelength = sqrt(len(board)) + assert sidelength == int(sidelength) + sidelength = int(sidelength) + for row_i in range(sidelength): + print(''.join(str(x) for x in board[row_i*sidelength:(row_i+1)*sidelength])) + +def generate_boards(board_squre_count: int, sample_count: int) -> Iterable[int]: + if sample_count >= 2**board_squre_count: + print('doing exhaustive verification.') + yield from range(2**board_squre_count) + print(f'not doing exhaustive. need sample_count>={2**board_squre_count}, got sample_count={sample_count}') + rng = Random(4) + for i in range(sample_count): + yield rng.getrandbits(board_squre_count) + + +def validate(board_squre_count, get_flip_pos: Callable, guess_key: Callable, sample_count=2**16) -> bool: + success_boards = set() + + for sample_i, board_int in enumerate(generate_boards(board_squre_count, sample_count)): + if board_int in success_boards: + continue + + if sample_i %100 == 0: + print(sample_i/sample_count) + for key in range(board_squre_count): + # potential optimization: could re-use board and unflip guess + board = [(board_int>>i)&1 for i in reversed(range(board_squre_count))] + + flip_pos = get_flip_pos(board, key) + flip_mutate(board, flip_pos) + guess = guess_key(board) + if guess != key: + print(f'Found counter example. Got guess {guess}, want {key}') + flip_mutate(board, flip_pos) + print('before flip:') + print_board(board) + flip_mutate(board, flip_pos) + print('after flip:') + print_board(board) + return False + + success_boards.add(board_int) + if len(success_boards) == 2**board_squre_count: + # All boards have been validated + print('exhaustive, yay') + return True + print(f'non exhaustive finish. covered {len(success_boards)} cases ({len(success_boards) / 2**board_squre_count:%} of cases) successfully') + return True + +# validate(4, get_flip_pos_2x2, guess_key_2x2) + +# Scaler possibilities: +# Total key index % 4 if we split into 4-squares +# is each reg +# XOR each 4x4 section into a bit to determine which quadrant +# layer each 4x4 section with XOR, to determine which quadrant in the above 4x4 +# layer each 2x2 section with XOR, to determine which quadrant in 2x2 + +def guess_key_pow2board(board: List[int]) -> int: + if len(board) == 4: + return guess_key_2x2(board) + + sidelength = sqrt(len(board)) + assert sidelength == int(sidelength) + sidelength = int(sidelength) + assert log2(sidelength) == int(log2(sidelength)) + quarter = len(board) / 4 + assert quarter == int(quarter) + quarter = int(quarter) + + # Not actually quadrants but good enough + quadrants = [ board[i*quarter:(i+1)*quarter] for i in range(0,4) ] + + quarter_i = guess_key_2x2([ reduce(lambda a,b: a^b, q) for q in quadrants ]) + sub_i = guess_key_pow2board([a^b^c^d for a,c,b,d in zip(*quadrants)]) + return quarter * quarter_i + sub_i + +def make_get_flip_pos(guess_key: Callable) -> Callable: + + # Flip the thing that works + def get_flip_pos(board, key) -> int: + possibilities = [ + flip_pos for flip_pos in range(len(board)) + if guess_key(flip_copy(board, flip_pos)) == key + ] + assert len(possibilities) == 1, f"possibilities={possibilities} want length 1" + return possibilities[0] + return get_flip_pos + +def get_flip_pos_pow2board(board: List[int], key: int) -> int: + if len(board) == 4: + return get_flip_pos_2x2(board, key) + quarter = len(board) / 4 + assert quarter == int(quarter) + quarter = int(quarter) + + # Not actually quadrants but good enough + quadrants = [ board[i*quarter:(i+1)*quarter] for i in range(0,4) ] + quarter_i = get_flip_pos_2x2([ reduce(lambda a,b: a^b, q) for q in quadrants ], key//4) + sub_i = get_flip_pos_pow2board([a^b^c^d for a,c,b,d in zip(*quadrants)], key%4) + return quarter * quarter_i + sub_i + + +# validate(4, make_get_flip_pos(guess_key_2x2), guess_key_2x2) +# validate(16, make_get_flip_pos(guess_key_pow2board), guess_key_pow2board) +# validate(16, get_flip_pos_pow2board, guess_key_pow2board) +validate(64, get_flip_pos_pow2board, guess_key_pow2board) +# validate(64, make_get_flip_pos(guess_key_pow2board), guess_key_pow2board) + diff --git a/Can you solve the rogue submarine riddle.py b/Can you solve the rogue submarine riddle.py new file mode 100644 index 0000000..2104fa9 --- /dev/null +++ b/Can you solve the rogue submarine riddle.py @@ -0,0 +1,86 @@ +from collections import Counter +from pprint import pprint + +basis = [1,2,3,4,5,6] + +def get_all_subsets(l): + if l == []: + return [[]] + subs = get_all_subsets(l[1:]) + return subs + [[l[0]] + el for el in subs] + +all_subsets = get_all_subsets(basis) +all_subsets = sorted([sub for sub in all_subsets if len(sub) >= 2]) +# all_subsets = sorted([sub for sub in all_subsets if len(sub) >= 2]) +# all_subsets = sorted([sub for sub in all_subsets if sum(sub) < 9]) + +def product(sub): + if len(sub) == 0: + return 1 + return sub[0] * product(sub[1:]) + +def sort_keys(counter: Counter) -> Counter: + out = Counter() + for key in sorted(counter.keys()): + out[key] = counter[key] + return out + +def print_sorted_by_key(counter: Counter) -> Counter: + print('{') + for key in sorted(counter.keys()): + print(f' {key}: {counter[key]},') + print('}') + + +sum_counter = Counter(sum(sub) for sub in all_subsets) +product_counter = Counter(product(sub) for sub in all_subsets) +product_counter_unique_sum = Counter(product(sub) for sub in all_subsets if sum_counter[sum(sub)] > 1) + +#debug +print_sorted_by_key(sum_counter) +print_sorted_by_key(product_counter) + +print(sum([3,4,5,6])) +print(product([3,4,5,6])) +print('a', [(sub, sum(sub)) for sub in all_subsets if sum(sub)==18]) +print('b', [(sub, product(sub)) for sub in all_subsets if product(sub)==360]) +print(sum([3,4,5,6])) +print(sum([1,3,4,5,6])) + + +print('xxx') + +# solve it one way +for sub in all_subsets: + if sum_counter[sum(sub)] > 1 and product_counter_unique_sum[product(sub)] == 1: + print(sub, sum(sub), product(sub)) + +print('xxx') + +# solve it one way +for sub in all_subsets: + if sum_counter[sum(sub)] > 1 and product_counter[product(sub)] == 1: + print(sub, sum(sub), product(sub)) + +print('yyy') + +# solve it another way +for answer_sum in range(100): + if sum_counter[answer_sum] == 0: + continue + has_some_determined = False + has_some_undetermined = False + for sub in all_subsets: + if not sum(sub) == answer_sum: + continue + if product_counter[product(sub)] > 1: + has_some_undetermined += True + if product_counter[product(sub)] == 1: + has_some_determined += True + + if not (has_some_determined and has_some_undetermined): + continue + + print(answer_sum, has_some_determined, has_some_undetermined) + # print(sub, sum(sub), product(sub)) + # print() diff --git a/Dongle's Difficult Dilemma.py b/Dongle's Difficult Dilemma.py new file mode 100644 index 0000000..4bd9557 --- /dev/null +++ b/Dongle's Difficult Dilemma.py @@ -0,0 +1,148 @@ +# https://www.youtube.com/watch?v=8xXslshomOs +from typing import Dict, Optional +from dataclasses import dataclass + + +@dataclass(frozen=True, order=True) +class State: + gold_a: int + gold_b: int + silver_a: int + silver_b: int + gems_a: int + gems_b: int + equal_priority: bool # 0 means "a", which is the protagonist + + +winner_cache : Dict[State, int] = {} + +def resolve_auction(state: State, bet_a: int, bet_b: int) -> Optional[State]: + if bet_b > state.gems_b: + return None + assert bet_a <= state.gems_a + + equal_priority = state.equal_priority + if bet_a > bet_b: + bet_winner = 0 + elif bet_a < bet_b: + bet_winner = 1 + else: + bet_winner = int(equal_priority) + equal_priority = 1 - equal_priority + + doing_golds = state.gold_a + state.gold_b <3 + return State( + gold_a=state.gold_a + 1 if doing_golds and bet_winner == 0 else state.gold_a, + gold_b=state.gold_b + 1 if doing_golds and bet_winner == 1 else state.gold_b, + silver_a=state.silver_a + 1 if not doing_golds and bet_winner == 0 else state.silver_a, + silver_b=state.silver_b + 1 if not doing_golds and bet_winner == 1 else state.silver_b, + gems_a=state.gems_a - bet_a if bet_winner == 0 else state.gems_a, + gems_b=state.gems_b - bet_b if bet_winner == 1 else state.gems_b, + equal_priority=equal_priority, + ) + + +next_print = 1 + +def get_winner(state: State) -> int: + if state.gold_a >= 3: + winner_cache[state] = 0; return 0, (state,) + if state.gold_b >= 3: + winner_cache[state] = 1; return 1, (state,) + if state.silver_a >= 3: + winner_cache[state] = 0; return 0, (state,) + if state.silver_b >= 3: + winner_cache[state] = 1; return 1, (state,) + winner = winner_cache.get(state) + if winner is not None: + return winner, (state,) + + + doing_golds = state.gold_a + state.gold_b <3 + child_states = [] + path = tuple() + + # minimax + winner_a = 1 + for bet_a in range(state.gems_a+1): + (winner_b, path) = max([ + get_winner(child) + for child in + [resolve_auction(state, bet_a, bet_b) for bet_b in {0, bet_a, bet_a+1}] + if child + ]) + winner_a = min(winner_a, winner_b) + if winner_a == 0: + path = (state, bet_a) + path + # break + + winner_cache[state] = winner_a + global next_print + if len(winner_cache) > next_print: + print(len(winner_cache)) + next_print *= 2 + + return winner_a, path + +initial_state = State( + gold_a=1, + gold_b=0, + silver_a=0, + silver_b=0, + gems_a=75, + gems_b=99, + equal_priority=0, +) +import pickle +cache_file = __file__+".winner_cache.pickle" + +# print(get_winner(initial_state)) +# with open(cache_file, 'wb') as f: +# pickle.dump(winner_cache, f) + +with open(cache_file, 'rb') as f: + winner_cache = pickle.load(f) + +# print(winner_cache) + +def find_children(state): + child_states = [] + for bet_a in range(state.gems_a+1): + responses = [resolve_auction(state, bet_a, bet_b) for bet_b in {0, bet_a, bet_a+1}] + if any(winner_cache.get(res) == 1 for res in responses): + responses = [res for res in responses if winner_cache.get(res) == 1] + child_states.extend(responses) + return [state for state in child_states if state is not None] + +def find_winning_children(state): + return [state for state in find_children(state) if winner_cache.get(state) == 0] + +def print_bets(state0, state1): + for bet_a in range(state0.gems_a+1): + for bet_b in {0, bet_a, bet_a+1}: + res = resolve_auction(state0, bet_a, bet_b) + if res == state1: + print(f'bet_a={bet_a} bet_b={bet_b}') + + +def get_winning_path(state: State): + get_winning_option_fraction = lambda child: len(find_winning_children(child)) / max(1, len(find_children(child))) + print() + print(len(find_winning_children(state)), "/", len(find_children(state))) + print(state) + + + winner = winner_cache.get(state) + + child_states = find_winning_children(state) + if not child_states: + print('fin') + return + + choice = min(child_states, key=get_winning_option_fraction) + print_bets(state, choice) + # for child in child_states: + # print(" " + (">" if child is choice else " ") + " " + repr(child)) + get_winning_path(choice) + +get_winning_path(initial_state) diff --git a/Dongle's Difficult Dilemma.py.winner_cache.pickle b/Dongle's Difficult Dilemma.py.winner_cache.pickle new file mode 100644 index 0000000..e49bc4d Binary files /dev/null and b/Dongle's Difficult Dilemma.py.winner_cache.pickle differ diff --git a/bad apple/bad_apple.py b/bad apple/bad_apple.py new file mode 100644 index 0000000..2d79803 --- /dev/null +++ b/bad apple/bad_apple.py @@ -0,0 +1,77 @@ +import pyperclip + +from draftsman.blueprintable import Blueprint, BlueprintBook +from draftsman.constants import Direction +from draftsman.entity import ConstantCombinator + +# from draftsman.data import items +import draftsman.data.signals +from PIL import Image + +blueprint = Blueprint() +blueprint.label = "bad apple" +blueprint.description = "bad apple." +blueprint.version = (1, 0) # 1.0 + +frame = Image.open("frame.png") +threshold = 40 +signals = list(draftsman.data.signals.raw)[3:] # skip the three special signals + + +pixels = frame.load() + +# Create the combinator for the single frame +frame_combinator = ConstantCombinator() +frame_combinator.tile_position = (-1, 0) +for x in range(frame.width): + column_mask = 0 + for y in range(frame.height): + if pixels[x, y][0] > threshold: + column_mask |= 1 << y + frame_combinator.set_signal(index=x, signal=signals[x], count=column_mask) +blueprint.entities.append(frame_combinator) + +pyperclip.copy(blueprint.to_string()) + +# Code for creating the column filters below. + +# for x in range(frame.width): +# blueprint.entities.append( # this is our y-combinator :^) +# "arithmetic-combinator", +# id=f"filter-{x}", +# tile_position=[x, 2], +# direction=Direction.NORTH, +# control_behavior={ +# "arithmetic_conditions": { +# "first_signal": signals[x], +# "operation": "AND", +# "second_signal": {"type": "virtual", "name": "signal-each"}, +# "output_signal": {"type": "virtual", "name": "signal-each"}, +# } +# }, +# ) + + +# arithmetic_conditions = { +# "first_signal": {"type": "fluid", "name": "steam"}, +# "second_signal": {"type": "virtual", "name": "signal-each"}, +# "operation": "AND", +# "output_signal": {"type": "virtual", "name": "signal-each"}, +# "first_signal_networks": {"red": True, "green": False}, +# "second_signal_networks": {"red": False, "green": True}, +# } + +# from draftsman.utils import JSON_to_string + +# # directly hack in these conditions because this is a new feature and we don't care about the validation bullshit +# d = blueprint.to_dict() +# for entity in d["blueprint"]["entities"]: +# entity["control_behavior"]["arithmetic_conditions"][ +# "first_signal_networks" +# ] = {"red": True, "green": False} +# entity["control_behavior"]["arithmetic_conditions"][ +# "second_signal_networks" +# ] = {"red": False, "green": True} + +# s = JSON_to_string(d) +# pyperclip.copy(str(s)) diff --git a/bad apple/frame.png b/bad apple/frame.png new file mode 100644 index 0000000..ee6cf7c Binary files /dev/null and b/bad apple/frame.png differ diff --git a/beltomatic.py b/beltomatic.py new file mode 100644 index 0000000..5733cc0 --- /dev/null +++ b/beltomatic.py @@ -0,0 +1,60 @@ +# from math import abs +from collections import defaultdict + +max_num = 16 +initial_nums = list(range(1, max_num + 1)) +initial_nums.remove(10) + +# end number -> list of steps to get here which are +# (prev_number, operation description) + +target = 7387 + +upper_bound = target * max_num +ops = [] +for n in initial_nums: + ops.append((lambda x, n=n: x + n, f"+{n}")) + ops.append((lambda x, n=n: abs(x - n), f"-{n}")) + ops.append((lambda x, n=n: x * n, f"*{n}")) + ops.append((lambda x, n=n: x // n if x and x % n == 0 else x, f"/{n}")) + ops.append((lambda x, n=n: x**n if x**2 < upper_bound else x, f"**{n}")) + + +seen = {n: [] for n in initial_nums} +recent = seen.keys() + +while target not in seen: + news = defaultdict(list) + for n in recent: + for op, description in ops: + new = op(n) + if new > upper_bound: + continue + if new in seen: + continue + news[new].append((n, description)) + seen.update(news) + recent = sorted(news.keys()) + if not news: + assert False + + print(f"progress: {len(seen)=}") + + +pickers = [ + lambda l: l[0], + lambda l: l[len(l) // 2], + lambda l: l[-1], +] +for picker in pickers: + steps = [target] + picks = [] + while prevs := seen[steps[-1]]: + pick = picker(prevs) + picks.append(pick) + steps.append(pick[0]) + + print(f"{steps[-1]:>3}", end=" ") + for pick in reversed(picks): + print(f"{pick[1]:>4}", end=" ") + print(f" = {target}") diff --git a/cheating royal riddle.py b/cheating royal riddle.py new file mode 100644 index 0000000..429d9c3 --- /dev/null +++ b/cheating royal riddle.py @@ -0,0 +1,37 @@ +# https://www.youtube.com/watch?v=hk9c7sJ08Bg + +from collections import defaultdict + +distribution = {0:1} + + +def print_sorted_by_key(counter): + print('{') + for key in sorted(counter.keys()): + print(f' {key}: {counter[key]},') + print('}') + +def convolve(distribution, equally_weighted_dice): + d2 = defaultdict(float) + for k,v in distribution.items(): + for red in equally_weighted_dice: + d2[k+red] += v/len(equally_weighted_dice) + return d2 + + +for _ in range(20): + distribution = convolve(distribution, [2,7,7,12,12, 17]) + distribution = convolve(distribution, [3,8,8,13,13, 18]) + + +# print_sorted_by_key(distribution) + +cumulative = defaultdict(float) +acc = 0 +for k in sorted(distribution): + acc += distribution[k] + cumulative[k] = acc + +# print_sorted_by_key(distribution) +# print(sum(distribution.values())) +print_sorted_by_key(cumulative) diff --git a/chef puzzle.py b/chef puzzle.py new file mode 100644 index 0000000..9e3e8f8 --- /dev/null +++ b/chef puzzle.py @@ -0,0 +1,55 @@ +# https://www.youtube.com/watch?v=HyRjuPP9S3o + +from collections import Counter + +max_val = 1300 + +possibilities = list(range(13, max_val+1)) + +# exclusions = {64, 729} + + +perfect_squares = {x*x for x in range(100)} +perfect_cubes = {x*x*x for x in range(100) if x*x*x <= max_val} + + + +def less_than_500(x): + return x < 500 + +def is_perfect_square(x): + return x in perfect_squares + +def is_perfect_cube(x): + return x in perfect_cubes + +def second_digit_1(x): + return str(x)[1] == "1" + + +# print([x for x in possibilities if is_perfect_cube(x) and is_perfect_square(x)]) + +# invert invert second digit is a 1 +conditions = [less_than_500, is_perfect_square, is_perfect_cube] +for mask in range(2**3): + pos = set(possibilities) + for x in possibilities: + for i, condition in enumerate(conditions): + if not (condition(x) ^ (mask & (1 << i))): + pos.remove(x) + break + + if 1 in Counter(second_digit_1(x) for x in pos).values(): + print(mask, pos) + + # print(mask, pos) + + +# What was said +# it is >500 (lie) +# it + +# truth +# less than 500 +# perfect square +# perfect diff --git a/cnf_or_tuples.py b/cnf_or_tuples.py new file mode 100644 index 0000000..81a61f1 --- /dev/null +++ b/cnf_or_tuples.py @@ -0,0 +1,114 @@ +################ solver +from itertools import chain, combinations +from functools import reduce + + +def powerset(iterable): + "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)" + s = list(iterable) + return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1)) + + +def check_solution(selection, operator): + return reduce( + lambda a, b: tuple(operator(x, y) for x, y in zip(a, b)), selection + ) + + +def tuple_selector(tuples, operator): + solution_count = 0 + for selection in powerset(tuples): + if not selection: + continue + if all(check_solution(selection, operator)): + print(f"found a solution! {selection}") + solution_count += 1 + print(f"{solution_count=}") + + +################# problem statement + + +def operator_with_secret(a: int, b: int) -> int: + either = {a, b} + if 1 in either: + return a + b % 2 + # if 0 in either: + # return 0 + + # final element that must be included + if 2 in either: + # The x's represent free choices. + # 0bxxxxxxx00 + secret = 0b010110100 + return 3 if secret in either else 0 + + # build your own number kit + return a | b + + +tuple_selector( + [ + # make a choice to set a bit in the first number + (0b000000100, 0, 1, 0, 0, 0, 0, 0, 0), + (0b000001000, 0, 0, 1, 0, 0, 0, 0, 0), + (0b000010000, 0, 0, 0, 1, 0, 0, 0, 0), + (0b000100000, 0, 0, 0, 0, 1, 0, 0, 0), + (0b001000000, 0, 0, 0, 0, 0, 1, 0, 0), + (0b010000000, 0, 0, 0, 0, 0, 0, 1, 0), + (0b100000000, 0, 0, 0, 0, 0, 0, 0, 1), + # .. or not + (0b000000000, 0, 1, 0, 0, 0, 0, 0, 0), + (0b000000000, 0, 0, 1, 0, 0, 0, 0, 0), + (0b000000000, 0, 0, 0, 1, 0, 0, 0, 0), + (0b000000000, 0, 0, 0, 0, 1, 0, 0, 0), + (0b000000000, 0, 0, 0, 0, 0, 1, 0, 0), + (0b000000000, 0, 0, 0, 0, 0, 0, 1, 0), + (0b000000000, 0, 0, 0, 0, 0, 0, 0, 1), + # this final element that must be included + (0b000000010, 1, 0, 0, 0, 0, 0, 0, 0), + ], + operator_with_secret, +) + + +# Commutative operator variant +def operator_with_secret(a: int, b: int) -> int: + + # We're going to create a bitpacked representation of the state of the + # computation. The state is the number of records included, and the value + # is a bitwise OR of the records included. The state is stored in the + # first slot of each tuple, but this operator doesn't know which slot is + # first, so we just check to see if the value is greater than 1. + value_bits = 9 + total_records_to_select = 8 + + def extract(bitpacked: int) -> Tuple[int, int]: + count = bitpacked >> value_bits + value = bitpacked & ((1 << value_bits) - 1) + return (count if count else 1), value + + def pack(count: int, value: int) -> int: + return (count << value_bits) | value + + either = {a, b} + # If we're building our state in the first slot, handle it + if any(x > 1 for x in either): + # Unpack the state from each input + # State is just how many records have been included so far, and the + # value which has been built + ac, av = extract(a) + bc, bv = extract(b) + count, value = ac + bc, av | bv + # If we haven't inluded eight records yet, just build up the number + if count < total_records_to_select: + return pack(count, value) + # If we've included eight records, we're done and need to check the + # value is what we wanted + # The x's represent free choices. + # 0bxxxxxxx00 + secret = 0b010110100 + return 3 if value == secret else 0 + + # Otherwise, we're just checking that we select at least one truthy value + return a | b diff --git a/double_decker_train_problem.py b/double_decker_train_problem.py new file mode 100644 index 0000000..546d1a3 --- /dev/null +++ b/double_decker_train_problem.py @@ -0,0 +1,153 @@ +""" +Double decker train problem: + + |^^^^^^^^^^^^^^| |^^^^^^^^^^^^^^| + | Double cargo | | wagon | + ~~~~ ____ |--------------| |--------------| + Y_,___|[]| | Double | | cargo wagon | + {|_|_|_|PU|_,_|______________|_,_|______________| +//oo---OO=OO O-O O-O ^ O-O O-O ^ + +This is a deceptively simple programming puzzle where you get to make an ordering of rail wagons. +Each wagon has two floors and there is text on the outside of the top and bottom floor. +When your chosen wagons are linked together, the whole text on the top should match the whole bottom. +Output an empty list only when there is no non-empty choice of wagons with matching text. +Your ordering may include any wagon any number of times. + +Example: (illustrated above) + Input wagons: [("Double cargo ", "Double "), ("wagon", "cargo wagon")] + Output ordering: [0, 1] + + Explanation: + Both the top and bottom will read "Double cargo wagon" when concatenated. + Note that there is no need to mutate whitespace characters in the input. +""" + +from typing import Callable, List, Optional, Tuple + +Wagon = Tuple[str, str] + + +MAX_LENGTH = 10 + +def my_solver(wagons: List[Wagon]) -> List[int]: + # Handle empty wagon case + for i, wagon in enumerate(wagons): + if wagon[0] + wagon[1] == "": + return [i] + return solve("", "", [], prepare(wagons)) + +def prepare(wagons: List[Wagon]) -> List[Tuple[int, Wagon]]: + seen = set() + prepared = [] + for i, wagon in enumerate(wagons): + if wagon in seen: + continue + prepared.append((i, wagon)) + seen.add(wagon) + return prepared + +def solve(top_text: str, bottom_text: str, ordering: List[int], wagons: List[Tuple[int, Wagon]]) -> List[int]: + if len(top_text) == 0 and len(bottom_text) == 0 and len(ordering) > 0: + return ordering + if len(ordering) > MAX_LENGTH: + return [] + + for z, (i, wagon) in enumerate(wagons): + top = top_text + wagon[0] + bottom = bottom_text + wagon[1] + reordered = wagons[:z] + wagons[z+1:] + [(i, wagon)] + if top.startswith(bottom): + solution = solve(top[len(bottom):], "", [*ordering, i], reordered) + if len(solution) > 0: + return solution + if bottom.startswith(top): + solution = solve("", bottom[len(top):], [*ordering, i], reordered) + if len(solution) > 0: + return solution + + return [] + +def check_ordering(wagons: List[Wagon], ordering: List[int]) -> bool: + """Checks if the text on top matches the text on the bottom""" + sentence0 = "" + sentence1 = "" + for i in ordering: + assert 0 <= i < len(wagons), f"wagon index out of range: {i}" + sentence0 += wagons[i][0] + sentence1 += wagons[i][1] + return sentence0 == sentence1 + + +def run_test_case( + wagons: List[Wagon], + example_ordering: List[int], + ordering: List[int], +) -> str: + if ordering == []: + if example_ordering == []: + return "PASS" + return "You falsely claimed it was impossible." + + if check_ordering(wagons, ordering): + if example_ordering == []: + return "PASS (and the problem setter is a dummy)" + return "PASS" + + sentence0 = "" + sentence1 = "" + for i in ordering: + assert 0 <= i < len(wagons), f"wagon index out of range: {i}" + sentence0 += wagons[i][0] + sentence1 += wagons[i][1] + + # return f"Your selected wagons had different sentences on the top and bottom.\n{ordering}\n{sentence0!r}\n{sentence1!r}" + + return "Your selected wagons had different sentences on the top and bottom." + + +def run_test_cases(solver: Callable[[List[Wagon]], List[int]]): + test_cases: Tuple[List[Wagon], List[int]] = [ + ([("Double cargo ", "Double "), ("wagon", "cargo wagon")], [0, 1]), + ([("wagon", "cargo wagon"), ("Double cargo ", "Double ")], [1, 0]), + ([("Hello ", "Hello"), ("World", " World")], [0, 1]), + ([("", "")], [0]), + ([(".", "-.."), (".-", ".."), ("--.", "--")], [2, 1, 2, 0]), + ([("bc", "ca"), ("a", "ab"), ("ca", "a"), ("abc", "c")], [1, 0, 1, 3]), + ([("yy", "y"), ("xy", "yx"), ("z", "yz")], [0, 1, 1, 1, 1, 1, 2]), + ([("", "42"), ("42", "")], [1, 0]), + ([("aaaa", ""), ("", "a")], [0, 1, 1, 1, 1]), + ([], []), + ([("0", "1")], []), + ([("0", "1"), (" ", " ")], []), + ([("", " ")], []), + ([("aa", "a"), ("aa", "a"), ("b", "ab")], [0, 2]), + ([("aa", ""), ("ab", ""), ("", "aa")], [0, 2]), + ([("aa", ""), ("ab", "")], []), + ([("ab", "a"), ("ab", "b")], []), + ([("aaaa", ""), ("", "a")], [0, 1, 1, 1, 1]), + ([("ab"*1000, ""), ("a", "ab"), ("b", "ab")], [0] + [1,2]*1000), + ([("a"*(2**4*3**5), ""), ("", "aa"), ("", "aaa")], [0]+[1]*2**4 +[2]*3**5), + + # ([("ab", "a"), ("ab", "b")], []), + # TODO: add more test cases + ] + for i, (wagons, example_ordering) in enumerate(test_cases): + print(f"Test case {i:>2}: ", end="") + if not check_ordering(wagons, example_ordering): + sentence0 = "" + sentence1 = "" + for i in example_ordering: + assert 0 <= i < len(wagons), f"wagon index out of range: {i}" + sentence0 += wagons[i][0] + sentence1 += wagons[i][1] + print(sentence0) + print(sentence1) + + + assert check_ordering(wagons, example_ordering), "example is invalid, dummy." + + print(run_test_case(wagons, example_ordering, solver(wagons))) + + +run_test_cases(my_solver) diff --git a/factorio nuclear power calculation.py b/factorio nuclear power calculation.py new file mode 100644 index 0000000..b42ab57 --- /dev/null +++ b/factorio nuclear power calculation.py @@ -0,0 +1,32 @@ +mega = 1000000 +giga = mega * 1000 +steam_joules = 5.8 * mega / 60 + + +# print(steam_joulesw) + +# target_power = d * giga / 120 +target_power = 2.28 * giga +print(target_power) + +turbine_power = 5.82*mega +turbine_count = target_power / turbine_power +print('number of turbines', turbine_count) + +duration = 120 +turbine_steam_per_second = 60 + +steam_required = turbine_count * duration * turbine_steam_per_second +steam_tanks = steam_required / 25000 +steam_tanks = 12*12 +print('steam_tanks', steam_tanks) + + +tanks_in_row = 6 +row_count = steam_tanks / tanks_in_row +turbines_per_row = turbine_count / row_count + +print(f'row_count: {row_count}') +print(f'turbines_per_row: {turbines_per_row}') + +print(25*6) diff --git a/factorio nuclear power calculation.txt b/factorio nuclear power calculation.txt new file mode 100644 index 0000000..e69de29 diff --git a/generators_example.py b/generators_example.py new file mode 100644 index 0000000..63f0787 --- /dev/null +++ b/generators_example.py @@ -0,0 +1,38 @@ +from contextlib import contextmanager + + +@contextmanager +def my_context_manager(): + print("begin") + yield "middle" + print("end") + + +with my_context_manager() as f: + print(f) + + +# from typing import Iterator + +# # deeply_nested = [[[1, 2, 3], [4, 5]], [[6, 7], [8, 9]]] + + +# # def flatten(deeply_nested: list) -> Iterator[int]: +# # for a in deeply_nested: +# # for b in a: +# # for c in b: +# # yield c +# # print("here") + + +# # for inner in flatten(deeply_nested): +# # print(inner) + + +# def infinite(private_key): +# while True: +# yield from private_key + + +# for private_key_char, plaintext_char in zip(infinite("hello"), "my plaintext"): +# print("combine", private_key_char, plaintext_char) diff --git a/heartbound OCR b/heartbound OCR new file mode 160000 index 0000000..5ed0877 --- /dev/null +++ b/heartbound OCR @@ -0,0 +1 @@ +Subproject commit 5ed08772af63910da82395c5e1912556473f3bbe diff --git a/klimpen_function_chaining.py b/klimpen_function_chaining.py new file mode 100644 index 0000000..b01574f --- /dev/null +++ b/klimpen_function_chaining.py @@ -0,0 +1,55 @@ +def func(datalist, b=False): + output = [] + for data in datalist: + data_a = funcA(data) + data_b = funcB(data_a) if b else data_a + data_c = funcC(data_b) if data.c else data_b + data_d = funcD(data_c) + output.append(data_d) + return output + + +def func_dom_simple(datalist, want_b=False): + out = [] + for data in datalist: + want_c = data.c + data = funcA(data) + if want_b: + data = funcB(data) + if want_c: + data = funcC(data) + data = funcD(data) + out.append(data) + return out + + +def compose(*functions): + return lambda start: reduce(lambda f, x: f(x), functions, start) + + +def identity(x): + return x + + +def func_very_haskell(datalist, want_b=False): + return map( + lambda data: compose( + funcA, + funcB if b else identity, + funcC if data.c else identity, + funcD, + )(data), + datalist, + ) + + +def func_very_haskell2(datalist, want_b=False): + funcAB = compose(funcA, *([funcB] if b else [])) + return map( + lambda data: compose( + funcAB, + funcC if data.c else identity, + funcD, + )(data), + datalist, + ) diff --git a/light_horn.py b/light_horn.py new file mode 100644 index 0000000..c222fd4 --- /dev/null +++ b/light_horn.py @@ -0,0 +1,163 @@ +import pyqtgraph as pg +import numpy as np + +import sys +from PyQt6.QtCore import Qt +from PyQt6.QtWidgets import QApplication, QHBoxLayout, QLabel, QSizePolicy, QSlider, QSpacerItem, \ + QVBoxLayout, QWidget + + + +class Slider(QWidget): + def __init__(self, minimum, maximum, parent=None): + super(Slider, self).__init__(parent=parent) + self.verticalLayout = QVBoxLayout(self) + self.label = QLabel(self) + self.verticalLayout.addWidget(self.label) + self.horizontalLayout = QHBoxLayout() + spacerItem = QSpacerItem(0, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.slider = QSlider(self) + self.slider.setOrientation(Qt.Orientation.Vertical) + self.horizontalLayout.addWidget(self.slider) + spacerItem1 = QSpacerItem(0, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) + self.horizontalLayout.addItem(spacerItem1) + self.verticalLayout.addLayout(self.horizontalLayout) + self.resize(self.sizeHint()) + + self.minimum = minimum + self.maximum = maximum + self.slider.valueChanged.connect(self._setLabelValue) + self.x = None + self._setLabelValue(self.slider.value()) + + def setValue(self, value : float): + self.slider.setValue(100 * (value - self.minimum) / (self.maximum - self.minimum)) + # self.slider.setValue(value) + self._setLabelValue(self.slider.value()) + + + def _setLabelValue(self, value): + self.x = self.minimum + (float(value) / (self.slider.maximum() - self.slider.minimum())) * ( + self.maximum - self.minimum) + self.label.setText("{0:.4g}".format(self.x)) + + +class Widget(QWidget): + def __init__(self, parent=None): + super(Widget, self).__init__(parent=parent) + self.horizontalLayout = QHBoxLayout(self) + self.w1 = Slider(0, 1) + self.w1.setValue(.4141414141414141414141414141) + # self.w1.setValue(50) + self.horizontalLayout.addWidget(self.w1) + + self.w2 = Slider(-1, 1) + self.horizontalLayout.addWidget(self.w2) + + self.w3 = Slider(-10, 10) + self.horizontalLayout.addWidget(self.w3) + + self.w4 = Slider(-10, 10) + self.horizontalLayout.addWidget(self.w4) + + self.win = pg.GraphicsWindow(title="Basic plotting examples") + self.horizontalLayout.addWidget(self.win) + self.p6 = self.win.addPlot(title="My Plot") + self.p6.setAspectLocked() + self.horn_upper = self.p6.plot(pen='r') + self.horn_lower = self.p6.plot(pen='r') + self.bounce_path = self.p6.plot(pen='w') + self.update_plot() + + self.w1.slider.valueChanged.connect(self.update_plot) + self.w2.slider.valueChanged.connect(self.update_plot) + self.w3.slider.valueChanged.connect(self.update_plot) + self.w4.slider.valueChanged.connect(self.update_plot) + + def update_plot(self): + + k = self.w1.x + def ii(x): + return np.floor(2**x + .5) + def qq(x): + return ii(x)*x + def horn(x): + return -k *qq(x-np.log2(ii(x)))/ii(x) + 1 + return np.exp(-k*x) + def horn_bot(x): + return -horn(x) + + #create numpy arrays + #make the numbers large to show that the range shows data from 10000 to all the way 0 + xs = np.linspace(-.5,10, 10000) + horn_upper = horn(xs) + horn_lower = -horn_upper + + vel = np.array([1,0]) + bounce_points = [np.array([xs[0], 1])] + def ray(t: float): + return bounce_points[-1] + t*vel + + epsilon = 1e-10 + + # simulate bounces + for bounce_i in range(100): + bot_t = 0 + top_t = .1 + t = bot_t + horn_func = horn if vel[1] >= 0 else horn_bot + + p = ray(t) + initial_side = p[1] < horn_func(p[0]) + t = top_t + # expand search forwards + for i in range(64): + p = ray(top_t) + side = p[1] < horn_func(p[0]) + if p[0] < -.5: + break + if side == initial_side: + (bot_t, top_t) = (top_t, top_t+1.1*(top_t - bot_t)) + else: + break + if side == initial_side: + # print(f'never found a crossover point at bounce_i={bounce_i}') + # bounce_points.append(p) + break + # bisect + for i in range(64): + t = (bot_t + top_t) / 2 + p = ray(t) + side = p[1] < horn_func(p[0]) + if side == initial_side: + bot_t = t + else: + top_t = t + if bot_t == top_t: + break + dx = 2*epsilon + dy = horn_func(p[0] + epsilon) - horn_func(p[0] - epsilon) + n = np.array([-dy, dx]) + n /= np.sqrt(n.dot(n)) # normal + # reflect + vel = vel - 2*(vel.dot(n))*n + bounce_points.append(p) + # (bot_t, top_t) + + bounce_xs = [p[0] for p in bounce_points] + bounce_ys = [p[1] for p in bounce_points] + + self.horn_upper.setData(x=xs, y=horn_upper) + self.horn_lower.setData(x=xs, y=horn_lower) + self.bounce_path.setData(x=bounce_xs, y=bounce_ys) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + w = Widget() + w.show() + sys.exit(app.exec()) + +# if __name__ == '__main__': +# pg.exec() diff --git a/print_shelf.py b/print_shelf.py new file mode 100644 index 0000000..a24483f --- /dev/null +++ b/print_shelf.py @@ -0,0 +1,7 @@ +import shelve +import sys + +if __name__ == "__main__": + with shelve.open(sys.argv[-1]) as shelf: + for k, v in sorted(shelf.items(), key=lambda tup: (tup[1], tup[0])): + print(k, v) diff --git a/puzzle.py b/puzzle.py new file mode 100644 index 0000000..294cec2 --- /dev/null +++ b/puzzle.py @@ -0,0 +1,53 @@ +from collections import deque + +initial = ". . . " + +transitions = [ + 'S SSS ', + ' SS S ', + ' S S ', + 'S SSS ', + 'S S S', + ' S S ', + ' S SS', + 'S S S', +] + +def toggle(c: str) -> str: + return ' ' if c == '.' else '.' + +def apply(state: str, transition: str) -> str: + return ''.join(toggle(c) if t=='S' else c for c, t in zip(state, transition)) + + + +print(apply(initial, transitions[0])) + +def bfs(start, neighbours_fn, is_finished): + source = {start: None} + q = deque([start]) + while q: + s = q.popleft() + if is_finished(s): + steps = [s] + parent = source[s] + while parent is not None: + steps.append(parent) + parent = source[parent] + steps = steps[::-1] + for i in range(len(steps)-1): + steps[i] = steps[i] + f' {neighbours_fn(steps[i]).index(steps[i+1])}' + return steps + for neigh in neighbours_fn(s): + if neigh in source: + continue + source[neigh] = s + q.append(neigh) + raise ValueError('impossible') + +print('\n'.join(bfs( + initial, + lambda s: [apply(s, t) for t in transitions], + lambda s: s==' ' + # lambda s: s==' . ' +))) diff --git a/signals b/signals new file mode 160000 index 0000000..171c1fa --- /dev/null +++ b/signals @@ -0,0 +1 @@ +Subproject commit 171c1fae662b711a75f0df8be7a3da3db9aa95b3 diff --git a/skewb_solver.py b/skewb_solver.py index 1c80f35..c28cac2 100644 --- a/skewb_solver.py +++ b/skewb_solver.py @@ -1,3 +1,4 @@ +from math import sqrt import shelve from collections import deque from dataclasses import dataclass, replace @@ -622,13 +623,13 @@ def shelve_it(file_name): return decorator -def get_path_from_heuristic( +def get_paths_from_heuristic( start: Skewb, heuristic_permutation: list[int] -) -> list[LowerAntiAxis]: +) -> list[list[LowerAntiAxis]]: + out: list[list[LowerAntiAxis]] = [] s = start mask = [0 for _ in heuristic_permutation] total_path_length = 0 - out: list[LowerAntiAxis] = [] for heuristic_i in heuristic_permutation: mask[heuristic_i] = 1 @@ -643,17 +644,17 @@ def get_path_from_heuristic( else: path = breadth_first_search(s, step_finished) # print() - # print(f"{mask=} {s=} {path=}") + print(f"{mask=} {s=} {path=}") if path is None: raise ValueError("oh no! solver could not find solution") - out.extend(path) + out.append(path) s = apply_twists(s, path) total_path_length += len(path) return out -def get_total_path_length(start: Skewb, heuristic_permutation: list[int]) -> int: - return len(get_path_from_heuristic(start, heuristic_permutation)) +# def get_total_path_length(start: Skewb, heuristic_permutation: list[int]) -> int: +# return sum(len(p) for p in get_paths_from_heuristic(start, heuristic_permutation)) # close_to_wrongly_solved = Skewb(top=(R0, B0, O0, G0), bot=(B0, O0, G0, R0), mids=(BY, GRB, ORG, RY, YY)) @@ -664,13 +665,27 @@ start = near_end HURISTIC_PERMUTATION_LENGTH = 4 + 4 + 5 +def quadratic_mean(values: list[float]) -> float: + return sqrt(sum(x * x for x in values) / len(values)) + + +def get_mean_path_length( + start: Skewb, + heuristic_permutation: list[int], + mean_fn: Callable[[list[float]], float] = quadratic_mean, +) -> float: + return mean_fn( + [len(p) for p in get_paths_from_heuristic(start, heuristic_permutation)] + ) + + @shelve_it("skewb_solver.evaluate_permutation.shelve.sqlite") def evaluate_permutation( heuristic_permutation: list[int], seed=4, sample_size: int = 10 ) -> float: pls = [] for i in range(sample_size): - pl = get_total_path_length(random_skewb(seed=i + seed), heuristic_permutation) + pl = get_mean_path_length(random_skewb(seed=i + seed), heuristic_permutation) print(f"{pl=}") pls.append(pl) return sum(pls) / len(pls) @@ -727,7 +742,7 @@ if __name__ == "__main__": # print(f"{hp=} {evaluate_permutation(hp, seed=200, sample_size=200)=}") hp = [10, 0, 1, 2, 3, 8, 9, 11, 12, 4, 5, 6, 7] - path = get_path_from_heuristic( + path = get_paths_from_heuristic( Skewb(top=(O0, B0, R0, G0), bot=(B2, R0, G1, O0), mids=(BY, YY, GY, RY, ORB)), hp, ) diff --git a/starchild_teaching.py b/starchild_teaching.py new file mode 100644 index 0000000..d8c9278 --- /dev/null +++ b/starchild_teaching.py @@ -0,0 +1,180 @@ +from typing import List, Set, Dict, Tuple + +# a: int = 2 +# b: float = 2.3 +# c: str = "lkasldkjaslkjdasd" +# cc: bytes = b"AAA" +# d: None = None +# e: bool = True +# f: bool = False +# k: Tuple[int, str, str] = (1, "hello", "world") + +# g: List[int] = [7] +# j: Set[str] = {"friend a", "friend b"} +# i: Dict[str, int] = {"4": 6, "6": 8} + +# import collections + +# print(dir(collections)) + +# i["5"] = 7 +# print(dict[str]) + +# + +from dataclasses import dataclass + + +@dataclass +class Card: + suit: str + rank: int # 1 -> ace, 10+ -> jack, queen, king + + def __str__(self) -> str: + if self.rank == 1: + rank_str = "ace" + elif self.rank == 10: + rank_str = "jack" + elif self.rank == 11: + rank_str = "queen" + elif self.rank == 12: + rank_str = "king" + else: + rank_str = str(self.rank) + return f"{rank_str} of {self.suit}" + + # def __lt__(self, other) -> bool: + # if self.suit != other.suit: + # return self.suit < other.suit + # else: + # return self.rank < other.rank + + +hand: List[Card] = [ + Card(suit="clubs", rank=6), + Card(suit="clubs", rank=5), + Card(suit="spades", rank=1), + Card(suit="hearts", rank=10), +] + + +def suit_first(card: Card) -> Tuple: + return (card.suit, card.rank) + + +def rank_first(card: Card) -> Tuple: + return (card.rank, card.suit) + + +# hand.sort(key=suit_first) +# hand.sort(key=rank_first) +# print(hand) + +print((1, "clubs", "cinnamon") < (1, "clubs", "vanilla")) + +# O(N ^ 2) + + +# for i in range(10): +# print(i) + +# iterable = range(4) +# iterator = iter(iterable) +# # print(dir(iterator)) +# # print(iterator.__str__()[0]) +# # print(iterator) + +# # print(dir(iterator.__next__)) +# print(next.__doc__) + +# try: +# while True: +# print(iterator.__next__()) + +# except StopIteration: +# print("handled!") + +[ + "__class__", + "__delattr__", + "__dir__", + "__doc__", + "__eq__", + "__format__", + "__ge__", + "__getattribute__", + "__gt__", + "__hash__", + "__init__", + "__init_subclass__", + "__iter__", + "__le__", + "__length_hint__", + "__lt__", + "__ne__", + "__new__", + "__next__", + "__reduce__", + "__reduce_ex__", + "__repr__", + "__setattr__", + "__setstate__", + "__sizeof__", + "__str__", + "__subclasshook__", +] + +# print(next(iterator)) +# print(next(iterator)) +# print(next(iterator)) +# print(next(iterator)) +# print(next(iterator)) + + +# def sum_until(x: int) -> int: +# """computes the sum of number from 0 to x (inclusive)""" + +# to_sum = list(range(x + 1)) +# print(to_sum) +# s = 0 +# for num in to_sum: +# s += num +# return s + + +# print(sum_until(3)) + +# from abc import ABC +# from dataclasses import dataclass +# from typing import List + + +# class Animal(ABC): +# def make_noise(self): +# pass + + +# @dataclass +# class Cat(Animal): +# name: str + +# def make_noise(self): +# print("meow") + + +# class Lion(Cat): +# def hunt(self): +# print("nom") + + +# class Kitty(Cat): +# def make_noise(self): +# print("purr") + + +# animals: List[Animal] = [ +# Kitty(name="Star Child"), +# Lion(name="Simba"), +# ] +# for animal in animals: +# animal.make_noise() diff --git a/try.py b/try.py new file mode 100644 index 0000000..d3f3efb --- /dev/null +++ b/try.py @@ -0,0 +1,27 @@ +import sys + + +class Foo(int): + def __init__(self, x): + super().__init__() + self.q = x + 1 + + +# print(Foo(3).q) +print(sys.getsizeof(3)) +print(sys.getsizeof(Foo(3))) + +# from typing import List + +# def contains_321(ints: List[int]) -> bool: +# try: +# i3 = ints.index(3) +# i2 = ints[i3+1:].index(2) +# i1 = ints[i2+1:].index(1) +# return True +# except ValueError: +# return False + +# print(contains_321([3,2,1])) +# print(contains_321([3,1,2,1,1])) +# print(contains_321([1,2,3])) diff --git a/upload/SimpleHTTPServerWithUpload.py b/upload/SimpleHTTPServerWithUpload.py new file mode 100644 index 0000000..1dac4e0 --- /dev/null +++ b/upload/SimpleHTTPServerWithUpload.py @@ -0,0 +1,295 @@ +#!/usr/bin/env python3 + +"""Simple HTTP Server With Upload. + +This module builds on BaseHTTPServer by implementing the standard GET +and HEAD requests in a fairly straightforward manner. + +see: https://gist.github.com/UniIsland/3346170 +""" + + +__version__ = "0.1" +__all__ = ["SimpleHTTPRequestHandler"] +__author__ = "bones7456" +__home_page__ = "http://li2z.cn/" + +import os +import posixpath +import http.server +import urllib.request, urllib.parse, urllib.error +import html +import shutil +import mimetypes +import re +from io import BytesIO + + +class SimpleHTTPRequestHandler(http.server.BaseHTTPRequestHandler): + + """Simple HTTP request handler with GET/HEAD/POST commands. + + This serves files from the current directory and any of its + subdirectories. The MIME type for files is determined by + calling the .guess_type() method. And can reveive file uploaded + by client. + + The GET/HEAD/POST requests are identical except that the HEAD + request omits the actual contents of the file. + + """ + + server_version = "SimpleHTTPWithUpload/" + __version__ + + def do_GET(self): + """Serve a GET request.""" + f = self.send_head() + if f: + self.copyfile(f, self.wfile) + f.close() + + def do_HEAD(self): + """Serve a HEAD request.""" + f = self.send_head() + if f: + f.close() + + def do_POST(self): + """Serve a POST request.""" + r, info = self.deal_post_data() + print((r, info, "by: ", self.client_address)) + f = BytesIO() + f.write(b'') + f.write(b"\n