diff --git a/Cargo.toml b/Cargo.toml index 53c2e14..475ce99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,5 @@ edition = "2021" authors = ["raa "] description = "Custom data structures crate for Rust" license = "MIT" -repository = "https://github.com/raa-dev/rust-ds" \ No newline at end of file +repository = "https://github.com/raa-dev/rust-ds" +categories = ["data structures", "algorithms"] \ No newline at end of file diff --git a/README.md b/README.md index 1767730..92b507d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,28 @@ # Data Structures Crate +[github](https://github.com/raa-dev/rust-ds) +[crates.io](https://crates.io/crates/rust-ds) +[docs.rs](https://docs.rs/rust-ds) + This is a library of data structures implemented in Rust. The goal of this project is to provide a collection of data structures that are not available in the Rust standard library. The library is designed to be easy to use and efficient. This crate still in early stage of development, but the intended objective is to provide the following data structures: -* [Linked List](src/linked_list/README.md) -* [Double Linked List](src/double_linked_list/README.md) +* [Linked List](src/linked_lists/README.md) +* [Double Linked List](src/linked_lists/README.md) +* [Hash Table](src/hash_table/README.md) * Stack * Queue * Tree * Graph -* Heap \ No newline at end of file +* Heap + +## Usage +Add this dependency to your `Cargo.toml` +```toml +[dependencies] +rust-ds = "0.1.1" +``` + +## License + +Licensed under +- MIT license ([LICENSE-MIT](https://github.com/Amanieu/parking_lot/blob/HEAD/LICENSE-MIT) or https://opensource.org/licenses/MIT) \ No newline at end of file diff --git a/src/hash_table/README.md b/src/hash_table/README.md new file mode 100644 index 0000000..57df322 --- /dev/null +++ b/src/hash_table/README.md @@ -0,0 +1,42 @@ +# Hash Table + +A Hash Table orders the data as pairs of keys and values. This enables +efficient data retrieval based on the key. Each key is hashed to produce +an index at which the corresponding value is stored. This allows for +average-case constant time complexity, O(1), for both insertion and +lookup operations. + +## Operations +- **Insert**: Add a key-value pair to the hash table. +- **Remove**: Remove a key-value pair from the hash table. +- **Get**: Retrieve the value associated with a given key. +- **Update**: Modify the value associated with a given key. +- **Resize**: Adjust the size of the hash table to maintain efficient operations. + +## Usage + +```rust +use rust_ds::hash_table::Table; + +fn main() { + let mut table = Table::new(4); // You can use default instead to use a 64 length capacity + + // Insert key-value pairs + table.insert("key1", "value1"); + table.insert("key2", "value2"); + + // Retrieve a value + if let Some(value) = table.get("key1") { + println!("The value for 'key1' is {}", value); + } + + // Update a value + table.update("key1", "new_value1"); + + // Remove a key-value pair + table.remove("key2"); + + // Resize the hash table + table.resize(32); +} +``` \ No newline at end of file diff --git a/src/hash_table/errors.rs b/src/hash_table/errors.rs new file mode 100644 index 0000000..7750213 --- /dev/null +++ b/src/hash_table/errors.rs @@ -0,0 +1,24 @@ +use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; + +pub type Result = core::result::Result; + +#[derive(Debug)] +pub enum Error { + EmptyTable, + KeyNotFound, + InvalidCapacity, +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self { + Error::EmptyTable => write!(f, "Operation failed: Table is empty"), + Error::KeyNotFound => { + write!(f, "Operation failed: Key not found in table") + } + Error::InvalidCapacity => { + write!(f, "Operation failed: Invalid capacity") + } + } + } +} diff --git a/src/hash_table/mod.rs b/src/hash_table/mod.rs new file mode 100644 index 0000000..4be7709 --- /dev/null +++ b/src/hash_table/mod.rs @@ -0,0 +1,133 @@ +mod errors; + +pub(super) use errors::{Error, Result}; +use std::collections::hash_map::DefaultHasher; +use std::fmt::Debug; +use std::hash::{Hash, Hasher}; + +type KeyPointer = Option<(K, V)>; + +/// `Table` is a simple hash table implementation. +#[derive(Debug)] +pub struct Table +where + K: Clone, + V: Clone, +{ + pub elements: Vec>, + capacity: usize, +} + +impl Table +where + K: Hash + Eq + Debug + Clone, + V: Debug + Clone, +{ + /// Create a new `Table` with the given capacity. + pub fn new(capacity: usize) -> Self { + Self { + elements: vec![None; capacity], + capacity, + } + } + /// Hash the key and return the index. + fn hash(&self, key: &Q) -> usize + where + K: std::borrow::Borrow, + Q: Hash + ?Sized, + { + let mut hasher = DefaultHasher::new(); + key.hash(&mut hasher); + (hasher.finish() as usize) % self.capacity + } + /// Insert a new key-value pair into the table. + pub fn insert(&mut self, key: K, value: V) { + let index = self.hash(&key); + self.elements[index] = Some((key, value)); + } + /// Get the value for the given key. + pub fn get(&self, key: &K) -> Result<&V> { + let index = self.hash(key); + self.elements[index] + .as_ref() + .map(|(_, v)| v) + .ok_or(Error::KeyNotFound) + } + /// Remove the key-value pair from the table. + pub fn remove(&mut self, key: &K) -> Result { + if self.capacity == 0 { + return Err(Error::EmptyTable); + } + let index = self.hash(key); + if let Some((_, value)) = self.elements[index].take() { + Ok(value) + } else { + Err(Error::KeyNotFound) + } + } + /// Update the value for the given key. + pub fn update(&mut self, key: &K) -> Result<&mut V> { + if self.capacity == 0 { + return Err(Error::EmptyTable); + } + let index = self.hash(key); + self.elements[index] + .as_mut() + .map(|(_, v)| v) + .ok_or(Error::KeyNotFound) + } + /// Resize the table to the new capacity. + pub fn resize(&mut self, new_capacity: usize) -> Result<()> { + if new_capacity == 0 { + return Err(Error::InvalidCapacity); + } + + let mut new_table = Table::new(new_capacity); + for element in self.elements.iter().filter_map(|e| e.as_ref()) { + new_table.insert(element.0.clone(), element.1.clone()); + } + self.elements = new_table.elements; + self.capacity = new_capacity; + Ok(()) + } +} +/// Default implementation for `Table`. +impl Default for Table +where + K: Hash + Eq + Debug + Clone, + V: Debug + Clone, +{ + fn default() -> Self { + Self::new(64) + } +} + +// region: --- Tests +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hash_table_ops() { + let mut table = Table::new(16); + table.insert("key1", "value1"); + assert_eq!(table.get(&"key1").unwrap(), &"value1"); + table.remove(&"key1").unwrap(); + assert!(table.get(&"key1").is_err()); + table.insert("key2", "value2"); + if let Ok(v) = table.update(&"key2") { + *v = "value3"; + } + assert_eq!(table.get(&"key2").unwrap(), &"value3"); + } + + #[test] + fn test_hash_table_errors() { + let mut table: Table<&str, &str> = Table::new(16); + assert!(table.get(&"key1").is_err()); + assert!(table.remove(&"key1").is_err()); + assert!(table.update(&"key1").is_err()); + assert!(table.resize(0).is_err()); + } +} +// endregion: --- Tests diff --git a/src/lib.rs b/src/lib.rs index 1321090..6d2b937 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,4 +2,5 @@ //! //! `rust_ds` is a collection of data structures utilities to use in Rust. +pub mod hash_table; pub mod linked_lists; diff --git a/src/linked_lists/README.md b/src/linked_lists/README.md index 0cbcb2e..ea72a31 100644 --- a/src/linked_lists/README.md +++ b/src/linked_lists/README.md @@ -9,3 +9,62 @@ The following operations are defined for a linked list: - **Search**: Searches for an element in the list. - **Update**: Updates an element in the list. - **Get**: Returns the element at a given index. + +## Usage +### Singly Linked List Example + +```rust +use linked_lists::Singly; + +fn main() { + let mut list = Singly::new(); + list.append(1); + list.append(2); + list.append(3); + println!("List after appending elements: {:?}", list); + + list.pop(); + println!("List after popping an element: {:?}", list); + + list.remove(1); + println!("List after removing an element: {:?}", list); + + let found = list.search(2); + println!("Element 2 found: {}", found); + + list.update(0, 4); + println!("List after updating an element: {:?}", list); + + let element = list.get(0); + println!("Element at index 0: {:?}", element); +} +``` + +### Doubly Linked List Example + +```rust +use linked_lists::Double; + +fn main() { + let mut list = Double::new(); + list.append(1); + list.append(2); + list.append(3); + println!("List after appending elements: {:?}", list); + + list.pop(); + println!("List after popping an element: {:?}", list); + + list.remove(1); + println!("List after removing an element: {:?}", list); + + let found = list.search(2); + println!("Element 2 found: {}", found); + + list.update(0, 4); + println!("List after updating an element: {:?}", list); + + let element = list.get(0); + println!("Element at index 0: {:?}", element); +} +```