From 504e231535031638576ff5c58eba867a385b37c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?joseLu=C3=ADs?= Date: Sat, 23 Nov 2024 11:24:34 +0100 Subject: [PATCH] vendor `tailcall-chunk` - refactor `DOCS/DERIVED.md`. --- DOCS/CHANGELOG.md | 2 +- DOCS/DERIVED.md | 24 +- .../collections/vec/chunk/MODIFICATIONS.md | 8 + src/data/collections/vec/chunk/mod.rs | 337 ++++++++++++++++++ src/data/collections/vec/chunk/tests.rs | 236 ++++++++++++ src/data/collections/vec/mod.rs | 11 +- 6 files changed, 602 insertions(+), 16 deletions(-) create mode 100644 src/data/collections/vec/chunk/MODIFICATIONS.md create mode 100644 src/data/collections/vec/chunk/mod.rs create mode 100644 src/data/collections/vec/chunk/tests.rs diff --git a/DOCS/CHANGELOG.md b/DOCS/CHANGELOG.md index 51c93b27..325a4b70 100644 --- a/DOCS/CHANGELOG.md +++ b/DOCS/CHANGELOG.md @@ -54,7 +54,7 @@ The format is based on [Keep a Changelog], and this project adheres to - `const_bool!`, `capture_first`, `capture_last`, `capture_tail_tuple!`, `impl_non_value!`. - private: `doc_availability!`. - vendored: - - structs: `CacheAlign`, `ConstList`, `FatPtr`, `IdPinBox`, `IdPin`. + - structs: `CacheAlign`, `ConstList`, `FatPtr`, `IdPinBox`, `IdPin`, `VecChunk`. - macros: `assert_const!`. - traits: `ConstBool`. - optional dependencies: diff --git a/DOCS/DERIVED.md b/DOCS/DERIVED.md index 94b426a2..787993b1 100644 --- a/DOCS/DERIVED.md +++ b/DOCS/DERIVED.md @@ -7,14 +7,15 @@ See linked files for detailed changes. - [const_list] by Douglas Dwyer, as [`ConstList`]. - [crossbeam-utils] by The Crossbeam Project Developers, as the [`CachePadded`] struct. - [fmtor] by Tyler Ruckinger, as part of the [`ExtOption`] trait. -- [fxhash] by Christopher Breeden, as the [`HasherFx`] struct ([*mod*][3]). +- [fxhash] by Christopher Breeden, as the [`HasherFx`] struct ([*mod*][m1]). - [no_std_io]|[core2] by *Brendan Molloy*, as part of the [`io`] module. - [numtoa] by Michael Murphy, as the [`NumToStr`] trait. - [pollster] by Joshua Barretto, as the [`future_block`] fn. -- [stack_dst] by John Hodge, as the [`data::dst`] module ([*mod*][1]). +- [stack_dst] by John Hodge, as the [`data::dst`] module ([*mod*][m2]). - [static_assertions] by Nikolai Vazquez, as part of the [`assert_const`] macro - and the [`ConstBool`] trait ([*mod*][5]). -- [tupl] by *Dragoteryx*, as part of the [`Tuple`] trait ([*mod*][2]). + and the [`ConstBool`] trait ([*mod*][m3]). +- [tailcall-chunk] by Tushar Mathur, as the [`VecChunk`] struct ([*mod*][m4]). +- [tupl] by *Dragoteryx*, as part of the [`Tuple`] trait ([*mod*][m5]). [const_list]: https://crates.io/crates/const_list/0.1.0 [`ConstList`]: https://docs.rs/devela/latest/devela/data/collections/struct.ConstList.html @@ -23,7 +24,7 @@ See linked files for detailed changes. [fmtor]: https://crates.io/crates/fmtor/0.1.2 [`ExtOption`]: https://docs.rs/devela/latest/devela/code/trait.ExtOption.html [fxhash]: https://crates.io/crates/fxhash/0.2.1 - [3]: https://github.com/andamira/devela/blob/main/src/data/hash/fx/MODIFICATIONS.md + [m1]: https://github.com/andamira/devela/blob/main/src/data/hash/fx/MODIFICATIONS.md [no_std_io]: https://crates.io/crates/no_std_io/0.6.0 [core2]: https://crates.io/crates/core2/0.4.0 [`io`]: https://docs.rs/devela/latest/devela/sys/io/ @@ -31,20 +32,23 @@ See linked files for detailed changes. [`NumToStr`]: https://docs.rs/devela/latest/devela/text/fmt/trait.NumToStr.html [stack_dst]: https://crates.io/crates/stack_dst/0.8.1 [`data::dst`]: https://docs.rs/devela/latest/devela/data/dst/index.html - [1]: https://github.com/andamira/devela/blob/main/src/data/dst/MODIFICATIONS.md + [m2]: https://github.com/andamira/devela/blob/main/src/data/dst/MODIFICATIONS.md [pollster]: https://crates.io/crates/pollster/0.3.0 [`future_block`]: https://docs.rs/devela/latest/devela/exec/fn.future_block.html [static_assertions]: https://crates.io/crates/static_assertions/1.1.0 [`assert_const`]: https://docs.rs/devela/latest/devela/code/macro.assert_const.html [`ConstBool`]: https://docs.rs/devela/latest/devela/num/logic/trait.ConstBool.html - [5]: https://github.com/andamira/devela/blob/main/src/code/asserts/static/MODIFICATIONS.md + [m3]: https://github.com/andamira/devela/blob/main/src/code/asserts/static/MODIFICATIONS.md +[tailcall-chunk]: https://crates.io/crates/tailcall-chunk/0.3.0 + [`VecChunk`]: https://docs.rs/devela/latest/devela/data/collections/struct.VecChunk.html + [m4]: https://github.com/andamira/devela/blob/main/src/data/collections/vec/chunk/MODIFICATIONS.md [tupl]: https://crates.io/crates/tupl/0.4.0 [`Tuple`]: https://docs.rs/devela/latest/devela/data/collections/trait.Tuple.html - [2]: https://github.com/andamira/devela/blob/main/src/data/collections/tuple/MODIFICATIONS.md + [m5]: https://github.com/andamira/devela/blob/main/src/data/collections/tuple/MODIFICATIONS.md ## Works under MIT License - [const_for] by Joachim EnggÄrd Nebel, as the [`cfor`] macro. -- [object-id] by *Altertech*, as part of the [`IdPinBox`] and [`IdPin`] structs ([*mod*][4]). +- [object-id] by *Altertech*, as part of the [`IdPinBox`] and [`IdPin`] structs ([*mod*][m6]). - [opt_reduce] by *Waffle Lapkin*, as part of the [`ExtOption`] trait. - [rawbytes] by Frank Denis, as the [`mem_as_bytes`] and [`mem_as_bytes_mut`] fns. - [unsized-stack] by *storycraft*, as the [`FatPtr`] struct. @@ -54,7 +58,7 @@ See linked files for detailed changes. [object-id]: https://crates.io/crates/object-id/0.1.4 [`IdPin`]: https://docs.rs/devela/latest/devela/data/id/struct.IdPin.html [`IdPinBox`]: https://docs.rs/devela/latest/devela/data/id/struct.IdPinBox.html - [4]: https://github.com/andamira/devela/blob/main/src/data/id/pin/MODIFICATIONS.md + [m6]: https://github.com/andamira/devela/blob/main/src/data/id/pin/MODIFICATIONS.md [opt_reduce]: https://crates.io/crates/opt_reduce/1.0.0 [rawbytes]: https://crates.io/crates/rawbytes/1.0.0 [`mem_as_bytes`]: https://docs.rs/devela/latest/devela/data/fn.mem_as_bytes.html diff --git a/src/data/collections/vec/chunk/MODIFICATIONS.md b/src/data/collections/vec/chunk/MODIFICATIONS.md new file mode 100644 index 00000000..3a514918 --- /dev/null +++ b/src/data/collections/vec/chunk/MODIFICATIONS.md @@ -0,0 +1,8 @@ +This is derived work from the +[`tailcall-chunk`](https://crates.io/crates/tailcall-chunk/0.3.0) crate, +including the following modifications: + +- add `must_use` attribute. +- make it `no_std` compatible. +- rename the structure to `VecChunk`. +- misc. refactoring. diff --git a/src/data/collections/vec/chunk/mod.rs b/src/data/collections/vec/chunk/mod.rs new file mode 100644 index 00000000..9f7d2281 --- /dev/null +++ b/src/data/collections/vec/chunk/mod.rs @@ -0,0 +1,337 @@ +// devela::data::collections::vec::chunk +// +// +// IMPROVE: bring benchmarks + +use crate::{vec_ as vec, Rc, RefCell, Vec}; + +#[cfg(test)] +mod tests; + +/// A persistent data structure that provides efficient append and concatenation operations. +/// +/// # Overview +/// `VecChunk` is an immutable data structure that allows O(1) complexity for append and +/// concatenation operations through structural sharing. It uses [`Rc`] (Reference Counting) +/// for efficient memory management. +/// +/// # Performance +/// - Append operation: O(1) +/// - Concatenation operation: O(1) +/// - Converting to Vec: O(n) +/// +/// # Implementation Details +/// The data structure is implemented as an enum with three variants: +/// - `Empty`: Represents an empty chunk +/// - `Append`: Represents a single element appended to another chunk +/// - `Concat`: Represents the concatenation of two chunks +/// +/// # Example +/// ``` +/// # use devela::VecChunk; +/// let mut chunk = VecChunk::default(); +/// chunk = chunk.append(1); +/// chunk = chunk.append(2); +/// +/// let other_chunk = VecChunk::default().append(3).append(4); +/// let combined = chunk.concat(other_chunk); +/// +/// assert_eq!(combined.as_vec(), vec![1, 2, 3, 4]); +/// ``` +/// +/// # References +/// - [Persistent Data Structures](https://en.wikipedia.org/wiki/Persistent_data_structure) +/// - [Structural Sharing](https://hypirion.com/musings/understanding-persistent-vector-pt-1) +/// +/// # Derived Work +#[doc = include_str!("./MODIFICATIONS.md")] +#[must_use] +#[derive(Clone)] +pub enum VecChunk { + /// Represents an empty chunk with no elements + Empty, + /// Represents a chunk containing exactly one element + Single(A), + /// Represents the concatenation of two chunks, enabling O(1) concatenation + Concat(Rc>, Rc>), + /// Represents a collection of elements + Collect(Rc>>), + /// Represents a lazy transformation that flattens elements + TransformFlatten(Rc>, Rc VecChunk>), +} + +impl Default for VecChunk { + /// Creates a new empty chunk. + /// + /// This is equivalent to using [`VecChunk::Empty`]. + fn default() -> Self { + VecChunk::Empty + } +} + +impl VecChunk { + /// Creates a new chunk containing a single element. + /// + /// # Arguments + /// * `a` - The element to store in the chunk + /// + /// # Example + /// ``` + /// # use devela::VecChunk; + /// let chunk: VecChunk = VecChunk::new(100); + /// assert!(!chunk.is_null()); + /// ``` + pub fn new(a: A) -> Self { + VecChunk::Single(a) + } + + /// Returns `true` if the chunk is empty. + /// + /// # Example + /// ``` + /// # use devela::VecChunk; + /// let chunk: VecChunk = VecChunk::default(); + /// assert!(chunk.is_null()); + /// + /// let non_empty = chunk.append(42); + /// assert!(!non_empty.is_null()); + /// ``` + pub fn is_null(&self) -> bool { + match self { + VecChunk::Empty => true, + VecChunk::Collect(vec) => vec.borrow().is_empty(), + _ => false, + } + } + + /// Append a new element to the chunk. + /// + /// This operation has O(1) complexity as it creates a new `Append` variant + /// that references the existing chunk through an [`Rc`]. + /// + /// # Example + /// ``` + /// # use devela::VecChunk; + /// let chunk = VecChunk::default().append(1).append(2); + /// assert_eq!(chunk.as_vec(), vec![1, 2]); + /// ``` + pub fn append(self, a: A) -> Self { + self.concat(VecChunk::new(a)) + } + + /// Prepend a new element to the beginning of the chunk. + /// + /// This operation has O(1) complexity as it creates a new `Concat` variant + /// that references the existing chunk through an [`Rc`]. + /// + /// # Example + /// ``` + /// # use devela::VecChunk; + /// let chunk = VecChunk::default().prepend(1).prepend(2); + /// assert_eq!(chunk.as_vec(), vec![2, 1]); + /// ``` + pub fn prepend(self, a: A) -> Self { + if self.is_null() { + VecChunk::new(a) + } else { + VecChunk::new(a).concat(self) + } + } + + /// Concatenates this chunk with another chunk. + /// + /// This operation has O(1) complexity as it creates a new `Concat` variant + /// that references both chunks through [`Rc`]s. + /// + /// # Performance Optimization + /// If either chunk is empty, returns the other chunk instead of creating + /// a new `Concat` variant. + /// + /// # Example + /// ``` + /// # use devela::VecChunk; + /// let chunk1 = VecChunk::default().append(1).append(2); + /// let chunk2 = VecChunk::default().append(3).append(4); + /// let combined = chunk1.concat(chunk2); + /// assert_eq!(combined.as_vec(), vec![1, 2, 3, 4]); + /// ``` + pub fn concat(self, other: VecChunk) -> VecChunk { + match (self, other) { + // Handle null cases + (VecChunk::Empty, other) => other, + (this, VecChunk::Empty) => this, + (VecChunk::Single(a), VecChunk::Single(b)) => { + VecChunk::Collect(Rc::new(RefCell::new(vec![a, b]))) + } + (VecChunk::Collect(vec), VecChunk::Single(a)) => { + if Rc::strong_count(&vec) == 1 { + // Only clone if there are no other references + vec.borrow_mut().push(a); + VecChunk::Collect(vec) + } else { + VecChunk::Concat(Rc::new(VecChunk::Collect(vec)), Rc::new(VecChunk::Single(a))) + } + } + // Handle all other cases with Concat + (this, that) => VecChunk::Concat(Rc::new(this), Rc::new(that)), + } + } + + /// Transforms each element in the chunk using the provided function. + /// + /// This method creates a lazy representation of the transformation without actually + /// performing it. The transformation is only executed when [`as_vec`](VecChunk::as_vec) + /// or [`as_vec_mut`](VecChunk::as_vec_mut) is called. + /// + /// # Performance + /// - Creating the transformation: O(1) + /// - Executing the transformation (during [`as_vec`](VecChunk::as_vec)): O(n) + /// + /// # Arguments + /// * `f` - A function that takes a reference to an element of type `A` and returns + /// a new element of type `A` + /// + /// # Example + /// ``` + /// # use devela::VecChunk; + /// let chunk = VecChunk::default().append(1).append(2).append(3); + /// // This operation is O(1) and doesn't actually transform the elements + /// let doubled = chunk.transform(|x| x * 2); + /// // The transformation happens here, when we call as_vec() + /// assert_eq!(doubled.as_vec(), vec![2, 4, 6]); + /// ``` + pub fn transform(self, f: impl Fn(A) -> A + 'static) -> Self { + self.transform_flatten(move |a| VecChunk::new(f(a))) + } + + /// Materializes a chunk by converting it into a collected form. + /// + /// This method evaluates any lazy transformations and creates a new chunk containing + /// all elements in a `Collect` variant. This can be useful for performance when you + /// plan to reuse the chunk multiple times, as it prevents re-evaluation of transformations. + /// + /// # Performance + /// - Time complexity: O(n) where n is the number of elements + /// - Space complexity: O(n) as it creates a new vector containing all elements + /// + /// # Example + /// ``` + /// # use devela::VecChunk; + /// let chunk = VecChunk::default() + /// .append(1) + /// .append(2) + /// .transform(|x| x * 2); // Lazy transformation + /// + /// // Materialize the chunk to evaluate the transformation once + /// let materialized = chunk.materialize(); + /// + /// assert_eq!(materialized.as_vec(), vec![2, 4]); + /// ``` + pub fn materialize(self) -> VecChunk + where + A: Clone, + { + VecChunk::Collect(Rc::new(RefCell::new(self.as_vec()))) + } + + /// Transforms each element in the chunk into a new chunk and flattens the result. + /// + /// This method creates a lazy representation of the transformation without actually + /// performing it. The transformation is only executed when [`as_vec`](VecChunk::as_vec) + /// or [`as_vec_mut`](VecChunk::as_vec_mut) is called. + /// + /// # Performance + /// - Creating the transformation: O(1) + /// - Executing the transformation (during [`as_vec`](VecChunk::as_vec)): O(n) + /// + /// # Arguments + /// * `f` - A function that takes an element of type `A` and returns + /// a new `VecChunk` + /// + /// # Example + /// ``` + /// # use devela::VecChunk; + /// let chunk = VecChunk::default().append(1).append(2); + /// // Transform each number x into a chunk containing [x, x+1] + /// let expanded = chunk.transform_flatten(|x| { + /// VecChunk::default().append(x).append(x + 1) + /// }); + /// assert_eq!(expanded.as_vec(), vec![1, 2, 2, 3]); + /// ``` + pub fn transform_flatten(self, f: impl Fn(A) -> VecChunk + 'static) -> Self { + VecChunk::TransformFlatten(Rc::new(self), Rc::new(f)) + } + + /// Converts the chunk into a vector of references to its elements. + /// + /// This operation has O(n) complexity where n is the number of elements + /// in the chunk. + /// + /// # Example + /// ``` + /// # use devela::VecChunk; + /// let chunk = VecChunk::default().append(1).append(2).append(3); + /// assert_eq!(chunk.as_vec(), vec![1, 2, 3]); + /// ``` + pub fn as_vec(&self) -> Vec + where + A: Clone, + { + let mut vec = Vec::new(); + self.as_vec_mut(&mut vec); + vec + } + + /// Helper method that populates a vector with references to the chunk's elements. + /// + /// This method is used internally by [`as_vec`](VecChunk::as_vec) to avoid + /// allocating multiple vectors during the traversal. + /// + /// # Arguments + /// * `buf` - A mutable reference to a vector that will be populated with + /// references to the chunk's elements + pub fn as_vec_mut(&self, buf: &mut Vec) + where + A: Clone, + { + match self { + VecChunk::Empty => {} + VecChunk::Single(a) => { + buf.push(a.clone()); + } + VecChunk::Concat(a, b) => { + a.as_vec_mut(buf); + b.as_vec_mut(buf); + } + VecChunk::TransformFlatten(a, f) => { + let mut tmp = Vec::new(); + a.as_vec_mut(&mut tmp); + for elem in tmp.into_iter() { + f(elem).as_vec_mut(buf); + } + } + VecChunk::Collect(vec) => { + buf.extend(vec.borrow().iter().cloned()); + } + } + } +} + +impl FromIterator for VecChunk { + /// Creates a chunk from an iterator. + /// + /// # Example + /// ``` + /// # use devela::VecChunk; + /// let vec = vec![1, 2, 3]; + /// let chunk: VecChunk<_> = vec.into_iter().collect(); + /// assert_eq!(chunk.as_vec(), vec![1, 2, 3]); + /// ``` + fn from_iter>(iter: T) -> Self { + let mut chunk = Vec::default(); + for item in iter { + chunk.push(item); + } + VecChunk::Collect(Rc::new(RefCell::new(chunk))) + } +} diff --git a/src/data/collections/vec/chunk/tests.rs b/src/data/collections/vec/chunk/tests.rs new file mode 100644 index 00000000..36d57921 --- /dev/null +++ b/src/data/collections/vec/chunk/tests.rs @@ -0,0 +1,236 @@ +// devela::data::collections::vec::chunk + +use crate::{vec_ as vec, ExtFloatConst, String, Vec, VecChunk}; + +#[test] +fn new() { + let chunk: VecChunk = VecChunk::default(); + assert!(chunk.is_null()); +} + +#[test] +fn default() { + let chunk: VecChunk = VecChunk::default(); + assert!(chunk.is_null()); +} + +#[test] +fn is_null() { + let empty: VecChunk = VecChunk::default(); + assert!(empty.is_null()); + + let non_empty = empty.append(1); + assert!(!non_empty.is_null()); +} + +#[test] +fn append() { + let chunk = VecChunk::default().append(1).append(2).append(3); + assert_eq!(chunk.as_vec(), vec![1, 2, 3]); + + // Test that original chunk remains unchanged (persistence) + let chunk1 = VecChunk::default().append(1); + let chunk2 = chunk1.clone().append(2); + assert_eq!(chunk1.as_vec(), vec![1]); + assert_eq!(chunk2.as_vec(), vec![1, 2]); +} + +#[test] +fn concat() { + let chunk1 = VecChunk::default().append(1).append(2); + let chunk2 = VecChunk::default().append(3).append(4); + let combined = chunk1.clone().concat(chunk2.clone()); + + assert_eq!(combined.as_vec(), vec![1, 2, 3, 4]); + + // Test concatenation with empty chunks + let empty = VecChunk::default(); + assert_eq!(empty.clone().concat(chunk1.clone()).as_vec(), chunk1.as_vec()); + assert_eq!(chunk1.clone().concat(empty.clone()).as_vec(), chunk1.as_vec()); + assert_eq!(empty.clone().concat(empty).as_vec(), Vec::::new()); +} + +#[test] +fn as_vec() { + // Test empty chunk + let empty: VecChunk = VecChunk::default(); + assert_eq!(empty.as_vec(), Vec::::new()); + + // Test single element + let single = VecChunk::default().append(42); + assert_eq!(single.as_vec(), vec![42]); + + // Test multiple elements + let multiple = VecChunk::default().append(1).append(2).append(3); + assert_eq!(multiple.as_vec(), vec![1, 2, 3]); + + // Test complex structure with concatenation + let chunk1 = VecChunk::default().append(1).append(2); + let chunk2 = VecChunk::default().append(3).append(4); + let complex = chunk1.concat(chunk2); + assert_eq!(complex.as_vec(), vec![1, 2, 3, 4]); +} + +#[test] +fn structural_sharing() { + let chunk1 = VecChunk::default().append(1).append(2); + let chunk2 = chunk1.clone().append(3); + let chunk3 = chunk1.clone().append(4); + + // Verify that modifications create new structures while preserving the original + assert_eq!(chunk1.as_vec(), vec![1, 2]); + assert_eq!(chunk2.as_vec(), vec![1, 2, 3]); + assert_eq!(chunk3.as_vec(), vec![1, 2, 4]); +} + +#[test] +fn with_different_types() { + // Test with strings + let string_chunk = + VecChunk::default().append(String::from("hello")).append(String::from("world")); + assert_eq!(string_chunk.as_vec().len(), 2); + + // Test with floating point numbers - using standard constants + let float_chunk = VecChunk::default().append(f64::PI).append(f64::E); + assert_eq!(float_chunk.as_vec(), vec![f64::PI, f64::E]); + + // Test with boolean values + let bool_chunk = VecChunk::default().append(true).append(false).append(true); + assert_eq!(bool_chunk.as_vec(), vec![true, false, true]); +} + +#[test] +fn transform() { + // Test transform on empty chunk + let empty: VecChunk = VecChunk::default(); + let transformed_empty = empty.transform(|x| x * 2); + assert_eq!(transformed_empty.as_vec(), Vec::::new()); + + // Test transform on single element + let single = VecChunk::default().append(5); + let doubled = single.transform(|x| x * 2); + assert_eq!(doubled.as_vec(), vec![10]); + + // Test transform on multiple elements + let multiple = VecChunk::default().append(1).append(2).append(3); + let doubled = multiple.transform(|x| x * 2); + assert_eq!(doubled.as_vec(), vec![2, 4, 6]); + + // Test transform with string manipulation + let string_chunk = + VecChunk::default().append(String::from("hello")).append(String::from("world")); + let uppercase = string_chunk.transform(|s| s.to_uppercase()); + assert_eq!(uppercase.as_vec(), vec!["HELLO", "WORLD"]); + + // Test chaining multiple transforms + let numbers = VecChunk::default().append(1).append(2).append(3); + let result = numbers.transform(|x| x * 2).transform(|x| x + 1).transform(|x| x * 3); + assert_eq!(result.as_vec(), vec![9, 15, 21]); +} + +#[test] +fn transform_flatten() { + // Test transform_flatten on empty chunk + let empty: VecChunk = VecChunk::default(); + let transformed_empty = empty.transform_flatten(|x| VecChunk::new(x * 2)); + assert_eq!(transformed_empty.as_vec(), Vec::::new()); + + // Test transform_flatten on single element + let single = VecChunk::default().append(5); + let doubled = single.transform_flatten(|x| VecChunk::new(x * 2)); + assert_eq!(doubled.as_vec(), vec![10]); + + // Test expanding each element into multiple elements + let numbers = VecChunk::default().append(1).append(2); + let expanded = numbers.transform_flatten(|x| VecChunk::default().append(x + 1).append(x)); + assert_eq!(expanded.as_vec(), vec![2, 1, 3, 2]); + + // Test with nested chunks + let chunk = VecChunk::default().append(1).append(2).append(3); + let nested = chunk.transform_flatten(|x| { + if x % 2 == 0 { + // Even numbers expand to [x, x+1] + VecChunk::default().append(x).append(x + 1) + } else { + // Odd numbers expand to [x] + VecChunk::new(x) + } + }); + assert_eq!(nested.as_vec(), vec![1, 2, 3, 3]); + + // Test chaining transform_flatten operations + let numbers = VecChunk::default().append(1).append(2); + let result = numbers + .transform_flatten(|x| VecChunk::default().append(x).append(x)) + .transform_flatten(|x| VecChunk::default().append(x).append(x + 1)); + assert_eq!(result.as_vec(), vec![1, 2, 1, 2, 2, 3, 2, 3]); + + // Test with empty chunk results + let chunk = VecChunk::default().append(1).append(2); + let filtered = chunk.transform_flatten(|x| { + if x % 2 == 0 { + VecChunk::new(x) + } else { + VecChunk::default() // Empty chunk for odd numbers + } + }); + assert_eq!(filtered.as_vec(), vec![2]); +} + +#[test] +fn prepend() { + let chunk = VecChunk::default().prepend(1).prepend(2).prepend(3); + assert_eq!(chunk.as_vec(), vec![3, 2, 1]); + + // Test that original chunk remains unchanged (persistence) + let chunk1 = VecChunk::default().prepend(1); + let chunk2 = chunk1.clone().prepend(2); + assert_eq!(chunk1.as_vec(), vec![1]); + assert_eq!(chunk2.as_vec(), vec![2, 1]); + + // Test mixing prepend and append + let mixed = VecChunk::default() + .prepend(1) // [1] + .append(2) // [1, 2] + .prepend(3); // [3, 1, 2] + assert_eq!(mixed.as_vec(), vec![3, 1, 2]); +} + +#[test] +fn from_iterator() { + // Test collecting from an empty iterator + let empty_vec: Vec = vec![]; + let empty_chunk: VecChunk = empty_vec.into_iter().collect(); + assert!(empty_chunk.is_null()); + + // Test collecting from a vector + let vec = vec![1, 2, 3]; + let chunk: VecChunk<_> = vec.into_iter().collect(); + assert_eq!(chunk.as_vec(), vec![1, 2, 3]); + + // Test collecting from a range + let range_chunk: VecChunk<_> = (1..=5).collect(); + assert_eq!(range_chunk.as_vec(), vec![1, 2, 3, 4, 5]); + + // Test collecting from map iterator + let doubled: VecChunk<_> = vec![1, 2, 3].into_iter().map(|x| x * 2).collect(); + assert_eq!(doubled.as_vec(), vec![2, 4, 6]); +} + +#[test] +fn concat_optimization() { + // Create a collected chunk + let collected: VecChunk = vec![1, 2, 3].into_iter().collect(); + + // Concat a single element + let result = collected.concat(VecChunk::Single(4)); + + // Verify the result + assert_eq!(result.as_vec(), vec![1, 2, 3, 4]); + + // Verify it's still a Collect variant (not a Concat) + match result { + VecChunk::Collect(_) => (), // This is what we want + _ => panic!("Expected Collect variant after optimization"), + } +} diff --git a/src/data/collections/vec/mod.rs b/src/data/collections/vec/mod.rs index 788797b1..1aec6280 100644 --- a/src/data/collections/vec/mod.rs +++ b/src/data/collections/vec/mod.rs @@ -1,20 +1,21 @@ // devela::data::collections::vec // //! Vectors, -#![doc = crate::doc_!(extends: vec)] -#![doc = crate::doc_!(modules: crate::data::collections; vec)] -#![doc = crate::doc_!(newline)] +// #![doc = crate::doc_!(extends: vec)] +// #![doc = crate::doc_!(modules: crate::data::collections; vec)] +// #![doc = crate::doc_!(newline)] //! //! Vectors are random-access, sequentially allocated, *dynamically* sized, //! homogeneous data structures. // +mod chunk; mod ext; #[allow(unused_imports)] -pub use ext::*; +pub use {chunk::*, ext::*}; pub(crate) mod all { #[doc(inline)] #[allow(unused_imports)] - pub use super::ext::*; + pub use super::{chunk::*, ext::*}; }