Skip to content

Commit

Permalink
tests added
Browse files Browse the repository at this point in the history
  • Loading branch information
Murat Yildirim committed Nov 30, 2024
1 parent b8ca465 commit d55b772
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 52 deletions.
43 changes: 15 additions & 28 deletions src/compression.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::huffman::{HuffmanLeafNode, HuffmanInternalNode, HuffmanNode};
use std::collections::HashMap;
use std::collections::{BinaryHeap, HashMap};

pub struct CompressionTool {
input: String, // todo: should also support streams
Expand All @@ -12,41 +12,28 @@ impl CompressionTool {
}
}

pub fn compress(&mut self) -> Result<HashMap<char, i32>, String> {
pub fn compress(&mut self) -> Result<HuffmanNode, String> {
let mut map: HashMap<char, i32> = HashMap::new();

for ch in self.input.chars() {
let counter: &mut i32 = map.entry(ch).or_insert(0);
*counter += 1;
}
// Create leaf nodes
let leaf_a = HuffmanLeafNode::new(5, 'a');
let leaf_b = HuffmanLeafNode::new(12, 'b');
let leaf_c = HuffmanLeafNode::new(13, 'c');
let leaf_d = HuffmanLeafNode::new(14, 'd');

// Create internal nodes with leaf nodes as children
let internal_1 = HuffmanInternalNode::new(17, HuffmanNode::Leaf(leaf_a), HuffmanNode::Leaf(leaf_b));
let internal_2 = HuffmanInternalNode::new(27, HuffmanNode::Leaf(leaf_c), HuffmanNode::Leaf(leaf_d));

// Create an internal node with other internal nodes as children
let root = HuffmanInternalNode::new(44, HuffmanNode::Internal(internal_1), HuffmanNode::Internal(internal_2));

// Wrap nodes in the enum
let huffman_tree = HuffmanNode::Internal(root);

// Accessing the weight of the root
println!("Root weight: {}", huffman_tree.weight());

// Accessing the value of leaf nodes
if let Some(value) = huffman_tree.left().unwrap().left().unwrap().value() {
println!("Left leaf value: {}", value);
}
let mut heap: BinaryHeap<HuffmanNode> = BinaryHeap::new();
for (ch, count) in map {
let leaf: HuffmanLeafNode = HuffmanLeafNode::new(count, ch);
heap.push(HuffmanNode::Leaf(leaf));
}

if let Some(value) = huffman_tree.right().unwrap().left().unwrap().value() {
println!("Right leaf value: {}", value);
}
while heap.len() > 1 {
let left: HuffmanNode = heap.pop().unwrap();
let right: HuffmanNode = heap.pop().unwrap();
let combined_weight: i32 = left.weight() + right.weight();
let internal_node: HuffmanInternalNode = HuffmanInternalNode::new(combined_weight, left, right);
heap.push(HuffmanNode::Internal(internal_node));
}

Ok(map)
Ok(heap.pop().unwrap())
}
}
69 changes: 69 additions & 0 deletions src/huffman.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
use std::cmp::Ordering;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::fmt::Result;

struct HuffmanBaseNode {
is_leaf: bool,
weight :i32,
Expand Down Expand Up @@ -31,6 +36,10 @@ impl HuffmanLeafNode {
pub fn value(&self) -> char {
self.element
}

pub fn weight(&self) -> i32 {
self.base.weight
}
}

pub struct HuffmanInternalNode {
Expand All @@ -57,6 +66,10 @@ impl HuffmanInternalNode {
pub fn right(&self) -> &HuffmanNode {
&self.right
}

pub fn weight(&self) -> i32 {
self.base.weight()
}
}

pub enum HuffmanNode {
Expand Down Expand Up @@ -100,3 +113,59 @@ impl HuffmanNode {
}
}
}

// Implementing Ord and PartialOrd for the HuffmanNode so we can use BinaryHeap
impl Ord for HuffmanNode {
fn cmp(&self, other: &Self) -> Ordering {
self.weight().cmp(&other.weight())
}
}

impl PartialOrd for HuffmanNode {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Eq for HuffmanNode {

}

impl PartialEq for HuffmanNode {
fn eq(&self, other: &Self) -> bool {
self.weight() == other.weight()
}
}

// For printing the tree
impl Debug for HuffmanNode {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
HuffmanNode::Leaf(leaf) => write!(f, "Leaf({} : {})", leaf.value(), leaf.base.weight()),
HuffmanNode::Internal(internal) => write!(f, "Internal({}, left: {:?}, right: {:?})", internal.base.weight(), internal.left(), internal.right()),
}
}
}

