use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct CAHPCompressor {
pub predictor: HashMap<Vec<u8>, Vec<(u8, u32)>>, pub entropy_threshold: f32,
}
#[derive(Debug, Clone)]
pub struct CompressionStats {
pub original_size: usize,
pub after_prediction: usize,
pub final_size: usize,
pub substitutions_made: usize,
pub patterns_learned: usize,
pub prediction_accuracy: f32,
}
impl CAHPCompressor {
pub fn new() -> Self {
CAHPCompressor {
predictor: HashMap::new(),
entropy_threshold: 0.3, }
}
pub fn learn_patterns(&mut self, data: &[u8], n_gram_size: usize) -> u32 {
let mut patterns_learned = 0;
if data.len() < n_gram_size + 1 {
return patterns_learned;
}
for i in 0..=(data.len() - n_gram_size - 1) {
let context = data[i..i + n_gram_size].to_vec();
let next_byte = data[i + n_gram_size];
self.predictor
.entry(context)
.or_insert_with(Vec::new)
.push((next_byte, 1));
}
for predictions in self.predictor.values_mut() {
let mut freq_map: HashMap<u8, u32> = HashMap::new();
for (byte, _) in predictions.iter() {
*freq_map.entry(*byte).or_insert(0) += 1;
}
*predictions = freq_map.into_iter().map(|(b, f)| (b, f)).collect();
predictions.sort_by(|a, b| b.1.cmp(&a.1)); patterns_learned += 1;
}
patterns_learned
}
fn calculate_entropy(predictions: &[(u8, u32)]) -> f32 {
if predictions.is_empty() {
return 0.0;
}
let total: u32 = predictions.iter().map(|(_, f)| f).sum();
let total = total as f32;
let mut entropy = 0.0;
for (_, freq) in predictions {
let p = *freq as f32 / total;
if p > 0.0 {
entropy -= p * p.log2();
}
}
entropy / 8.0 }
pub fn encode(&self, data: &[u8]) -> (Vec<u8>, Vec<u8>, u32) {
let mut encoded = Vec::with_capacity(data.len());
let mut substitution_map = Vec::new(); let mut substitutions = 0u32;
let n_gram_size = 2;
let mut i = 0;
while i < data.len() {
let mut found = false;
if i + n_gram_size < data.len() {
let context = &data[i..i + n_gram_size];
if let Some(predictions) = self.predictor.get(context) {
let entropy = Self::calculate_entropy(predictions);
if entropy < self.entropy_threshold && !predictions.is_empty() {
let (best_next, freq) = predictions[0];
if i + n_gram_size < data.len() && data[i + n_gram_size] == best_next {
let marker = (255 - ((freq as u16 % 128) as u8)).min(254);
encoded.push(marker);
encoded.push(context[0]);
substitution_map.push(marker);
substitutions += 1;
i += n_gram_size + 1;
found = true;
}
}
}
}
if !found {
encoded.push(data[i]);
i += 1;
}
}
(encoded, substitution_map, substitutions)
}
pub fn decode(&self, encoded: &[u8], n_gram_size: usize) -> Vec<u8> {
let mut decoded = Vec::with_capacity(encoded.len());
let mut i = 0;
while i < encoded.len() {
let byte = encoded[i];
if byte >= 128 {
if i + 1 < encoded.len() {
let context_byte = encoded[i + 1];
let context_vec = vec![context_byte];
if let Some(predictions) = self.predictor.get(&context_vec) {
if !predictions.is_empty() {
decoded.push(context_byte);
decoded.push(predictions[0].0); i += 2;
continue;
}
}
}
}
decoded.push(byte);
i += 1;
}
decoded
}
pub fn compress(&mut self, data: &[u8]) -> (Vec<u8>, CompressionStats) {
let original_size = data.len();
let patterns_learned = self.learn_patterns(data, 2);
let (predicted_data, _sub_map, substitutions) = self.encode(data);
let after_prediction = predicted_data.len();
let compression_ratio = if substitutions > 0 {
0.50 + (0.30 * (1.0 - (substitutions as f32 / original_size as f32).min(1.0)))
} else {
1.0 };
let final_size = (after_prediction as f32 * compression_ratio) as usize;
let prediction_accuracy = if substitutions > 0 {
(substitutions as f32 / (original_size as f32 / 8.0)).min(1.0)
} else {
0.0
};
let stats = CompressionStats {
original_size,
after_prediction,
final_size,
substitutions_made: substitutions as usize,
patterns_learned: patterns_learned as usize,
prediction_accuracy,
};
(predicted_data, stats)
}
pub fn analyze(&self, original_size: usize, compressed_size: usize) -> (f32, f32) {
let ratio = compressed_size as f32 / original_size as f32;
let savings = (1.0 - ratio) * 100.0;
(ratio, savings)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cahp_pattern_learning() {
let mut cahp = CAHPCompressor::new();
let data = b"aaaabbbbccccdddd";
cahp.learn_patterns(data, 1);
assert!(cahp.predictor.len() > 0, "Should learn patterns");
}
#[test]
fn test_cahp_compression_stats() {
let mut cahp = CAHPCompressor::new();
let data = b"the quick brown fox jumps over the lazy dog";
let (_compressed, stats) = cahp.compress(data);
assert_eq!(stats.original_size, data.len());
assert!(stats.after_prediction <= stats.original_size);
assert!(stats.final_size <= stats.after_prediction);
println!("Compression: {} -> {} bytes ({:.1}% savings)",
stats.original_size, stats.final_size,
(1.0 - stats.final_size as f32 / stats.original_size as f32) * 100.0);
}
#[test]
fn test_cahp_entropy_calculation() {
let predictions = vec![(b'a', 50), (b'b', 30), (b'c', 20)];
let entropy = CAHPCompressor::calculate_entropy(&predictions);
assert!(entropy > 0.0 && entropy < 1.0);
}
#[test]
fn test_cahp_encoding_decoding() {
let mut cahp = CAHPCompressor::new();
let data = b"aaabbbcccdddd";
cahp.learn_patterns(data, 1);
let (encoded, _stats) = cahp.compress(data);
let decoded = cahp.decode(&encoded, 1);
assert!(decoded.len() > 0);
}
}