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

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