#[cfg(test)]
mod tests {
use super::*;

// Test HuffmanLeafNode creation
#[test]
fn test_leaf_node_creation() {
let leaf = HuffmanLeafNode::new(3, 'a');
assert_eq!(leaf.value(), 'a');
assert_eq!(leaf.weight(), 3);
}

// Test HuffmanInternalNode creation
#[test]
fn test_internal_node_creation() {
let left = HuffmanNode::Leaf(HuffmanLeafNode::new(2, 'b'));
let right = HuffmanNode::Leaf(HuffmanLeafNode::new(3, 'a'));
let internal_node = HuffmanInternalNode::new(5, left, right);
assert_eq!(internal_node.base.weight, 5);
}
}

8 changes: 2 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@ fn main() {

let mut tool = CompressionTool::new(&content);
match tool.compress() {
Ok(map) => {
for (ch, count) in map {
println!("character: '{}', count: {}", ch, count);
}
},
Err(_) => println!("error compressing content")
Ok(root) => println!("{:?}", root),
Err(e) => println!("Error: {}", e),
}
}
111 changes: 111 additions & 0 deletions tests/compression_integration_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use compression_tool::huffman::HuffmanNode;
use compression_tool::compression::CompressionTool;

#[cfg(test)]
mod tests {
use super::*;

// Helper function to extract the leaf nodes from the Huffman tree for validation
fn extract_leaves(node: &HuffmanNode, leaves: &mut Vec<(char, i32)>) {
match node {
HuffmanNode::Leaf(leaf) => {
leaves.push((leaf.value(), leaf.weight()));
}
HuffmanNode::Internal(internal) => {
extract_leaves(&internal.left(), leaves);
extract_leaves(&internal.right(), leaves);
}
}
}

// Test for a simple input string
#[test]
fn test_huffman_tree_structure() {
let input = "abacab";
let mut tool = CompressionTool::new(input);

// Compress the input to get the Huffman tree root
let root = tool.compress().expect("Compression failed");

// Extract all leaf nodes and their frequencies
let mut leaves = Vec::new();
extract_leaves(&root, &mut leaves);

// The expected frequencies for "a" and "b" in the string "abacab"
let expected_frequencies = vec![('a', 3), ('b', 2), ('c', 1)];

// Sort both vectors so we can compare them
leaves.sort_by(|a, b| a.0.cmp(&b.0)); // Sort by character
let mut expected_frequencies = expected_frequencies;
expected_frequencies.sort_by(|a, b| a.0.cmp(&b.0)); // Sort by character

// Check that the leaves match the expected frequencies
assert_eq!(leaves, expected_frequencies);
}

// Test for a case with a more complex string
#[test]
fn test_complex_huffman_tree() {
let input = "this is an example of huffman compression";
let mut tool = CompressionTool::new(input);

// Compress the input to get the Huffman tree root
let root = tool.compress().expect("Compression failed");

// Extract all leaf nodes and their frequencies
let mut leaves = Vec::new();
extract_leaves(&root, &mut leaves);

// Expected frequencies for a more complex string (you can manually calculate or expect a certain structure)
let expected_frequencies = vec![
(' ', 6), ('a', 3), ('e', 3), ('s', 4), ('i', 3),
('n', 3), ('t', 1), ('h', 2), ('m', 3), ('o', 3),
('f', 3), ('l', 1), ('x', 1), ('p', 2), ('c', 1),
('r', 1), ('u', 1),
];

// Sort both vectors so we can compare them
leaves.sort_by(|a, b| a.0.cmp(&b.0)); // Sort by character
let mut expected_frequencies = expected_frequencies;
expected_frequencies.sort_by(|a, b| a.0.cmp(&b.0)); // Sort by character

// Check that the leaves match the expected frequencies
assert_eq!(leaves, expected_frequencies);
}

// Test to check if the tree is properly built (you can manually check if internal nodes are correct)
#[test]
fn test_tree_structure() {
let input = "aaabbbcc";
let mut tool = CompressionTool::new(input);

// Compress the input to get the Huffman tree root
let root = tool.compress().expect("Compression failed");

// We should have a tree with only two internal nodes (since we only have three distinct characters)
let internal_count = count_internal_nodes(&root);
assert_eq!(internal_count, 2, "The tree should have 2 internal nodes");

// Also check the total weight of the tree (should be equal to the sum of character frequencies)
let total_weight = sum_weights(&root);
assert_eq!(total_weight, input.len() as i32, "The total weight should be equal to the length of the input string");
}

// Helper function to count the number of internal nodes
fn count_internal_nodes(node: &HuffmanNode) -> i32 {
match node {
HuffmanNode::Leaf(_) => 0,
HuffmanNode::Internal(internal) => {
1 + count_internal_nodes(&internal.left()) + count_internal_nodes(&internal.right())
}
}
}

// Helper function to sum the weights of all nodes (leaf and internal)
fn sum_weights(node: &HuffmanNode) -> i32 {
match node {
HuffmanNode::Leaf(leaf) => leaf.weight(),
HuffmanNode::Internal(internal) => sum_weights(&internal.left()) + sum_weights(&internal.right()),
}
}
}
18 changes: 0 additions & 18 deletions tests/integration_test.rs

This file was deleted.

0 comments on commit d55b772

Please sign in to comment.