devela/data/list/array/vec/chunk/
mod.rs

1// devela::data::list::array::vec::chunk
2//
3//
4// IMPROVE: bring benchmarks
5
6use crate::{Rc, RefCell, Vec, vec_ as vec};
7
8#[cfg(test)]
9mod tests;
10
11#[doc = crate::TAG_DATA_STRUCTURE!()]
12/// A persistent data structure with efficient append and concatenation operations.
13///
14/// # Overview
15/// `VecChunk<A>` is an immutable data structure that allows O(1) complexity for append and
16/// concatenation operations through structural sharing. It uses [`Rc`] (Reference Counting)
17/// for efficient memory management.
18///
19/// # Performance
20/// - Append operation: O(1)
21/// - Concatenation operation: O(1)
22/// - Converting to Vec: O(n)
23///
24/// # Implementation Details
25/// The data structure is implemented as an enum with three variants:
26/// - `Empty`: Represents an empty chunk
27/// - `Append`: Represents a single element appended to another chunk
28/// - `Concat`: Represents the concatenation of two chunks
29///
30/// # Example
31/// ```
32/// # use devela::VecChunk;
33/// let mut chunk = VecChunk::default();
34/// chunk = chunk.append(1);
35/// chunk = chunk.append(2);
36///
37/// let other_chunk = VecChunk::default().append(3).append(4);
38/// let combined = chunk.concat(other_chunk);
39///
40/// assert_eq!(combined.as_vec(), vec![1, 2, 3, 4]);
41/// ```
42///
43/// # References
44/// - [Persistent Data Structures](https://en.wikipedia.org/wiki/Persistent_data_structure)
45/// - [Structural Sharing](https://hypirion.com/musings/understanding-persistent-vector-pt-1)
46///
47#[doc = crate::_doc!(vendor: "tailcall-chunk")]
48#[must_use]
49#[derive(Clone)]
50#[allow(missing_debug_implementations, reason = "unsatisfied trait bounds")]
51pub enum VecChunk<A> {
52    /// Represents an empty chunk with no elements
53    Empty,
54    /// Represents a chunk containing exactly one element
55    Single(A),
56    /// Represents the concatenation of two chunks, enabling O(1) concatenation
57    Concat(Rc<VecChunk<A>>, Rc<VecChunk<A>>),
58    /// Represents a collection of elements
59    Collect(Rc<RefCell<Vec<A>>>),
60    /// Represents a lazy transformation that flattens elements
61    TransformFlatten(Rc<VecChunk<A>>, Rc<dyn Fn(A) -> VecChunk<A>>),
62}
63
64impl<A> Default for VecChunk<A> {
65    /// Creates a new empty chunk.
66    ///
67    /// This is equivalent to using [`VecChunk::Empty`].
68    fn default() -> Self {
69        VecChunk::Empty
70    }
71}
72
73impl<A> VecChunk<A> {
74    /// Creates a new chunk containing a single element.
75    ///
76    /// # Arguments
77    /// * `a` - The element to store in the chunk
78    ///
79    /// # Example
80    /// ```
81    /// # use devela::VecChunk;
82    /// let chunk: VecChunk<i32> = VecChunk::new(100);
83    /// assert!(!chunk.is_null());
84    /// ```
85    pub fn new(a: A) -> Self {
86        VecChunk::Single(a)
87    }
88
89    /// Returns `true` if the chunk is empty.
90    ///
91    /// # Example
92    /// ```
93    /// # use devela::VecChunk;
94    /// let chunk: VecChunk<i32> = VecChunk::default();
95    /// assert!(chunk.is_null());
96    ///
97    /// let non_empty = chunk.append(42);
98    /// assert!(!non_empty.is_null());
99    /// ```
100    pub fn is_null(&self) -> bool {
101        match self {
102            VecChunk::Empty => true,
103            VecChunk::Collect(vec) => vec.borrow().is_empty(),
104            _ => false,
105        }
106    }
107
108    /// Append a new element to the chunk.
109    ///
110    /// This operation has O(1) complexity as it creates a new `Append` variant
111    /// that references the existing chunk through an [`Rc`].
112    ///
113    /// # Example
114    /// ```
115    /// # use devela::VecChunk;
116    /// let chunk = VecChunk::default().append(1).append(2);
117    /// assert_eq!(chunk.as_vec(), vec![1, 2]);
118    /// ```
119    pub fn append(self, a: A) -> Self {
120        self.concat(VecChunk::new(a))
121    }
122
123    /// Prepend a new element to the beginning of the chunk.
124    ///
125    /// This operation has O(1) complexity as it creates a new `Concat` variant
126    /// that references the existing chunk through an [`Rc`].
127    ///
128    /// # Example
129    /// ```
130    /// # use devela::VecChunk;
131    /// let chunk = VecChunk::default().prepend(1).prepend(2);
132    /// assert_eq!(chunk.as_vec(), vec![2, 1]);
133    /// ```
134    pub fn prepend(self, a: A) -> Self {
135        if self.is_null() { VecChunk::new(a) } else { VecChunk::new(a).concat(self) }
136    }
137
138    /// Concatenates this chunk with another chunk.
139    ///
140    /// This operation has O(1) complexity as it creates a new `Concat` variant
141    /// that references both chunks through [`Rc`]s.
142    ///
143    /// # Performance Optimization
144    /// If either chunk is empty, returns the other chunk instead of creating
145    /// a new `Concat` variant.
146    ///
147    /// # Example
148    /// ```
149    /// # use devela::VecChunk;
150    /// let chunk1 = VecChunk::default().append(1).append(2);
151    /// let chunk2 = VecChunk::default().append(3).append(4);
152    /// let combined = chunk1.concat(chunk2);
153    /// assert_eq!(combined.as_vec(), vec![1, 2, 3, 4]);
154    /// ```
155    pub fn concat(self, other: VecChunk<A>) -> VecChunk<A> {
156        match (self, other) {
157            // Handle null cases
158            (VecChunk::Empty, other) => other,
159            (this, VecChunk::Empty) => this,
160            (VecChunk::Single(a), VecChunk::Single(b)) => {
161                VecChunk::Collect(Rc::new(RefCell::new(vec![a, b])))
162            }
163            (VecChunk::Collect(vec), VecChunk::Single(a)) => {
164                if Rc::strong_count(&vec) == 1 {
165                    // Only clone if there are no other references
166                    vec.borrow_mut().push(a);
167                    VecChunk::Collect(vec)
168                } else {
169                    VecChunk::Concat(Rc::new(VecChunk::Collect(vec)), Rc::new(VecChunk::Single(a)))
170                }
171            }
172            // Handle all other cases with Concat
173            (this, that) => VecChunk::Concat(Rc::new(this), Rc::new(that)),
174        }
175    }
176
177    /// Transforms each element in the chunk using the provided function.
178    ///
179    /// This method creates a lazy representation of the transformation without actually
180    /// performing it. The transformation is only executed when [`as_vec`](VecChunk::as_vec)
181    /// or [`as_vec_mut`](VecChunk::as_vec_mut) is called.
182    ///
183    /// # Performance
184    /// - Creating the transformation: O(1)
185    /// - Executing the transformation (during [`as_vec`](VecChunk::as_vec)): O(n)
186    ///
187    /// # Example
188    /// ```
189    /// # use devela::VecChunk;
190    /// let chunk = VecChunk::default().append(1).append(2).append(3);
191    /// // This operation is O(1) and doesn't actually transform the elements
192    /// let doubled = chunk.transform(|x| x * 2);
193    /// // The transformation happens here, when we call as_vec()
194    /// assert_eq!(doubled.as_vec(), vec![2, 4, 6]);
195    /// ```
196    pub fn transform(self, f: impl Fn(A) -> A + 'static) -> Self {
197        self.transform_flatten(move |a| VecChunk::new(f(a)))
198    }
199
200    /// Materializes a chunk by converting it into a collected form.
201    ///
202    /// This method evaluates any lazy transformations and creates a new chunk containing
203    /// all elements in a `Collect` variant. This can be useful for performance when you
204    /// plan to reuse the chunk multiple times, as it prevents re-evaluation of transformations.
205    ///
206    /// # Performance
207    /// - Time complexity: O(n) where n is the number of elements
208    /// - Space complexity: O(n) as it creates a new vector containing all elements
209    ///
210    /// # Example
211    /// ```
212    /// # use devela::VecChunk;
213    /// let chunk = VecChunk::default()
214    ///     .append(1)
215    ///     .append(2)
216    ///     .transform(|x| x * 2);  // Lazy transformation
217    ///
218    /// // Materialize the chunk to evaluate the transformation once
219    /// let materialized = chunk.materialize();
220    ///
221    /// assert_eq!(materialized.as_vec(), vec![2, 4]);
222    /// ```
223    pub fn materialize(self) -> VecChunk<A>
224    where
225        A: Clone,
226    {
227        VecChunk::Collect(Rc::new(RefCell::new(self.as_vec())))
228    }
229
230    /// Transforms each element in the chunk into a new chunk and flattens the result.
231    ///
232    /// This method creates a lazy representation of the transformation without actually
233    /// performing it. The transformation is only executed when [`as_vec`](VecChunk::as_vec)
234    /// or [`as_vec_mut`](VecChunk::as_vec_mut) is called.
235    ///
236    /// # Performance
237    /// - Creating the transformation: O(1)
238    /// - Executing the transformation (during [`as_vec`](VecChunk::as_vec)): O(n)
239    ///
240    /// # Example
241    /// ```
242    /// # use devela::VecChunk;
243    /// let chunk = VecChunk::default().append(1).append(2);
244    /// // Transform each number x into a chunk containing [x, x+1]
245    /// let expanded = chunk.transform_flatten(|x| {
246    ///     VecChunk::default().append(x).append(x + 1)
247    /// });
248    /// assert_eq!(expanded.as_vec(), vec![1, 2, 2, 3]);
249    /// ```
250    pub fn transform_flatten(self, f: impl Fn(A) -> VecChunk<A> + 'static) -> Self {
251        VecChunk::TransformFlatten(Rc::new(self), Rc::new(f))
252    }
253
254    /// Converts the chunk into a vector of references to its elements.
255    ///
256    /// This operation has O(n) complexity where n is the number of elements
257    /// in the chunk.
258    ///
259    /// # Example
260    /// ```
261    /// # use devela::VecChunk;
262    /// let chunk = VecChunk::default().append(1).append(2).append(3);
263    /// assert_eq!(chunk.as_vec(), vec![1, 2, 3]);
264    /// ```
265    pub fn as_vec(&self) -> Vec<A>
266    where
267        A: Clone,
268    {
269        let mut vec = Vec::new();
270        self.as_vec_mut(&mut vec);
271        vec
272    }
273
274    /// Helper method that populates a vector with references to the chunk's elements.
275    ///
276    /// This method is used internally by [`as_vec`](VecChunk::as_vec) to avoid
277    /// allocating multiple vectors during the traversal.
278    pub fn as_vec_mut(&self, buf: &mut Vec<A>)
279    where
280        A: Clone,
281    {
282        match self {
283            VecChunk::Empty => {}
284            VecChunk::Single(a) => {
285                buf.push(a.clone());
286            }
287            VecChunk::Concat(a, b) => {
288                a.as_vec_mut(buf);
289                b.as_vec_mut(buf);
290            }
291            VecChunk::TransformFlatten(a, f) => {
292                let mut tmp = Vec::new();
293                a.as_vec_mut(&mut tmp);
294                for elem in tmp.into_iter() {
295                    f(elem).as_vec_mut(buf);
296                }
297            }
298            VecChunk::Collect(vec) => {
299                buf.extend(vec.borrow().iter().cloned());
300            }
301        }
302    }
303}
304
305impl<A> FromIterator<A> for VecChunk<A> {
306    /// Creates a chunk from an iterator.
307    ///
308    /// # Example
309    /// ```
310    /// # use devela::VecChunk;
311    /// let vec = vec![1, 2, 3];
312    /// let chunk: VecChunk<_> = vec.into_iter().collect();
313    /// assert_eq!(chunk.as_vec(), vec![1, 2, 3]);
314    /// ```
315    fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
316        let vec: Vec<_> = iter.into_iter().collect();
317        VecChunk::Collect(Rc::new(RefCell::new(vec)))
318    }
319}