Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added hashtable and removed some of the warnings + trapezoidal method for function integration + bingo sort #66

Merged
merged 11 commits into from
Jan 14, 2024

Conversation

BogdanCiocea
Copy link
Contributor

@BogdanCiocea BogdanCiocea commented Dec 30, 2023

A Generic Hash Table Implementation

This code implements a generic hash table using the std::collections::LinkedList container. It supports the core operations of inserting and searching for keys.

Implementation Overview

A hash table is a data structure that maps keys to values. It uses a hash function to assign each key to a unique bucket in the table. The hash table stores key-value pairs in the corresponding buckets, allowing for efficient insertion, searching, and updating of data.

This implementation uses a linked list to store key-value pairs for each bucket. The HashTable struct maintains a vector of these linked lists, where the elements vector stores references to the linked lists. The count member tracks the number of key-value pairs in the hash table.

The Hashable trait defines a hash function for the keys used in the hash table. The HashTable struct provides two methods:

  • insert(key: K, value: V): Inserts a key-value pair into the hash table. If the hash table is full, it resizes itself to accommodate more data.

  • search(key: K) -> Option<&V>: Searches for a key in the hash table and returns the corresponding value, or None if the key is not found.

Load Factor and Automatic Resizing

The resize method doubles the size of the hash table when the load factor, which is the ratio of count to elements.len(), exceeds 0.75. This ensures that the hash table remains efficient as it grows.

Key-Value Pair Storage

Each key-value pair is stored as a tuple of the key and value. The HashTable struct maintains a linked list of these tuples for each bucket. When a key is inserted, its hash value is calculated and used to determine the corresponding bucket. The key-value pair is then appended to the linked list at that bucket.

Search Operation

The search method iterates over the linked list at the specified hash bucket index. It checks each tuple's key against the given search key. If a matching key is found, the corresponding value is returned. Otherwise, None is returned.

Test Coverage

The test module includes unit tests that cover various scenarios for the hash table implementation. These tests ensure that the hash table behaves as expected under different conditions, including inserting and searching for keys, resizing the hash table, and handling non-existent keys.

Key Features

  • Generic hash table implementation: Supports arbitrary key types.

  • Automatic resizing: Handles increasing load factors without performance degradation.

  • Efficient insert and search operations: Provides fast insertion and search times.

  • Comprehensive test coverage: Ensures reliable and robust behavior.

Usage

To use the hash table, first define a type that implements the Hashable trait. Then, create a new hash table using the new method:

let mut hash_table = HashTable::new();

Sleep Sort Implementation in Rust

This Rust code implements a sleep sort algorithm that simulates a sorting process using threads and delays. The algorithm takes a vector of integers as input and returns a sorted vector. It works by spawning multiple threads, each of which sleeps for a duration proportional to its assigned integer value. Smaller numbers take less time to sort than larger numbers, so the sorted elements are gradually collected from the threads and returned.

Code Example:

pub fn sleep_sort(vec: &[usize]) -> Vec<usize> {
    // Get the length of the input vector
    let len = vec.len();

    // Create a channel for communication between threads
    let (tx, rx) = mpsc::channel();

    // Iterate over each element (denoted by &x) in the input vector using a for_each loop
    vec.iter().for_each(|&x| {
        // Clone the sender (tx) for each thread to send data to the receiver
        let tx = tx.clone();

        // Spawn a new thread for each element in the vector
        thread::spawn(move || {
            // Simulate a delay based on the value of the element
            thread::sleep(Duration::from_millis((20 * x) as u64));

            // Send the element to the receiver through the channel
            tx.send(x).expect("Failed to send data");
        });
    });

    // Collect the sorted elements from the receiver into a vector and return it
    rx.into_iter().take(len).collect()
}

Description:

  1. The sleep_sort function takes a vector of integers as input.

  2. It creates a channel using the mpsc::channel() function. This channel will be used to communicate between the main thread and the worker threads.

  3. It iterates over each element in the input vector using a for-each loop.

  4. For each element, it clones the sender (tx) of the channel. This is necessary because each worker thread will need its own copy of the sender to send data to the receiver.

  5. It spawns a new thread for each element in the vector using the thread::spawn() function.

  6. Inside the worker thread, it simulates a delay based on the value of the element. This is done by calling the thread::sleep() function with a duration that is proportional to the element's value. For example, an element with the value 1 will sleep for 20 milliseconds, an element with the value 2 will sleep for 40 milliseconds, and so on.

  7. After the delay, it sends the element to the receiver through the channel using the tx.send() method.

  8. Finally, it collects the sorted elements from the receiver into a vector and returns it.

Example Usage:

let vec = vec![5, 3, 7, 10, 1, 0, 8];
let sorted_vec = sleep_sort(&vec);
println!("{:?}", sorted_vec);

Trapezoidal Integral Implementation in Rust

This Rust code implements the trapezoidal rule for approximating the definite integral of a function f(x) between the limits x = a and x = b. The trapezoidal rule divides the interval between a and b into a specified number of trapezoids and approximates the integral as the sum of the areas of the trapezoids.

Code Example

