1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
use std::{fmt::Debug, rc::Rc};
pub(crate) type GetCapacityOfFragment = Rc<dyn Fn(usize) -> usize>;
#[derive(Clone)]
/// Growth policy of fragments in a split vector.
///
/// A policy can be defined by one of the three constructors.
///
/// * `FragmentGrowth::exponential(initial_capacity, capacity_multiplier)`
/// * capacity of the f-th fragment will be computed as `initial_capacity * capacity_multiplier^f`.
///
/// * `FragmentGrowth::constant(constant_fragment_length)`
/// * capacity of all fragments will be equal to `constant_fragment_length`, leading to a linear growth.
///
/// * `FragmentGrowth::by_function(get_capacity_of_fragment)`
/// * capacity of the f-th fragment will be computed as `get_capacity_of_fragment(f)`;
/// * any custom growth policy can be represented by this functional form.
pub struct FragmentGrowth {
fun: GetCapacityOfFragment,
}
impl Default for FragmentGrowth {
/// Creates the default growth with an initial capacity of 4 and capacity multiplier of 1.5.
///
/// Fragment capacities with the default function will be:
/// * fragment-0: capacity=4 -> total-capacity=4
/// * fragment-1: capacity=6 -> total-capacity=10
/// * fragment-2: capacity=9 -> total-capacity=19
/// * fragment-3: capacity=13 -> total-capacity=32
/// * ...
fn default() -> Self {
fn default_exponential_growth(f: usize) -> usize {
const INITIAL_CAPACITY: usize = 4;
const CAPACITY_MULTIPLIER: f32 = 1.5;
(INITIAL_CAPACITY as f32 * f32::powf(CAPACITY_MULTIPLIER, f as f32)) as usize
}
Self {
fun: Rc::new(default_exponential_growth),
}
}
}
impl FragmentGrowth {
/// Creates an exponential growth policy with the given positive coefficients.
///
/// The capacity of the f-th fragment will be computed as:
///
/// `initial_capacity * capacity_multiplier^f`
///
/// # Panics
///
/// Panics when:
/// * `initial_capacity` is nonpositive, or
/// * `capacity_multiplier` is nonpositive;
///
/// as either of these cases would lead to zero capacity allocations for growth.
///
/// # Examples
///
/// Below example demonstrates an exponential growth with a multiplier of 1.5.
/// ```
/// use orx_split_vec::{FragmentGrowth, SplitVec};
///
/// let growth = FragmentGrowth::exponential(4, 1.5);
/// let mut vec = SplitVec::with_growth(growth);
///
/// assert_eq!(4, vec.capacity());
///
/// vec.extend_from_slice(&[0, 1, 2, 3, 4]);
/// assert_eq!(5, vec.len());
/// assert_eq!(4 + 6, vec.capacity());
///
/// vec.extend_from_slice(&[0, 1, 2, 3, 4]);
/// assert_eq!(10, vec.len());
/// assert_eq!(4 + 6, vec.capacity());
///
/// vec.push(42);
/// assert_eq!(11, vec.len());
/// assert_eq!(4 + 6 + 9, vec.capacity());
/// assert_eq!(4, vec.fragments()[0].capacity());
/// assert_eq!(6, vec.fragments()[1].capacity());
/// assert_eq!(9, vec.fragments()[2].capacity());
/// ```
///
/// One can also allocate the same amount of memory every time new capacity is required.
///
/// ```
/// use orx_split_vec::{FragmentGrowth, SplitVec};
///
/// let growth = FragmentGrowth::exponential(10, 1.0);
/// let mut vec = SplitVec::with_growth(growth);
///
/// assert_eq!(10, vec.capacity());
///
/// for x in 0..10 {
/// vec.push(x);
/// }
///
/// assert_eq!(10, vec.len());
/// assert_eq!(10, vec.capacity());
///
/// vec.push(42);
/// assert_eq!(11, vec.len());
/// assert_eq!(20, vec.capacity());
/// for fragment in vec.fragments() {
/// assert_eq!(10, fragment.capacity());
/// }
/// ```
pub fn exponential(initial_capacity: usize, capacity_multiplier: f32) -> Self {
assert!(initial_capacity > 0, "initial capacity must be positive");
assert!(
capacity_multiplier > 1e-5,
"capacity multiplier must be positive"
);
let fun: GetCapacityOfFragment = Rc::new(move |f| {
(initial_capacity as f32 * f32::powf(capacity_multiplier, f as f32)) as usize
});
Self { fun }
}
/// Creates a constant growth policy where every fragment will have the same length;
/// i.e., `constant_fragment_length`.
///
/// Note that `FragmentGrowth::constant(10)` is a shorthand for `FragmentGrowth::exponential(10, 1.0)`.
///
/// # Panics
///
/// Panics when:
/// * `constant_fragment_length` is nonpositive
///
/// as ot would lead to zero capacity allocations for growth.
///
/// # Examples
///
/// ```
/// use orx_split_vec::{FragmentGrowth, SplitVec};
///
/// let growth = FragmentGrowth::constant(10);
/// let mut vec = SplitVec::with_growth(growth);
///
/// assert_eq!(10, vec.capacity());
///
/// for x in 0..10 {
/// vec.push(x);
/// }
///
/// assert_eq!(10, vec.len());
/// assert_eq!(10, vec.capacity());
///
/// vec.push(42);
/// assert_eq!(11, vec.len());
/// assert_eq!(20, vec.capacity());
/// for fragment in vec.fragments() {
/// assert_eq!(10, fragment.capacity());
/// }
/// ```
pub fn constant(constant_fragment_length: usize) -> Self {
assert!(
constant_fragment_length > 0,
"constant growth factor, fragment length, must be positive"
);
let fun: GetCapacityOfFragment = Rc::new(move |_| constant_fragment_length);
Self { fun }
}
/// Creates a growth policy function where the capacities are computed by the given function.
///
/// The capacity of the f-th fragment will be computed as:
///
/// `get_capacity_by_fragment(f)`
///
/// # Examples
///
/// One interesting policy could be to increase the fragment lengths until it reaches a particular level.
/// Then, each expansion could be a constant expansion.
///
/// ```
/// use orx_split_vec::{FragmentGrowth, SplitVec};
/// use std::rc::Rc;
///
/// fn get_fragment_capacity(fragment: usize) -> usize {
/// let exp = (4.0 * f32::powf(1.5, fragment as f32)) as usize;
/// exp.min(10)
/// }
/// let growth = FragmentGrowth::by_function(Rc::new(get_fragment_capacity));
/// let mut vec = SplitVec::with_growth(growth);
///
/// for i in 0..1000 {
/// vec.push(i);
/// }
///
/// assert_eq!(4, vec.fragments()[0].capacity());
/// assert_eq!(6, vec.fragments()[1].capacity());
/// assert_eq!(9, vec.fragments()[2].capacity());
/// assert_eq!(10, vec.fragments()[3].capacity());
/// for fragment in vec.fragments().iter().skip(4) {
/// assert_eq!(10, fragment.capacity());
/// }
///
/// ```
pub fn by_function(get_capacity_of_fragment: Rc<dyn Fn(usize) -> usize>) -> Self {
Self {
fun: get_capacity_of_fragment,
}
}
/// Returns the capacity of the `fragment_index`-th fragment.
///
/// This method returns the maximum of 4 and the computed capacity with the defined strategy;
/// i.e., capacities 0, 1, 2 and 3 are not allowed.
pub fn get_capacity(&self, fragment_index: usize) -> usize {
let capacity = (self.fun)(fragment_index);
capacity.max(4)
}
}
impl Debug for FragmentGrowth {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FragmentGrowth").finish()
}
}