diff --git a/hostnames_allocator/Cargo.toml b/hostnames_allocator/Cargo.toml index d700871..5bfcc96 100644 --- a/hostnames_allocator/Cargo.toml +++ b/hostnames_allocator/Cargo.toml @@ -6,3 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +[dev-dependencies] +itertools = "0.11.0" diff --git a/hostnames_allocator/src/lib.rs b/hostnames_allocator/src/lib.rs new file mode 100644 index 0000000..09cb3ae --- /dev/null +++ b/hostnames_allocator/src/lib.rs @@ -0,0 +1,3 @@ +#![feature(btree_cursors)] + +pub mod ranged_number_allocator; diff --git a/hostnames_allocator/src/ranged_number_allocator.rs b/hostnames_allocator/src/ranged_number_allocator.rs new file mode 100644 index 0000000..c322edf --- /dev/null +++ b/hostnames_allocator/src/ranged_number_allocator.rs @@ -0,0 +1,88 @@ +use std::{collections::BTreeMap, ops::Bound}; + +pub struct RangedNumberAllocator { + pub tree: BTreeMap, +} + +impl RangedNumberAllocator { + pub fn new() -> Self { + RangedNumberAllocator { + tree: BTreeMap::new() + } + } + + pub fn remove(&mut self, value: u32) { + let mut cursor = self.tree.upper_bound_mut(Bound::Included(&value)); + if let Some((&range_start, &range_end)) = cursor.key_value() { + if range_end >= value { + // The found range covers the requested value, meaning this value was previously used. Now there are four scenarios: + // 1. It's [value, value]; we should remove it; + // 2. It's [value, x] where x > value; we should replace it with [value+1, x]; + // 3. It's [x, value] where value > x; we should change it to [x, value-1]; + // 4. It's [x, y] where x < value < y; we should replace it with [x, value-1] and [value+1, y]. + if range_start == value && range_end == value { + // Scenario 1 + cursor.remove_current().unwrap(); + } else if range_start == value { + // Scenario 2 + cursor.insert_after(value + 1, range_end); + cursor.remove_current().unwrap(); + } else if range_end == value { + // Scenario 3 + *cursor.value_mut().unwrap() = value - 1; + } else { + // Scenario 4 + cursor.insert_after(value + 1, range_end); + *cursor.value_mut().unwrap() = value - 1; + } + } + } + } + + pub fn allocate(&mut self) -> u32 { + let mut cursor = self.tree.lower_bound_mut(Bound::Unbounded); + // Six scenarios: + // 1. Tree is empty; we should add [1, 1] range and return 1; + // 2. Tree has only one range [1, x]; we should change it to [1, x+1] and return x+1; + // 3. Tree has at least first two ranges, [1, x] and [x+2, y]; we should replace them with [1, y] and return x+1; + // 4. Tree has at least first two ranges, [1, x] and [y, z] where y > x+2; we should change the first range to [1, x+1] and return x+1; + // 5. Tree has at least first range [2, x]; we should replace it with [1, x] and return 1; + // 6. Tree has at least first range [x, y] where x > 2; we should add [1, 1] and return 1. + if let Some((&range_start, &range_end)) = cursor.key_value() { + // There is at least one range; eliminated scenario 1, left with 2-6. + if range_start == 1 { + // First range starts with 1; eliminated scenarios 5-6, left with 2-4. + if let Some((&next_range_start, &mut next_range_end)) = cursor.peek_next() { + // There is another range; eliminated scenario 2, left with 3-4 + if next_range_start == range_end + 2 { + // Scenario 3 + *cursor.value_mut().unwrap() = next_range_end; + cursor.move_next(); + cursor.remove_current_and_move_back().unwrap(); + return range_end + 1; + } else { + // Scenario 4 + *cursor.value_mut().unwrap() = range_end + 1; + return range_end + 1; + } + } else { + // Scenario 2 + *cursor.value_mut().unwrap() = range_end + 1; + return range_end + 1; + } + } else if range_start == 2 { + // Scenario 5 + cursor.insert_before(1, range_end); + cursor.remove_current().unwrap(); + return 1; + } else { + // Scenario 6 + cursor.insert_before(1, 1); + return 1; + } + } else { + cursor.insert_after(1, 1); + return 1; + } + } +} diff --git a/hostnames_allocator/tests/ranged_number_allocator_tests.rs b/hostnames_allocator/tests/ranged_number_allocator_tests.rs new file mode 100644 index 0000000..200ca4a --- /dev/null +++ b/hostnames_allocator/tests/ranged_number_allocator_tests.rs @@ -0,0 +1,111 @@ +extern crate hostnames_allocator; + +use hostnames_allocator::ranged_number_allocator::RangedNumberAllocator; + +fn create_allocator(data: &[(u32, u32)]) -> RangedNumberAllocator { + RangedNumberAllocator { + tree: data.to_owned().into_iter().collect() + } +} + +fn check_allocator(allocator: RangedNumberAllocator, expected: &[(u32, u32)]) { + itertools::assert_equal(allocator.tree, expected.to_owned()); +} + +#[test] +fn remove_preserves_tree_when_empty() { + let mut allocator = create_allocator(&[]); + allocator.remove(5); + check_allocator(allocator, &[]); +} + +#[test] +fn remove_preserves_tree_when_not_found() { + let mut allocator = create_allocator(&[(1, 3), (11, 13)]); + allocator.remove(5); + check_allocator(allocator, &[(1, 3), (11, 13)]); +} + +#[test] +fn remove_removes_range_with_single_value() { + let mut allocator = create_allocator(&[(1, 3), (5, 5), (11, 13)]); + allocator.remove(5); + check_allocator(allocator, &[(1, 3), (11, 13)]); +} + +#[test] +fn remove_replaces_range_starting_with_value() { + let mut allocator = create_allocator(&[(1, 3), (5, 7), (11, 13)]); + allocator.remove(5); + check_allocator(allocator, &[(1, 3), (6, 7), (11, 13)]); +} + +#[test] +fn remove_changes_range_ending_with_value() { + let mut allocator = create_allocator(&[(1, 3), (5, 7), (11, 13)]); + allocator.remove(7); + check_allocator(allocator, &[(1, 3), (5, 6), (11, 13)]); +} + +#[test] +fn remove_splits_range_containing_value() { + let mut allocator = create_allocator(&[(1, 3), (5, 9), (11, 13)]); + allocator.remove(7); + check_allocator(allocator, &[(1, 3), (5, 6), (8, 9), (11, 13)]); +} + +#[test] +fn remove_removes_last_value() { + let mut allocator = create_allocator(&[(5, 5)]); + allocator.remove(5); + check_allocator(allocator, &[]); +} + +#[test] +fn allocate_initializes_empty() { + let mut allocator = create_allocator(&[]); + assert_eq!(allocator.allocate(), 1); + check_allocator(allocator, &[(1, 1)]); +} + +#[test] +fn allocate_adds_second_value() { + let mut allocator = create_allocator(&[(1, 1)]); + assert_eq!(allocator.allocate(), 2); + check_allocator(allocator, &[(1, 2)]); +} + +#[test] +fn allocate_adds_tenth_value() { + let mut allocator = create_allocator(&[(1, 9)]); + assert_eq!(allocator.allocate(), 10); + check_allocator(allocator, &[(1, 10)]); +} + +#[test] +fn allocate_adds_value_for_two_ranges_starting_with_one() { + let mut allocator = create_allocator(&[(1, 5), (10, 15)]); + assert_eq!(allocator.allocate(), 6); + check_allocator(allocator, &[(1, 6), (10, 15)]); +} + +#[test] +fn allocate_combines_two_adjacent_ranges_starting_with_one() { + let mut allocator = create_allocator(&[(1, 5), (7, 15)]); + assert_eq!(allocator.allocate(), 6); + check_allocator(allocator, &[(1, 15)]); +} + +#[test] +fn allocate_adds_value_for_range_starting_with_two() { + let mut allocator = create_allocator(&[(2, 10)]); + assert_eq!(allocator.allocate(), 1); + check_allocator(allocator, &[(1, 10)]); +} + +#[test] +fn allocate_adds_value_for_range_starting_with_ten() { + let mut allocator = create_allocator(&[(10, 20)]); + assert_eq!(allocator.allocate(), 1); + check_allocator(allocator, &[(1, 1), (10, 20)]); +}