Skip to main content

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

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