Skip to content

Commit

Permalink
document library
Browse files Browse the repository at this point in the history
  • Loading branch information
tibvdm committed May 16, 2024
1 parent 84edc18 commit a618019
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 4 deletions.
69 changes: 69 additions & 0 deletions bitarray/src/binary.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,45 @@
//! This module provides utilities for reading and writing the bitarray as binary.
use std::io::{BufRead, Read, Result, Write};

use crate::BitArray;

/// The `Binary` trait provides methods for reading and writing a struct as binary.
pub trait Binary {
/// Writes the struct as binary to the given writer.
///
/// # Arguments
///
/// * `writer` - The writer to write the binary data to.
///
/// # Returns
///
/// Returns `Ok(())` if the write operation is successful, or an `Err` if an error occurs.
fn write_binary<W: Write>(&self, writer: W) -> Result<()>;

/// Reads binary data into a struct from the given reader.
///
/// # Arguments
///
/// * `reader` - The reader to read the binary data from.
///
/// # Returns
///
/// Returns `Ok(())` if the read operation is successful, or an `Err` if an error occurs.
fn read_binary<R: BufRead>(&mut self, reader: R) -> Result<()>;
}

/// Implementation of the `Binary` trait for the `BitArray` struct.
impl<const B: usize> Binary for BitArray<B> {
/// Writes the binary representation of the `BitArray` to the given writer.
///
/// # Arguments
///
/// * `writer` - The writer to which the binary data will be written.
///
/// # Errors
///
/// Returns an error if there was a problem writing to the writer.
fn write_binary<W: Write>(&self, mut writer: W) -> Result<()> {
for value in self.data.iter() {
writer.write_all(&value.to_le_bytes())?;
Expand All @@ -16,6 +48,15 @@ impl<const B: usize> Binary for BitArray<B> {
Ok(())
}

/// Reads the binary representation of the `BitArray` from the given reader.
///
/// # Arguments
///
/// * `reader` - The reader from which the binary data will be read.
///
/// # Errors
///
/// Returns an error if there was a problem reading from the reader.
fn read_binary<R: BufRead>(&mut self, mut reader: R) -> Result<()> {
self.data.clear();

Expand All @@ -36,6 +77,17 @@ impl<const B: usize> Binary for BitArray<B> {
}
}

/// Fills the buffer with data read from the input.
///
/// # Arguments
///
/// * `input` - The input source to read data from.
/// * `buffer` - The buffer to fill with data.
///
/// # Returns
///
/// Returns a tuple `(finished, bytes_read)` where `finished` indicates whether the end of the input is reached,
/// and `bytes_read` is the number of bytes read into the buffer.
fn fill_buffer<T: Read>(input: &mut T, buffer: &mut Vec<u8>) -> (bool, usize) {
// Store the buffer size in advance, because rust will complain
// about the buffer being borrowed mutably while it's borrowed
Expand Down Expand Up @@ -71,6 +123,14 @@ fn fill_buffer<T: Read>(input: &mut T, buffer: &mut Vec<u8>) -> (bool, usize) {
mod tests {
use super::*;

pub struct ErrorInput;

impl Read for ErrorInput {
fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
Err(std::io::Error::new(std::io::ErrorKind::Other, "read error"))
}
}

#[test]
fn test_fill_buffer() {
let input_str = "a".repeat(8_000);
Expand All @@ -90,6 +150,15 @@ mod tests {
}
}

#[test]
#[should_panic]
fn test_fill_buffer_read_error() {
let mut input = ErrorInput;
let mut buffer = vec![0; 800];

fill_buffer(&mut input, &mut buffer);
}

#[test]
fn test_write_binary() {
let mut bitarray = BitArray::<40>::with_capacity(4);
Expand Down
60 changes: 56 additions & 4 deletions bitarray/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
pub mod binary;
//! This module contains the `BitArray` struct and its associated methods.
mod binary;

/// Re-export the `Binary` trait.
pub use binary::Binary;

/// A fixed-size bit array implementation.
pub struct BitArray<const B: usize> {
pub data: Vec<u64>,
pub mask: u64,
pub len: usize,
/// The underlying data storage for the bit array.
data: Vec<u64>,
/// The mask used to extract the relevant bits from each element in the data vector.
mask: u64,
/// The length of the bit array.
len: usize,
}

impl<const B: usize> BitArray<B> {
/// Creates a new `BitArray` with the specified capacity.
///
/// # Arguments
///
/// * `capacity` - The number of bits the `BitArray` can hold.
///
/// # Returns
///
/// A new `BitArray` with the specified capacity.
pub fn with_capacity(capacity: usize) -> Self {
Self {
data: vec![0; capacity * B / 64 + 1],
Expand All @@ -15,43 +33,77 @@ impl<const B: usize> BitArray<B> {
}
}

/// Retrieves the value at the specified index in the `BitArray`.
///
/// # Arguments
///
/// * `index` - The index of the value to retrieve.
///
/// # Returns
///
/// The value at the specified index.
pub fn get(&self, index: usize) -> u64 {
let start_block = index * B / 64;
let start_block_offset = index * B % 64;

// If the value is contained within a single block
if start_block_offset + B <= 64 {
// Shift the value to the right so that the relevant bits are in the least significant position
// Then mask out the irrelevant bits
return self.data[start_block] >> (64 - start_block_offset - B) & self.mask;
}

let end_block = (index + 1) * B / 64;
let end_block_offset = (index + 1) * B % 64;

// Extract the relevant bits from the start block and shift them {end_block_offset} bits to the left
let a = self.data[start_block] << end_block_offset;

// Extract the relevant bits from the end block and shift them to the least significant position
let b = self.data[end_block] >> (64 - end_block_offset);

// Paste the two values together and mask out the irrelevant bits
(a | b) & self.mask
}

/// Sets the value at the specified index in the `BitArray`.
///
/// # Arguments
///
/// * `index` - The index of the value to set.
/// * `value` - The value to set at the specified index.
pub fn set(&mut self, index: usize, value: u64) {
let start_block = index * B / 64;
let start_block_offset = index * B % 64;

// If the value is contained within a single block
if start_block_offset + B <= 64 {
// Clear the relevant bits in the start block
self.data[start_block] &= !(self.mask << (64 - start_block_offset - B));
// Set the relevant bits in the start block
self.data[start_block] |= value << (64 - start_block_offset - B);
return;
}

let end_block = (index + 1) * B / 64;
let end_block_offset = (index + 1) * B % 64;

// Clear the relevant bits in the start block
self.data[start_block] &= !(self.mask >> start_block_offset);
// Set the relevant bits in the start block
self.data[start_block] |= value >> end_block_offset;

// Clear the relevant bits in the end block
self.data[end_block] &= !(self.mask << (64 - end_block_offset));
// Set the relevant bits in the end block
self.data[end_block] |= value << (64 - end_block_offset);
}

/// Returns the length of the `BitArray`.
///
/// # Returns
///
/// The length of the `BitArray`.
pub fn len(&self) -> usize {
self.len
}
Expand Down

0 comments on commit a618019

Please sign in to comment.