From a3ba2d278d79870ad609c2663117542e2ce8fe44 Mon Sep 17 00:00:00 2001 From: Raghav Swaminathan Date: Tue, 19 May 2026 01:55:56 +0000 Subject: [PATCH 1/2] proj3: deterministic internal node char to match Huffman tests --- proj3.py | 141 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 102 insertions(+), 39 deletions(-) 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 - From 1b26ceae5975312c7840cac2c6de4be7ab272432 Mon Sep 17 00:00:00 2001 From: Raghav Swaminathan Date: Tue, 19 May 2026 01:59:23 +0000 Subject: [PATCH 2/2] Save local changes --- __pycache__/proj3.cpython-312.pyc | Bin 0 -> 6892 bytes __pycache__/test_p3.cpython-312.pyc | Bin 0 -> 2354 bytes __pycache__/test_student.cpython-312.pyc | Bin 0 -> 4203 bytes test_student.py | 50 +++++++++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 __pycache__/proj3.cpython-312.pyc create mode 100644 __pycache__/test_p3.cpython-312.pyc create mode 100644 __pycache__/test_student.cpython-312.pyc diff --git a/__pycache__/proj3.cpython-312.pyc b/__pycache__/proj3.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..659956510831fded89d84161c88e6f5910a8bf7f GIT binary patch literal 6892 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 JBV%00{|lNNeF^{o literal 0 HcmV?d00001 diff --git a/__pycache__/test_p3.cpython-312.pyc b/__pycache__/test_p3.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1a4add3655ef2231bede61aa7df2ba93c5a9beb GIT binary patch literal 2354 zcmd6o&rcgi6vt=wH^z_zl7a~gF1G!79PTJMhvlPhsM`vxd zwrnYtsLHWY58=Q8RXu>X@F(P0sTbU)iZy9ZJ@tm-oN`It+g%JrKrgApSo-?Cc{6Wj z_A@_z?dgdSC@+5OUZ`n={Ef~|QA49$0^GWFgiLU^AdnZqEqThUD}WWmy$!; zsF3f!+Gurow@sBKjEqp{9M>(1ND7N-nkiFIQ%LY9gO8F*D1y3z76o+#om-bUPEmB* zp6BrL%vTlLUDt);Iqs~8&KF9RvgMUId~tEo`KILL$_@)4I85|*Y5#JSj+ea2iwlr*(n^0aVNN8=z{Dy|j+d$;;@kdmYUd7M zHNG21Qr>4E_epEu+)}i4_KhuLuysDZc6xK>q1udnxYYY+d}8z3@A3DW#`z`vhk@mb zEhGBa7;GAY%L7~SiATodo{H5s9~sGA|IBLdA4akb1CU&YH#oQ|e}}AranKH72J2eU zj@d4WWQ3lx-BQu5iarx|h5z&S3+@<>mw}K>*etkvKvKLLsyH6kgvNU?Osj(0g64Ry z@0Iu|Y|#(07d1bN(Wnn99I-ZfD9W$}X7s)QL71ZmGwH*8eQj*>*26Qu+}gVRc{66c z3Zi@jhWQs!1uA@^t6=OWx*{Djl3GS6Q`6H^LP3nY3+60nDj^9vCFt}~OuBp~*ta%v zC=d(}LQd~C2+|lq8W+N&)!&R=`JW;DbN~dVt;oj=gPsxc)O1>?aF9X_;UR*{Ru(kP zufRh1ILZ;{#0Z(BYr{t%Nne08-W?DmGK@sxoy+cnX6))KW0Fbe!mzA@op&rtL@X;` zWEB_fsAb_Z5k&Yft_xmu!jmX(fvgk%X!+-#mU_?fHUb;S?%``7(YQp_bsWm<*mP13yvPh9}EU|2Fngun)fq=I-dvjcdWQ-{tQ? z0B>*a9guCAQu>6P_>07!DPz=JzVvwLa&zeNGXl=`O-hvLQtdZ6)>2O0zyAG=Z*P3} L@wQBqAwTm=c~b#n literal 0 HcmV?d00001 diff --git a/__pycache__/test_student.cpython-312.pyc b/__pycache__/test_student.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e609c3c33cccca4c68e8d2f30c93fd6c6355fd3e GIT binary patch literal 4203 zcmbtXO>7&-6`uVghx!RE+mc1qvL#tTP1%+d$xZs>258j4ZW}R8lLE%V!D7W-S(LRT zJ-ciwl>%JAMFkXSeKUm!FpKm+*B2jq?5QZw6H5vXroQ#0n=1tfkW=5A5X|ZL&HN{2=;1QT)FCnw_+? zI~NIICuIV>V-Zi#d(!GiWL;X9(y=`pXVSeqnJtv^p0UjAyCplHUFU;YW}BXE6j`po za^AWD^Cg?da(UNg9`Ch(=`l0w8BQ+GU%PGRZHC<0f@Qlr20K89$1GcX2bW5@HOugr zZNnc5PHV3dE2ZUS$IKgoJ(s`D5@fs*^=%P zU9n`KDk@3WEEQp^h%bU_ip(A%?5ko)@U$P0dv(D^!Qv1CiJR7=}Y+SQYe zWq8aq&84O69%^L|Rkw#Kd8}~B}4N*+q0yYh(6R6%j~K z-2WVq!@3x5q~`uI{@3xpkJqohw@VjU3RpU;spVR;Oi@8oBNlOv1a1Fd1^Eu)3C8#CQ}yo*3D?Cm}Ua*sSu4n1JM z`_-2EJURHl+WBF1pt|tsdyQoJ6=y~-)=oD@r|WcNi$1s*f=6Ze(dhPQO=;50N9Uv_ zoeJl6PE~F``gr@}M(R@SgC?B`7yJ#Xck8M2v($7wHT_iCrB{y!9V%MDbMZ*fVMygd z_dXmTf%|OA;C^8c!cMJLmIDsqR$;gV>x6(?B2FA>uOQVSj3OxyeMhwD1R^BEa0u4n z1Pa_xfg7R&LK7$Lz~>G4>lm)(Fz_txrE&-pYwj?W(r=WkhghXGyx-Z&0hAn#&Wk;Jcha!fx}S03^65$6}fnq ztE^A}UQtdQ36U`oB5f6g3zfqflpYX&P>|ScW4>FM{0||FQWtAA2$lYc>OyUx`t3&l zc-tMT6A#o_FXd4VS3wM&F)cF$AYtXewsF5Qw0PVv4J~#JSdLhD zSXkKSGz@ig#Pv;sFZ`SGgt@wj8mDfWZSI$%?z);=CgH?H# zUO2A&5RYyNJVF%0if2aw1PTj+DD7^_Z3RJyI-5#V8bHjp_qqyV)!6n-$NpN z74rxt>{iA;C!F>01IT~rxGQGS-gu*%%XSs{Cj7wt0}v=oN4`-VuZ`DFU*1X}EPbcZ zf2AFgIsgo0+MhqtxAjkNHBwW5e!oet3Fkf!zW*ns`flyQAAkH=^65a6&VQ}>T{Uw7=}}@N`Srw?=#?8XRi4xY#8-*0m%%K?<4sElIuVo6S0>lc^Kw0 zK@iVewCH`ZFH73oeoWFb(ta$VeQSRxp}n;~q-v7~eN^+y10jQFaZISCjPwAfrF_mq zb8xK)FRFR*?(!W$RBWPH6Nx9xIU;fSFeNHGbaGD7WD6#|-^AH&;GBwoVLyRxbo8(2 zHUIW+vkzba!`EE|@0c!IrA(w{`Ab*{=zm223-yw8wKDuHHC0bdeL-OGLYIiz dx3%$4CH1*FaPP)%7k|C@oA+NRL>&>o`!D^qc8mZ3 literal 0 HcmV?d00001 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()