pub fn trapezoidal_integral<F>(a: f64, b: f64, f: F, precision: u32) -> f64
where
    F: Fn(f64) -> f64,
{
    let delta = (b - a) / precision as f64;

    let integral: f64 = (0..precision)
        .map(|trapezoid| {
            let left_side = a + (delta * trapezoid as f64);
            let right_side = left_side + delta;

            0.5 * (f(left_side) + f(right_side)) * delta
        })
        .sum();

    if a > b {
        -integral
    } else {
        integral
    }
}

Description

The trapezoidal_integral function takes four arguments:

  1. a: The lower bound of the integration interval

  2. b: The upper bound of the integration interval

  3. f: A function of the form f(x) that represents the integrand

  4. precision: The number of trapezoids to use in the approximation

The function first defines a helper function core that performs the actual calculations for a single trapezoid. The core function takes the same four arguments as the trapezoidal_integral function and returns the area of a single trapezoid.

The trapezoidal_integral function then calculates the sum of the areas of the trapezoids using a for loop. The loop iterates over the range of 0..precision, which represents the indices of the trapezoids. For each trapezoid, the left_side and right_size variables are calculated as the left and right endpoints of the trapezoid, respectively. The area of the trapezoid is then calculated using the formula:

0.5 * (f(left_side) + f(right_size)) * delta

where delta is the width of the trapezoid. The final step is to add the area of each trapezoid to the running sum and return the result.

Usage

The trapezoidal_integral function can be used to approximate the definite integral of any function f(x) within a specified interval. For example, the following code approximates the integral of the function x^2 from 0 to 1:

let f = |x: f64| x.powi(2);
let result = trapezoidal_integral(0.0, 1.0, f, 1000); // Approximates 1/3
println!("{}", result);

Bingo Sort Implementation in Rust

The provided Rust code implements the bingo sort algorithm for sorting a vector of integers. The bingo sort algorithm repeatedly identifies and moves the "bingo" elements, which are elements that equal the current bingo value.

Empty Vector Check

  • If the vector is empty, the function returns early to avoid further processing.

Initializing Bingo Elements

  1. Two variables, bingo and next_bingo, are initialized with the first two elements of the vector.

Identifying Maximum and Minimum

  1. The max_min function is called to identify the maximum and minimum elements in the vector.
  2. The minimum element is stored in next_bingo for later use.

Tracking Largest Element

  1. The largest_element variable is set to next_bingo, which is the current minimum element.

Position of Next Element to Swap

  1. The next_element_pos variable keeps track of the position of the next element to swap with the current bingo element.

Sorting Loop

  1. A loop iterates over the range of bingo..=largest_element, which represents the sequence of bingo elements.

  2. Inside the loop:

    a. Initializing Start Position:

    • The start_pos variable is set to the current next_element_pos to serve as the starting position for iterating on the current bingo element.

    b. Iterating on Remaining Elements

    • The loop iterates over the remaining elements in the vector starting from the start_pos position.

      1. Swapping Equal Elements:

        • If the current element at index i equals the current bingo element bingo, then the elements at positions i and next_element_pos are swapped.
      2. Updating Next Bingo Element:

        • If the current element at index i is less than the current bingo element bingo, then it indicates that a new bingo element has been found.
        • The next_bingo variable is updated to the current element to prepare for the next iteration.

Updating Bingo and Next Bingo Elements

  1. After the loop completes, the current and next bingo elements are updated:

    • The bingo variable is updated to the current next_bingo element.
    • The next_bingo variable is reset to the largest_element to start the next iteration with the next bingo element.

This iterative process continues until all bingo elements are sorted into their correct positions.

@BogdanCiocea BogdanCiocea changed the title Added hashtable and removed some of the warnings Added hashtable and removed some of the warnings + trapezoidal method for function integration Dec 31, 2023
@BogdanCiocea BogdanCiocea changed the title Added hashtable and removed some of the warnings + trapezoidal method for function integration Added hashtable and removed some of the warnings + trapezoidal method for function integration + bingo sort Dec 31, 2023
Copy link
Owner

@alexfertel alexfertel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful contributions! Thanks ❤️

Sorry about the late review, I have been super busy.

A few structural changes and this is good to go!


impl<K: Hashable + std::cmp::PartialEq, V> HashTable<K, V> {
pub fn new() -> HashTable<K, V> {
let initial_capacity = 3000;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Extract to a constant. const INITIAL_CAPACITY: usize = 3000;.

Why was this value chosen? std::collection::HashMap has an initial capacity of 6 for example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To minimize resizing for larger datasets.

}

pub fn insert(&mut self, key: K, value: V) {
if self.count >= self.elements.len() * 3 / 4 {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Extract to a constant. const LOAD_FACTOR_BOUND: f32 = 0.75;.

This will make this section a bit uglier because of the casts, but the constant will be explained. wdyt?

}

fn resize(&mut self) {
let new_size = self.elements.len() * 2;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Extract to a constant. const GROWTH_FACTOR: usize = 2;.

src/math/trapezoidal_integration.rs Outdated Show resolved Hide resolved
Comment on lines +42 to 45
#[allow(dead_code)]
pub fn z_array<T: Eq>(input: &[T]) -> Vec<usize> {
match_with_z_array(input, input, 1, false)
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind exporting z_array next to match_pattern in src/string/mod.rs instead?

@alexfertel alexfertel merged commit e26fa2a into alexfertel:main Jan 14, 2024
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants