Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track Solutions #26

Open
wants to merge 41 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
5 changes: 3 additions & 2 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ jobs:
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Test with pytest
run: |
pytest --continue-on-collection-errors
git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/*
git diff origin/main HEAD --name-only -- practice | xargs dirname | sort | uniq | xargs pytest
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
Binary file added practice/.DS_Store
Binary file not shown.
94 changes: 92 additions & 2 deletions practice/affine-cipher/affine_cipher.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,96 @@
import math
import string

def is_coprime(x, y):
"""
Check if two numbers are coprime.

Args:
x (int): First number.
y (int): Second number.

Returns:
bool: True if x and y are coprime, False otherwise.
"""
return math.gcd(x, y) == 1

def encode(plain_text, a, b):
pass
"""
Encode plain text using the affine cipher.

Args:
plain_text (str): The plain text to be encoded.
a (int): Coefficient 'a' for the affine cipher.
b (int): Coefficient 'b' for the affine cipher.

Returns:
str: The encoded cipher text.

Raises:
ValueError: If 'a' and the length of the alphabet are not coprime.
"""
alphabet = list(string.ascii_lowercase)
numbers = list(string.digits)

if not is_coprime(a, len(alphabet)):
raise ValueError("a and m must be coprime.")

indexes = []
for letter in plain_text.lower():
if letter in alphabet:
indexes.append((a * alphabet.index(letter) + b) % len(alphabet))
elif letter in numbers:
indexes.append(int(letter) + 1000)

out = ""
for place, index in enumerate(indexes):
if place % 5 == 0 and place > 1:
out += " "
if index < len(alphabet):
out += alphabet[index]
else:
out += str(index - 1000)
else:
if index < len(alphabet):
out += alphabet[index]
else:
out += str(index - 1000)

return out

def decode(ciphered_text, a, b):
pass
"""
Decode ciphered text using the affine cipher.

Args:
ciphered_text (str): The ciphered text to be decoded.
a (int): Coefficient 'a' for the affine cipher.
b (int): Coefficient 'b' for the affine cipher.

Returns:
str: The decoded plain text.

Raises:
ValueError: If 'a' and the length of the alphabet are not coprime.
"""
alphabet = list(string.ascii_lowercase)
numbers = list(string.digits)

if not is_coprime(a, len(alphabet)):
raise ValueError("a and m must be coprime.")

indexes = []
for letter in ciphered_text:
if letter in numbers:
indexes.append(int(letter) + 1000)
elif letter in alphabet:
indexes.append(
pow(a, -1, len(alphabet)) * (alphabet.index(letter) - b) % len(alphabet)
)

return "".join(
[
str(letter - 1000) if letter > len(alphabet) else alphabet[letter]
for letter in indexes
]
)
57 changes: 55 additions & 2 deletions practice/alphametics/alphametics.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,55 @@
def solve(puzzle):
pass
from itertools import permutations
from typing import Dict, Optional, List, Tuple


def parse_puzzle(puzzle: str) -> Tuple[List[str], str]:
"""
Parse the alphametics puzzle into a list of words and the result.

Args:
puzzle (str): The alphametics puzzle to parse.

Returns:
Tuple[List[str], str]: A tuple containing a list of words and the result.
"""
# Split the puzzle into words and the result
inputs, value = puzzle.split(" == ")
# Split the words based on the '+' sign and strip whitespace
words = [i.strip() for i in inputs.split("+")]
return (words, value.strip())


def solve(puzzle: str) -> Optional[Dict[str, int]]:
"""
Solve an alphametics puzzle.

Args:
puzzle (str): The alphametics puzzle to solve.

Returns:
Optional[Dict[str, int]]: A dictionary mapping characters to digits if a solution is found,
otherwise None.
"""
# Parse the puzzle into words and the result
words, value = parse_puzzle(puzzle)
# Find the first character of each word and the result
nonzero = set([w[0] for w in words + [value] if len(w) > 1])
# Gather all unique letters in the puzzle
letters = list(set("".join(words + [value])) - nonzero) + list(nonzero)

# Iterate through all permutations of digits
for perm in permutations("0123456789", len(letters)):
conv_dict = dict(zip(letters, perm))
# Skip permutations where leading letters map to '0'
if "0" in perm[-len(nonzero):]:
continue

# Convert words to integers based on the permutation
values = [int("".join(conv_dict[w] for w in word)) for word in words]
# Convert the result to an integer based on the permutation
summed = int("".join(conv_dict[v] for v in value))

# Check if the sum of the words equals the result
if sum(values) == summed:
return {k: int(v) for k, v in conv_dict.items()}
return None
2 changes: 1 addition & 1 deletion practice/alphametics/alphametics_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def test_puzzle_with_ten_letters(self):
)

# See https://github.com/exercism/python/pull/1358
@unittest.skip("extra-credit")
# @unittest.skip("extra-credit")
def test_puzzle_with_ten_letters_and_199_addends(self):
"""This test may take a long time to run. Please be patient when running it."""
puzzle = (
Expand Down
73 changes: 67 additions & 6 deletions practice/bank-account/bank_account.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,79 @@
class BankAccount:
def __init__(self):
pass
"""Initialize a BankAccount object.

The balance is set to zero and the account status is closed by default.
"""
self._balance = 0
self._opened = False

def _raise_error_if_not_open(self):
"""Raise ValueError if the account is closed."""
if not self._opened:
raise ValueError("account not open")

def _raise_error_if_amount_less_than_0(self, amount):
"""Raise ValueError if the amount is less than zero."""
if amount < 0:
raise ValueError("amount must be greater than 0")

def get_balance(self):
pass
"""Get the current balance of the account.

Returns:
int: The current balance of the account.

Raises:
ValueError: If the account is closed.
"""
self._raise_error_if_not_open()
return self._balance

def open(self):
pass
"""Open the bank account.

Raises:
ValueError: If the account is already open.
"""
if self._opened:
raise ValueError("account already open")
self._opened = True

def deposit(self, amount):
pass
"""Deposit funds into the bank account.

Args:
amount (int): The amount to deposit.

Raises:
ValueError: If the account is closed or the amount is negative.
"""
self._raise_error_if_not_open()
self._raise_error_if_amount_less_than_0(amount)
self._balance += amount

def withdraw(self, amount):
pass
"""Withdraw funds from the bank account.

Args:
amount (int): The amount to withdraw.

Raises:
ValueError: If the account is closed, the amount is negative,
or the withdrawal amount exceeds the balance.
"""
self._raise_error_if_not_open()
self._raise_error_if_amount_less_than_0(amount)
if amount > self._balance:
raise ValueError("amount must be less than balance")
self._balance -= amount

def close(self):
pass
"""Close the bank account and reset the balance to zero.

Raises:
ValueError: If the account is already closed.
"""
self._raise_error_if_not_open()
self._opened = False
self._balance = 0
110 changes: 104 additions & 6 deletions practice/binary-search-tree/binary_search_tree.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,117 @@
class TreeNode:
"""Represents a single node in a binary search tree."""

def __init__(self, data, left=None, right=None):
self.data = None
self.left = None
self.right = None
"""
Initialize a TreeNode.

Args:
data: The data stored in the node.
left (TreeNode): Reference to the left child node.
right (TreeNode): Reference to the right child node.
"""
self.data = data
self.left = left
self.right = right

def __str__(self):
"""Return a string representation of the TreeNode."""
return f'TreeNode(data={self.data}, left={self.left}, right={self.right})'


class BinarySearchTree:
"""Represents a binary search tree."""

def __init__(self, tree_data):
pass
"""
Initialize a BinarySearchTree with a list of data elements.

Args:
tree_data (list): List of data elements to insert into the binary search tree.
"""
self.root = None
for data in tree_data:
self.insert(data)

def insert(self, data):
"""
Insert a data element into the binary search tree.

Args:
data: The data element to insert.
"""
if self.root is None:
self.root = TreeNode(data)
else:
self._insert_recursive(data, self.root)

def _insert_recursive(self, data, node):
"""
Recursively insert a data element into the binary search tree.

Args:
data: The data element to insert.
node (TreeNode): The current node being considered.
"""
if data <= node.data:
if node.left is None:
node.left = TreeNode(data)
else:
self._insert_recursive(data, node.left)
else:
if node.right is None:
node.right = TreeNode(data)
else:
self._insert_recursive(data, node.right)

def search(self, data):
"""
Search for a specific data element in the binary search tree.

Args:
data: The data element to search for.

Returns:
TreeNode: The node containing the data if found, otherwise None.
"""
return self._search_recursive(data, self.root)

def _search_recursive(self, data, node):
"""
Recursively search for a specific data element in the binary search tree.

Args:
data: The data element to search for.
node (TreeNode): The current node being considered.

Returns:
TreeNode: The node containing the data if found, otherwise None.
"""
if node is None or node.data == data:
return node
if data < node.data:
return self._search_recursive(data, node.left)
else:
return self._search_recursive(data, node.right)

def data(self):
pass
"""Get the root node of the binary search tree."""
return self.root

def sorted_data(self):
pass
"""Get a list of data elements in sorted order."""
return self._inorder_traversal(self.root)

def _inorder_traversal(self, node):
"""
Perform an inorder traversal of the binary search tree.

Args:
node (TreeNode): The current node being considered.

Returns:
list: A list of data elements in sorted order.
"""
if node is None:
return []
return self._inorder_traversal(node.left) + [node.data] + self._inorder_traversal(node.right)
Loading
Loading