diff --git a/dotfiles/lib/python/blackjack.py b/dotfiles/lib/python/blackjack.py new file mode 100644 index 00000000..111be7d6 --- /dev/null +++ b/dotfiles/lib/python/blackjack.py @@ -0,0 +1,123 @@ +import random +import enum +import itertools + + +class Suit(enum.Enum): + CLUBS = 0 + DIAMONDS = 1 + HEARTS = 2 + SPADES = 3 + + +class Deck(object): + + @classmethod + def random_deck(cls): + return cls(generate_cards()) + + def __init__(self, cards): + self._cards = cards + self.top = 0 + + def pop_top(self): + if self.top >= 52: + raise Exception() + card = get_card(self._cards[self.top]) + self.top += 1 + return card + + +def get_card(card_number): + return (Suit(card_number // 13), card_number % 13) + + +def random_permutation_of_size(n): + remaining = list(range(n)) + for i in range(n-1, -1, -1): + yield remaining.pop(random.randint(0, i)) + + +def random_permutation(the_list): + for index in random_permutation_of_size(len(the_list)): + yield the_list[index] + + +def generate_cards(num_cards=52): + return list(random_permutation(range(num_cards))) + + +def card_value(card_number): + if card_number >= 10: + return 10 + return card_number + 1 + + +def card_string(card_number): + if card_number == 12: + return 'K' + elif card_number == 11: + return 'Q' + elif card_number == 10: + return 'J' + return str(card_number + 1) + + +def get_hand_value(hand): + number_of_aces = 0 + total_value = 0 + for _, card_number in hand: + if card_number == 0: + number_of_aces += 1 + else: + total_value += card_value(card_number) + while total_value < 10 - (number_of_aces - 1): + total_value += 11 + number_of_aces -= 1 + total_value += number_aces + return total_value + + +class Blackjack(object): + + def __init__(self, deck=None): + self._deck = deck or Deck.random_deck() + self.initialize_game() + + def initialize_game(self): + self.dealer_hand = [self._deck.pop_top() for _ in range(2)] + self.player_hand = [self._deck.pop_top() for _ in range(2)] + + def hit(self): + self.player_hand.append(self._deck.pop_top()) + + def run_dealer(self): + while get_hand_value(self.dealer_hand) < 17: + self.dealer_hand.append(self._deck.pop_top()) + + +class UserHandler(object): + + def __init__(self, game=None): + self._game = game or Blackjack() + + def print_game_state(self): + print(self.game_string()) + + def game_string(self): + return "\n".join([ + self.dealer_string(), self.hand_string(self._game.player_hand), + ]) + + def dealer_string(self): + return "X {0}".format(self.hand_string(self._game.dealer_hand[1:])) + + def hand_string(self, cards): + return " ".join(card_string(card[1]) for card in cards) + + +if __name__ == '__main__': + UserHandler().print_game_state() + the_cards = UserHandler()._game._deck._cards + print (the_cards) + print(set(the_cards) == set(range(52))) diff --git a/dotfiles/lib/python/crime_profit.py b/dotfiles/lib/python/crime_profit.py new file mode 100644 index 00000000..01b83eed --- /dev/null +++ b/dotfiles/lib/python/crime_profit.py @@ -0,0 +1,42 @@ +class CountCrimes(object): + + def __init__(self, profits, groups, profit_needed, group_count): + self.profits = profits + self.groups = groups + self.crime_count = len(profits) + self.profit_needed = profit_needed + self.group_count = count + self.reset_cache() + + def process_crime(self, profit, num_required): + for gangster_count in range(self.group_count, -1, -1): + for profit_amount in range(self.profit_needed, -1, -1): + new_gangster_count = gangster_count + num_required + new_profit = profit_amount + profit + if new_profit > self.profit_needed: + new_profit = self.profit_needed + new_count = self.cache[gangster_count][profit_amount] + if new_count > 0 and new_gangster_count <= self.group_count: + self.cache[new_gangster_count][new_profit] += new_count + + def reset_cache(self): + self.cache = [[0 for _ in range(profit_needed + 1)] + for _ in range(group_count + 1)] + self.cache[0][0] = 1 + + def process_crimes(self): + for profit, num_required in zip(self.profits, self.groups): + self.process_crime(profit, num_required) + + def get_count(self): + self.reset_cache() + self.process_crimes() + return self.count_ways() + + def count_ways(self): + return sum(self.cache[i][self.profit_needed] + for i in range(self.group_count + 1)) + + +if __name__ == '__main__': + print(CountCrimes().get_count()) diff --git a/dotfiles/lib/python/dashed_paragraph.py b/dotfiles/lib/python/dashed_paragraph.py new file mode 100755 index 00000000..6ac251fd --- /dev/null +++ b/dotfiles/lib/python/dashed_paragraph.py @@ -0,0 +1,58 @@ +#! /usr/bin/env python +import itertools + +def textJustify(input_string, justification_length): + partitioning = partition_paragraph(input_string, justification_length) + return "\n".join(itertools.chain( + (justify_line(line_partition, justification_length) + for line_partition in partitioning[:-1]), + [" ".join(partitioning[-1])] + )) + +def justify_line(line_words, justification_length): + if len(line_words) == 1: + return line_words[0] + total_length = sum(len(word) for word in line_words) + word_count = len(line_words) + number_of_word_boundaries = word_count - 1 + spaces_to_add = justification_length - total_length + base_spaces = spaces_to_add // number_of_word_boundaries + extra_spaces = spaces_to_add % number_of_word_boundaries + + output_string = "" + for i, word in enumerate(line_words): + output_string += word + if i >= len(line_words) - 1: + break + space_count = base_spaces + if i < extra_spaces: + space_count += 1 + spaces = " " * space_count + output_string += spaces + + return output_string + +def partition_paragraph(input_string, justification_length): + current_line_lenth = 0 + partitioning = [] + current = [] + for word in input_string.split(): + word_length = len(word) + length_with_word = current_line_lenth + word_length + if justification_length < length_with_word: + partitioning.append(current) + current = [] + current_line_lenth = 0 + length_with_word = word_length + + current.append(word) + current_line_lenth = length_with_word + 1 + + if current: + partitioning.append(current) + + return partitioning + +if __name__ == '__main__': + sample = "Coursera provides universal access to the world's best education, partnering with to universities and organizations to offer courses online." + print(textJustify(sample, 10)) diff --git a/dotfiles/lib/python/dialpad.py b/dotfiles/lib/python/dialpad.py new file mode 100644 index 00000000..a2822ab9 --- /dev/null +++ b/dotfiles/lib/python/dialpad.py @@ -0,0 +1,107 @@ +# neighbors = { +# 1: [6, 8], +# 2: [7, 9], +# 3: [8, 4], +# 4: [9, 3, 0], +# 5: [], +# 6: [0, 7, 1], +# 7: [2, 6], +# 8: [1, 3], +# 9: [4, 2], +# 0: [4, 6] +# } + +# cache = {} + +# def count_numbers(current_number, number_of_hops): +# cache_value = (current_number, number_of_hops) +# if cache_value in cache: +# return cache[cache_value] + +# if number_of_hops == 1: +# return 1 +# number_count = 0 +# for neighbor in neighbors[current_number]: +# number_count += count_numbers(neighbor, number_of_hops - 1) + +# cache[cache_value] = number_count +# return number_count + + +class DialpadCounter(object): + + knight_deltas = [ + (2, 1), + (-2, -1), + (-2, 1), + (2, -1), + (1, 2), + (-1, -2), + (-1, 2), + (1, -2) + ] + + def __init__(self, dialpad_matrix): + self._matrix = dialpad_matrix + self._row_size = len(dialpad_matrix[0]) + self._row_count = len(dialpad_matrix) + self._cache = {} + + def neighbors(self, y, x): + result = [] + for delta_y, delta_x in self.knight_deltas: + neighbor_y = delta_y + y + neighbor_x = delta_x + x + neighbor = (neighbor_y, neighbor_x) + if (self.inbounds(neighbor_y, neighbor_x) and + self._matrix[neighbor_y][neighbor_x]): + result.append(neighbor) + return result + + def inbounds(self, y, x): + return 0 <= x < self._row_size and 0 <= y < self._row_count + + def count_numbers(self, coordinate, number_of_hops): + y, x = coordinate + if not self._matrix[y][x]: + raise Exception() + + cache_value = (coordinate, number_of_hops) + + if cache_value in self._cache: + return self._cache[cache_value] + + if number_of_hops == 1: + return 1 + + number_count = 0 + for neighbor in self.neighbors(y, x): + number_count += self.count_numbers(neighbor, number_of_hops - 1) + + self._cache[cache_value] = number_count + + return number_count + +def count_numbers(number, number_of_hops): + matrix = [ + [True, True, True], + [True, True, True], + [True, True, True], + [False, True, False] + ] + if number == 0: + coordinate = 3, 1 + else: + row = (number - 1) // 3 + column = (number - 1) % 3 + coordinate = (row, column) + counter = DialpadCounter(matrix) + return counter.count_numbers(coordinate, number_of_hops) + +if __name__ == '__main__': + print(count_numbers(1, 1)) + print(count_numbers(1, 2)) + print(count_numbers(1, 3)) + print(count_numbers(1, 4)) + print(count_numbers(1, 10)) + print(count_numbers(1, 30)) diff --git a/dotfiles/lib/python/freenome.py b/dotfiles/lib/python/freenome.py new file mode 100644 index 00000000..714a6138 --- /dev/null +++ b/dotfiles/lib/python/freenome.py @@ -0,0 +1,56 @@ +class MazeSolver(object): + + def __init__(self, maze): + self.maze = maze + self.row_length = len(maze[0]) + self.column_length = len(maze) + self.visited = set() + + @property + def finish(self): + return (self.column_length - 1, self.row_length - 1) + + deltas = [(1, 0), (0, 1), (-1, 0), (0, -1)] + + def is_in_bounds(self, location): + column_index, row_index = location + return ( + 0 <= column_index < self.column_length and + 0 <= row_index < self.row_length + ) + + def find_adjacency(self, location): + for delta in self.deltas: + column_delta, row_delta = delta + column_location, row_location = location + new_column_location = column_location + column_delta + new_row_location = row_location + row_delta + adjacent_location = (new_column_location, new_row_location) + if ( + self.is_in_bounds(adjacent_location) and + self.maze[new_column_location][new_row_location] + ): + yield adjacent_location + + def solve(self, current_location=(0, 0)): + if current_location == self.finish: + return [current_location] + self.visited.add(current_location) + for new_location in self.find_adjacency(current_location): + if new_location in self.visited: + continue + result = self.solve(new_location) + if result is not None: + return [current_location] + result + return None + +if __name__ == '__main__': + maze = [ + [1, 1, 1, 1, 1], + [0, 0, 1, 0, 0], + [1, 0, 1, 1, 1], + [1, 0, 0, 0, 1], + [1, 1, 1, 1, 1] + ] + + print(MazeSolver(maze).solve()) diff --git a/dotfiles/lib/python/game_of_life.py b/dotfiles/lib/python/game_of_life.py new file mode 100755 index 00000000..506832b7 --- /dev/null +++ b/dotfiles/lib/python/game_of_life.py @@ -0,0 +1,85 @@ +#! /usr/bin/env python + +class GameOfLife(object): + + neighbor_deltas = [ + (1, 0), (1, 1), (1, -1), + (0, 1), (0, -1), + (-1, 0), (-1, 1), (-1, -1) + ] + + @classmethod + def empty_with_size(cls, rows, columns=None): + columns = columns or rows + return cls([]) + + @staticmethod + def build_empty_grid(rows, columns): + return [[False for _ in range(columns)] for _ in range(rows)] + + def __init__(self, initial_state): + self.current_state = initial_state + self.row_count = len(initial_state) + self.column_count = len(initial_state[0]) + + def _neighbors(self, row, column): + for (row_delta, column_delta) in self.neighbor_deltas: + candidate_row = row + row_delta + candidate_column = column + column_delta + if self._in_bounds(candidate_row, candidate_column): + yield candidate_row, candidate_column + + def _in_bounds(self, row, column): + return 0 <= row < self.row_count and 0 <= column < self.column_count + + def _next_state_for_cell(self, row, column): + live_count = 0 + cell_was_live = self.current_state[row][column] + for neighbor_row, neighbor_column in self._neighbors(row, column): + if self.current_state[neighbor_row][neighbor_column]: + live_count += 1 + if cell_was_live: + return 1 < live_count < 4 + else: + return live_count == 3 + + def compute_next_game_state(self, new_state=None): + new_state = new_state or self.build_empty_grid( + self.row_count, self.column_count, + ) + for row in range(self.row_count): + for column in range(self.column_count): + new_state[row][column] = self._next_state_for_cell(row, column) + return new_state + + def tick(self, new_state=None): + self.current_state = self.compute_next_game_state(new_state) + + def _build_row_string(self, row): + return " ".join(["o" if state else "." for state in row]) + + @property + def state_string(self): + return "\n".join( + self._build_row_string(row) for row in self.current_state + ) + + @classmethod + def run(cls, initial_state, generations=30): + game = cls(initial_state) + for _ in range(generations): + game.tick() + print(game.state_string) + return game.current_state + +sample_size = 50 + +sample_state = [ + [False, True, False] + ([False] * (sample_size - 3)), + [False, False, True] + ([False] * (sample_size - 3)), + [True, True, True] + ([False] * (sample_size - 3)), +] + [[False] * sample_size for _ in range(sample_size - 3)] + + +if __name__ == '__main__': + GameOfLife.run(sample_state) diff --git a/dotfiles/lib/python/justify_paragraph.py b/dotfiles/lib/python/justify_paragraph.py new file mode 100755 index 00000000..114cb6a5 --- /dev/null +++ b/dotfiles/lib/python/justify_paragraph.py @@ -0,0 +1,58 @@ +#! /usr/bin/env python +import itertools + +def textJustify(input_string, justification_length): + partitioning = partition_paragraph(input_string, justification_length) + return "\n".join(itertools.chain( + (justify_line(line_partition, justification_length) + for line_partition in partitioning[:-1]), + [" ".join(partitioning[-1])] + )) + +def justify_line(line_words, justification_length): + if len(line_words) == 1: + return line_words[0] + total_length = sum(len(word) for word in line_words) + word_count = len(line_words) + number_of_word_boundaries = word_count - 1 + spaces_to_add = justification_length - total_length + base_spaces = spaces_to_add // number_of_word_boundaries + extra_spaces = spaces_to_add % number_of_word_boundaries + + output_string = "" + for i, word in enumerate(line_words): + output_string += word + if i >= len(line_words) - 1: + break + space_count = base_spaces + if i < extra_spaces: + space_count += 1 + spaces = " " * space_count + output_string += spaces + + return output_string + +def partition_paragraph(input_string, justification_length): + min_line_length = 0 + partitioning = [] + current = [] + for word in input_string.split(): + word_length = len(word) + length_with_word = min_line_length + word_length + if justification_length < length_with_word: + partitioning.append(current) + current = [] + min_line_length = 0 + length_with_word = word_length + + current.append(word) + min_line_length = length_with_word + 1 + + if current: + partitioning.append(current) + + return partitioning + +if __name__ == '__main__': + sample = "Coursera provides universal access to the world's best education, partnering with to universities and organizations to offer courses online." + print(textJustify(sample, 10)) diff --git a/dotfiles/lib/python/webhook.py b/dotfiles/lib/python/webhook.py new file mode 100644 index 00000000..ec7be83e --- /dev/null +++ b/dotfiles/lib/python/webhook.py @@ -0,0 +1,62 @@ +import asyncio +import random +import queue +import requests +import threading + +class WebhookHandler(object): + + def __init__(self, callback_uri='http://whatever'): + self.queue = queue.Queue() + self.callback_uri = callback_uri + + def enqueue(self, webhook_request): + self.queue.put(webhook_request) + + def webhook_worker(self): + while True: + callback_request = self.queue.get() + self.run_request(callback_request) + self.queue.task_done() + + def flaky_request(self, callback_request): + random_value = random.random() + print(random_value) + if random_value > .9: + return 500 + r = requests.get(self.callback_uri, params=callback_request) + return r.status_code + + def run_request(self, request): + status_code = self.flaky_request(request) + if status_code != 200: + asyncio.run(self.do_retry(callback_request)) + else: + print("made request") + + async def do_retry(self, request, delay=1): + await asyncio.sleep(delay) + print("Retried request") + self.run_request(request) + + +if __name__ == '__main__': + handler = WebhookHandler(callback_uri="https://www.google.com/") + thread_count = 10 + for _ in range(1000): + handler.enqueue({}) + + threads = [] + for _ in range(thread_count): + thread = threading.Thread(target=handler.webhook_worker, daemon=False) + thread.start() + threads.append(thread) + + for _ in range(1000): + handler.enqueue({}) + + loop = asyncio.get_event_loop() + try: + loop.run_forever() + finally: + loop.close()