Skip to content

Commit

Permalink
added comments
Browse files Browse the repository at this point in the history
  • Loading branch information
Aayush Badoni authored and Aayush Badoni committed Apr 22, 2024
1 parent 7495b50 commit 606eb65
Show file tree
Hide file tree
Showing 29 changed files with 1,210 additions and 566 deletions.
54 changes: 45 additions & 9 deletions practice/affine-cipher/affine_cipher.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,47 @@
"""
Approach:
- The provided code implements a simple affine cipher, which is a type of substitution cipher.
- The `encode` function encodes plain text using the formula E(x) = (ax + b) mod m, where 'a' and 'm' are coprime, and 'b' is any integer.
- The `decode` function decodes the ciphered text using the formula D(x) = a^(-1)(x - b) mod m.
- The `is_coprime` function checks whether two numbers are coprime.
- The implementation ensures that 'a' and the length of the alphabet are coprime to ensure that every letter is mapped to a unique value.
"""

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):
"""
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:
Expand All @@ -37,13 +55,30 @@ def encode(plain_text, a, b):
out += alphabet[index]
else:
out += str(index - 1000)

return out

def decode(ciphered_text, a, b):
"""
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:
Expand All @@ -52,6 +87,7 @@ def decode(ciphered_text, a, b):
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]
Expand Down
17 changes: 14 additions & 3 deletions practice/alphametics/alphametics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
from typing import Dict, Optional, List, Tuple


def parse_puzzle(puzzle: str) -> Tuple[List, str]:
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]: A tuple containing a list of words and the result.
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())

Expand All @@ -28,17 +30,26 @@ def solve(puzzle: str) -> Optional[Dict[str, int]]:
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))
if "0" in perm[-len(nonzero) :]:
# 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
52 changes: 41 additions & 11 deletions practice/bank-account/bank_account.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,79 @@
class BankAccount:
def __init__(self):
# Initialize balance to zero and account status to closed
"""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):
# Check if account is not open, raise ValueError if closed
"""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):
# Check if amount is less than 0, raise ValueError if negative
"""Raise ValueError if the amount is less than zero."""
if amount < 0:
raise ValueError("amount must be greater than 0")

def get_balance(self):
# Check if account is open, return balance if open
"""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):
# Check if account is already open, raise ValueError if already open
"""Open the bank account.
Raises:
ValueError: If the account is already open.
"""
if self._opened:
raise ValueError("account already open")
# Set account status to open
self._opened = True

def deposit(self, amount):
# Check if account is open and amount is valid
"""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)
# Add deposit amount to balance
self._balance += amount

def withdraw(self, amount):
# Check if account is open, amount is valid, and balance is sufficient
"""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")
# Deduct withdrawal amount from balance
self._balance -= amount

def close(self):
# Check if account is open, close the account, reset balance to zero
"""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
72 changes: 62 additions & 10 deletions practice/binary-search-tree/binary_search_tree.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,58 @@
"""
Approach:
- The provided code defines two classes: TreeNode and BinarySearchTree.
- TreeNode represents a single node in a binary search tree, while BinarySearchTree represents the binary search tree itself.
- TreeNode contains the data stored in the node and references to its left and right child nodes.
- BinarySearchTree initializes with a list of data elements and inserts each element into the binary search tree.
- Insertion is done recursively by comparing the data with each node and traversing either the left or right subtree accordingly.
- The search method recursively searches for a specific data element in the binary search tree.
"""

class TreeNode:
"""Represents a single node in a binary search tree."""

def __init__(self, data, left=None, 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):
"""
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)
Expand All @@ -43,9 +65,28 @@ def _insert_recursive(self, data, node):
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:
Expand All @@ -54,12 +95,23 @@ def _search_recursive(self, data, node):
return self._search_recursive(data, node.right)

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

def sorted_data(self):
"""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

0 comments on commit 606eb65

Please sign in to comment.