diff --git a/README.md b/README.md index 6975f74..7db0315 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ We encourage you to share your progress and ask questions in the Discussions sec | Day #44 | [Maximum Edge Of A Triangle](https://github.com/LiveGray/100-Days-Of-Rust/tree/main/Week-07/Day-44_Maximum-Edge-Of-A-Triangle) | :white_check_mark: | | Day #45 | [Subtract The Swapped Bits...](https://github.com/LiveGray/100-Days-Of-Rust/tree/main/Week-07/Day-45_Subtract-The-Swapped-Bits-Without-Temp-Storage) | :white_check_mark: | | Day #46 | [Hot Pics Of Danny Devito](https://github.com/LiveGray/100-Days-Of-Rust/tree/main/Week-07/Day-46_Hot-Pics-Of-Danny-Devito) | :white_check_mark: | -| Day #47 | [Zip It](https://github.com/LiveGray/100-Days-Of-Rust/tree/main/Week-07/Day-47_Zip-It) | :white_large_square: | +| Day #47 | [Zip It](https://github.com/LiveGray/100-Days-Of-Rust/tree/main/Week-07/Day-47_Zip-It) | :white_check_mark: | | Day #48 | [Christmas Tree](https://github.com/LiveGray/100-Days-Of-Rust/tree/main/Week-07/Day-48_Christmas-Tree) | :white_large_square: | | Day #49 | [Swimming Pool](https://github.com/LiveGray/100-Days-Of-Rust/tree/main/Week-07/Day-49_Swimming-Pool) | :white_large_square: | | Day #50 | [Tic Tac Toe](https://github.com/LiveGray/100-Days-Of-Rust/tree/main/Week-08/Day-50_Tic-Tac-Toe) | :white_large_square: | diff --git a/Week-07/Day-47_Zip-It/day47/Cargo.toml b/Week-07/Day-47_Zip-It/day47/Cargo.toml new file mode 100644 index 0000000..f4dafab --- /dev/null +++ b/Week-07/Day-47_Zip-It/day47/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "day47" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/Week-07/Day-47_Zip-It/day47/src/lib.rs b/Week-07/Day-47_Zip-It/day47/src/lib.rs new file mode 100644 index 0000000..b7b4e87 --- /dev/null +++ b/Week-07/Day-47_Zip-It/day47/src/lib.rs @@ -0,0 +1,109 @@ +use std::collections::HashMap; +use std::fs; +use std::io::{BufWriter, Write}; + +fn get_bit(byte: u8, position: u8) -> u8 { + (byte >> position) & 1 +} + +pub fn compress(source: &str, destination: &str) -> Result<(), std::io::Error> { + let file_content = fs::read(source)?; + + let mut association = HashMap::new(); + for char in file_content.iter() { + if !association.contains_key(char) { + association.insert(char, association.len() as u8); + } + } + + let file = fs::File::create(destination)?; + let mut dest_file = BufWriter::new(file); + + let mut occurrence: Vec<_> = association.iter().collect(); + occurrence.sort_by_key(|&(_, v)| v); + + for (key, _) in occurrence.iter() { + dest_file.write_all(&[***key])?; + } + + dest_file.write_all(&[**occurrence.last().unwrap().0])?; + + let nbits = (association.len() as f32).log2().ceil() as u8; + + let mut bytes = Vec::new(); + let mut byte = 0; + let mut pos = 0; + for char in file_content.iter() { + let tmp = association[char]; + for i in 0..nbits { + byte |= get_bit(tmp, i) << (7 - pos); + + pos += 1; + if pos == 8 { + pos = 0; + bytes.push(byte); + byte = 0; + } + } + } + + if pos != 0 { + bytes.push(byte); + } + + dest_file.write_all(&bytes)?; + + Ok(()) +} + +fn reverse_bits(mut n: u8) -> u8 { + let mut result = 0; + for _ in 0..8 { + result = (result << 1) | (n & 1); + n >>= 1; + } + result +} + +pub fn uncompress(source: &str, destination: &str) -> Result<(), std::io::Error> { + let file_content = fs::read(source)?; + + let mut association = Vec::new(); + let mut i = 1; + for char in file_content.iter() { + if !association.is_empty() && &char == association.last().unwrap() { + break; + } + association.push(char); + i += 1; + } + + let nbits = (association.len() as f32).log2().ceil() as u8; + let bytes = &file_content[i..]; + + let mut nbit = 0; + let mut current_bits = 0; + let mut uncompressed = Vec::new(); + for byte in bytes { + let byte = reverse_bits(*byte); + for i in 0..8 { + let bit = get_bit(byte, i); + current_bits = (current_bits << 1) | bit; + nbit += 1; + if nbit == nbits { + current_bits = reverse_bits(current_bits); + current_bits >>= 8 - nbits; + uncompressed.push(*association[current_bits as usize]); + nbit = 0; + current_bits = 0; + } + } + } + + let file = fs::File::create(destination)?; + let mut dest_file = BufWriter::new(file); + + dest_file.write_all(&uncompressed)?; + + Ok(()) +} diff --git a/Week-07/Day-47_Zip-It/day47/src/main.rs b/Week-07/Day-47_Zip-It/day47/src/main.rs new file mode 100644 index 0000000..04eb2a0 --- /dev/null +++ b/Week-07/Day-47_Zip-It/day47/src/main.rs @@ -0,0 +1,28 @@ +use std::{env, process::exit}; + +use day47::{compress, uncompress}; + +fn main() { + let args: Vec<_> = env::args().collect(); + if args.len() != 4 { + eprintln!("Usage: day47 (c|u) "); + exit(1); + } + + let f = match args[1].as_str() { + "c" => compress, + "u" => uncompress, + _ => { + eprintln!("The first argument should be c (compress) or u (uncompress)"); + exit(1); + } + }; + + match f(&args[2], &args[3]) { + Ok(_) => println!("Done!"), + Err(e) => { + eprintln!("{}", e); + exit(1); + } + }; +} diff --git a/Week-07/Day-47_Zip-It/day47/tests/compress_uncompress.rs b/Week-07/Day-47_Zip-It/day47/tests/compress_uncompress.rs new file mode 100644 index 0000000..dc93dba --- /dev/null +++ b/Week-07/Day-47_Zip-It/day47/tests/compress_uncompress.rs @@ -0,0 +1,50 @@ +#[cfg(test)] +mod tests { + use std::fs; + + use day47::{compress, uncompress}; + + #[test] + fn librs() { + assert!(compress("src/lib.rs", "compressed1").is_ok()); + assert!(uncompress("compressed1", "uncompressed1").is_ok()); + let original = fs::read("src/lib.rs").unwrap(); + let uncompressed = fs::read("uncompressed1").unwrap(); + assert_eq!(original, uncompressed); + fs::remove_file("compressed1").unwrap(); + fs::remove_file("uncompressed1").unwrap(); + } + + #[test] + fn mainrs() { + assert!(compress("src/main.rs", "compressed2").is_ok()); + assert!(uncompress("compressed2", "uncompressed2").is_ok()); + let original = fs::read("src/main.rs").unwrap(); + let uncompressed = fs::read("uncompressed2").unwrap(); + assert_eq!(original, uncompressed); + fs::remove_file("compressed2").unwrap(); + fs::remove_file("uncompressed2").unwrap(); + } + + #[test] + fn cargotoml() { + assert!(compress("Cargo.toml", "compressed3").is_ok()); + assert!(uncompress("compressed3", "uncompressed3").is_ok()); + let original = fs::read("Cargo.toml").unwrap(); + let uncompressed = fs::read("uncompressed3").unwrap(); + assert_eq!(original, uncompressed); + fs::remove_file("compressed3").unwrap(); + fs::remove_file("uncompressed3").unwrap(); + } + + #[test] + fn cargolock() { + assert!(compress("Cargo.lock", "compressed4").is_ok()); + assert!(uncompress("compressed4", "uncompressed4").is_ok()); + let original = fs::read("Cargo.lock").unwrap(); + let uncompressed = fs::read("uncompressed4").unwrap(); + assert_eq!(original, uncompressed); + fs::remove_file("compressed4").unwrap(); + fs::remove_file("uncompressed4").unwrap(); + } +}