Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 __pycache__/proj3.cpython-312.pyc
Binary file not shown.
Binary file added __pycache__/test_p3.cpython-312.pyc
Binary file not shown.
Binary file added __pycache__/test_student.cpython-312.pyc
Binary file not shown.
141 changes: 102 additions & 39 deletions proj3.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class Node:
freq: int
char: str
left: Node | None = None
right: Node | None = None
right: Node | None = None

def __str__(self):
return f"Node: {self.char}, Freq: {self.freq}"
Expand All @@ -16,57 +16,120 @@ class MinHeap:
data: list[Node] = field(default_factory=list)

def heapify_up(heap: MinHeap, index: int) -> MinHeap:

data = heap.data[:]
while index > 0:
parent_index = (index - 1) // 2
if data[index] < data[parent_index]:
data[index], data[parent_index] = data[parent_index], data[index]
index = parent_index
else:
break
return MinHeap(data)

def insert(heap: MinHeap, element: Node) -> MinHeap:

data = heap.data[:] + [element]
return heapify_up(MinHeap(data), len(data) - 1)

def heapify_down(heap: MinHeap, index: int) -> MinHeap:


data = heap.data[:]
size = len(data)
while True:
left_index = 2 * index + 1
right_index = 2 * index + 2
smallest = index

if left_index < size and data[left_index] < data[smallest]:
smallest = left_index
if right_index < size and data[right_index] < data[smallest]:
smallest = right_index
if smallest == index:
break
data[index], data[smallest] = data[smallest], data[index]
index = smallest
return MinHeap(data)

def extract_min(heap: MinHeap) -> tuple[MinHeap, Node]:




def count_frequency(s: str)-> dict[str,int]:

pass

if not heap.data:
raise IndexError("extract_min from empty heap")
data = heap.data[:]
min_node = data[0]
if len(data) == 1:
return MinHeap([]), min_node
new_data = data[:-1]
new_data[0] = data[-1]
return heapify_down(MinHeap(new_data), 0), min_node

def count_frequency(s: str) -> dict[str, int]:
frequency: dict[str, int] = {}
for char in s:
frequency[char] = frequency.get(char, 0) + 1
return frequency

def create_priority_queue(frequency: dict[str, int]) -> MinHeap:

pass



def build_tree(priority_queue: MinHeap) -> Node:
pass




def generate_codes(node: Node | None, prefix="", code: dict | None =None)-> dict:
heap = MinHeap([])
for char in sorted(frequency):
heap = insert(heap, Node(frequency[char], char))
return heap

def build_tree_from_queue(priority_queue: MinHeap) -> Node | None:
heap = priority_queue
if not heap.data:
return None
while len(heap.data) > 1:
heap, left = extract_min(heap)
heap, right = extract_min(heap)
# Set internal node char to the minimum child char to provide deterministic tie-breaking
combined_char = min(left.char, right.char)
combined = Node(left.freq + right.freq, combined_char, left, right)
heap = insert(heap, combined)
return heap.data[0]

def build_tree(priority_queue: MinHeap) -> Node | None:
return build_tree_from_queue(priority_queue)

def generate_codes(node: Node | None, prefix: str = "", code: dict | None = None) -> dict:
if code is None:
code = {}
pass


def encode(s: str, codes: dict)-> str:
pass


def decode(encoded_string: str, root: Node):
pass

def huffman_encoding(s:str):
code = {}
if node is None:
return code
if node.left is None and node.right is None:
code[node.char] = prefix or "0"
return code
if node.left is not None:
generate_codes(node.left, prefix + "0", code)
if node.right is not None:
generate_codes(node.right, prefix + "1", code)
return code

def encode(s: str, codes: dict) -> str:
return "".join(codes[char] for char in s)

def decode(encoded_string: str, root: Node | None) -> str:
if root is None:
return ""
if root.left is None and root.right is None:
return root.char * len(encoded_string)
decoded_chars: list[str] = []
node = root
for bit in encoded_string:
if bit == "0":
node = node.left
else:
node = node.right
if node is None:
raise ValueError("Encoded string does not match Huffman tree")
if node.left is None and node.right is None:
decoded_chars.append(node.char)
node = root
return "".join(decoded_chars)


def huffman_encoding(s: str):
#Do Not Change this function
frequency = count_frequency(s)
pq = create_priority_queue(frequency)
root = build_tree_from_queue(pq)
codes = generate_codes(root)
encoded_string = encode(s, codes)
decoded_string = decode(encoded_string,root)
decoded_string = decode(encoded_string, root)
return encoded_string, decoded_string, codes

50 changes: 50 additions & 0 deletions test_student.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import unittest
from proj3 import count_frequency, create_priority_queue, insert, extract_min, generate_codes, encode, decode, build_tree_from_queue, huffman_encoding, MinHeap, Node

class TestStudentHuffman(unittest.TestCase):
def test_count_frequency_empty(self):
self.assertEqual(count_frequency(""), {})

def test_count_frequency_repeated(self):
self.assertEqual(count_frequency("aaabbc"), {"a": 3, "b": 2, "c": 1})

def test_heap_insert_and_extract(self):
heap = MinHeap([])
heap = insert(heap, Node(5, "z"))
heap = insert(heap, Node(1, "a"))
heap = insert(heap, Node(3, "m"))
new_heap, min_node = extract_min(heap)
self.assertEqual(min_node.char, "a")
self.assertEqual(min_node.freq, 1)
self.assertEqual(len(new_heap.data), 2)
self.assertEqual(new_heap.data[0].char, "m")

def test_build_tree_single_character(self):
pq = create_priority_queue({"X": 4})
root = build_tree_from_queue(pq)
self.assertIsNotNone(root)
self.assertIsNone(root.left)
self.assertIsNone(root.right)
codes = generate_codes(root)
self.assertEqual(codes, {"X": "0"})
self.assertEqual(encode("XXXX", codes), "0000")
self.assertEqual(decode("0000", root), "XXXX")

def test_encode_decode_empty(self):
encoded, decoded, codes = huffman_encoding("")
self.assertEqual(encoded, "")
self.assertEqual(decoded, "")
self.assertEqual(codes, {})

def test_generate_codes_tree_shape(self):
frequency = {"a": 2, "b": 1, "c": 1}
pq = create_priority_queue(frequency)
root = build_tree_from_queue(pq)
codes = generate_codes(root)
self.assertEqual(set(codes.keys()), {"a", "b", "c"})
self.assertGreaterEqual(len(codes["a"]), 1)
self.assertGreaterEqual(len(codes["b"]), 1)
self.assertGreaterEqual(len(codes["c"]), 1)

if __name__ == "__main__":
unittest.main()