diff --git a/__pycache__/proj3.cpython-312.pyc b/__pycache__/proj3.cpython-312.pyc new file mode 100644 index 0000000..6599565 Binary files /dev/null and b/__pycache__/proj3.cpython-312.pyc differ diff --git a/__pycache__/test_p3.cpython-312.pyc b/__pycache__/test_p3.cpython-312.pyc new file mode 100644 index 0000000..c1a4add Binary files /dev/null and b/__pycache__/test_p3.cpython-312.pyc differ diff --git a/__pycache__/test_student.cpython-312.pyc b/__pycache__/test_student.cpython-312.pyc new file mode 100644 index 0000000..e609c3c Binary files /dev/null and b/__pycache__/test_student.cpython-312.pyc differ diff --git a/proj3.py b/proj3.py index c9069e5..2b5cf1b 100644 --- a/proj3.py +++ b/proj3.py @@ -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}" @@ -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 - diff --git a/test_student.py b/test_student.py index e69de29..20a8e87 100644 --- a/test_student.py +++ b/test_student.py @@ -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()