Skip to main content

gqlforge_chunk/
chunk.rs

1//! A Rust implementation of a persistent data structure for efficient append
2//! and concatenation operations.
3//!
4//! This crate provides the [`Chunk`] type, which implements a persistent data
5//! structure that allows O(1) append and concatenation operations through
6//! structural sharing.
7//!
8//! # Features
9//! - O(1) append operations
10//! - O(1) concatenation operations
11//! - Immutable/persistent data structure
12//! - Memory efficient through structural sharing
13//!
14//! # Example
15//! ```
16//! use gqlforge_chunk::Chunk;
17//!
18//! let chunk1 = Chunk::default().append(1).append(2);
19//! let chunk2 = Chunk::default().append(3).append(4);
20//! let combined = chunk1.concat(chunk2);
21//!
22//! assert_eq!(combined.as_vec(), vec![1, 2, 3, 4]);
23//! ```
24
25use std::cell::RefCell;
26use std::rc::Rc;
27
28/// A persistent data structure that provides efficient append and concatenation
29/// operations.
30///
31/// # Overview
32/// `Chunk<A>` is an immutable data structure that allows O(1) complexity for
33/// append and concatenation operations through structural sharing. It uses
34/// [`Rc`] (Reference Counting) for efficient memory management.
35///
36/// # Performance
37/// - Append operation: O(1)
38/// - Concatenation operation: O(1)
39/// - Converting to Vec: O(n)
40///
41/// # Implementation Details
42/// The data structure is implemented as an enum with three variants:
43/// - `Empty`: Represents an empty chunk
44/// - `Append`: Represents a single element appended to another chunk
45/// - `Concat`: Represents the concatenation of two chunks
46///
47/// # Examples
48/// ```
49/// use gqlforge_chunk::Chunk;
50///
51/// let mut chunk = Chunk::default();
52/// chunk = chunk.append(1);
53/// chunk = chunk.append(2);
54///
55/// let other_chunk = Chunk::default().append(3).append(4);
56/// let combined = chunk.concat(other_chunk);
57///
58/// assert_eq!(combined.as_vec(), vec![1, 2, 3, 4]);
59/// ```
60///
61/// # References
62/// - [Persistent Data Structures](https://en.wikipedia.org/wiki/Persistent_data_structure)
63/// - [Structural Sharing](https://hypirion.com/musings/understanding-persistent-vector-pt-1)
64#[derive(Clone)]
65pub enum Chunk<A> {
66    /// Represents an empty chunk with no elements
67    Empty,
68    /// Represents a chunk containing exactly one element
69    Single(A),
70    /// Represents the concatenation of two chunks, enabling O(1) concatenation
71    Concat(Rc<Chunk<A>>, Rc<Chunk<A>>),
72    /// Represents a collection of elements
73    Collect(Rc<RefCell<Vec<A>>>),
74    /// Represents a lazy transformation that flattens elements
75    TransformFlatten(Rc<Chunk<A>>, Rc<dyn Fn(A) -> Chunk<A>>),
76}
77
78impl<A> Default for Chunk<A> {
79    /// Creates a new empty chunk.
80    ///
81    /// This is equivalent to using [`Chunk::Empty`].
82    fn default() -> Self {
83        Chunk::Empty
84    }
85}
86
87impl<A> Chunk<A> {
88    /// Creates a new chunk containing a single element.
89    ///
90    /// # Arguments
91    /// * `a` - The element to store in the chunk
92    ///
93    /// # Examples
94    /// ```
95    /// use gqlforge_chunk::Chunk;
96    ///
97    /// let chunk: Chunk<i32> = Chunk::new(100);
98    /// assert!(!chunk.is_null());
99    /// ```
100    pub fn new(a: A) -> Self {
101        Chunk::Single(a)
102    }
103
104    /// Returns `true` if the chunk is empty.
105    ///
106    /// # Examples
107    /// ```
108    /// use gqlforge_chunk::Chunk;
109    ///
110    /// let chunk: Chunk<i32> = Chunk::default();
111    /// assert!(chunk.is_null());
112    ///
113    /// let non_empty = chunk.append(42);
114    /// assert!(!non_empty.is_null());
115    /// ```
116    pub fn is_null(&self) -> bool {
117        match self {
118            Chunk::Empty => true,
119            Chunk::Collect(vec) => vec.borrow().is_empty(),
120            _ => false,
121        }
122    }
123
124    /// Append a new element to the chunk.
125    ///
126    /// This operation has O(1) complexity as it creates a new `Append` variant
127    /// that references the existing chunk through an [`Rc`].
128    ///
129    /// # Examples
130    /// ```
131    /// use gqlforge_chunk::Chunk;
132    ///
133    /// let chunk = Chunk::default().append(1).append(2);
134    /// assert_eq!(chunk.as_vec(), vec![1, 2]);
135    /// ```
136    #[must_use]
137    pub fn append(self, a: A) -> Self {
138        self.concat(Chunk::new(a))
139    }
140
141    /// Prepend a new element to the beginning of the chunk.
142    ///
143    /// This operation has O(1) complexity as it creates a new `Concat` variant
144    /// that references the existing chunk through an [`Rc`].
145    ///
146    /// # Examples
147    /// ```
148    /// use gqlforge_chunk::Chunk;
149    ///
150    /// let chunk = Chunk::default().prepend(1).prepend(2);
151    /// assert_eq!(chunk.as_vec(), vec![2, 1]);
152    /// ```
153    #[must_use]
154    pub fn prepend(self, a: A) -> Self {
155        if self.is_null() {
156            Chunk::new(a)
157        } else {
158            Chunk::new(a).concat(self)
159        }
160    }
161
162    /// Concatenates this chunk with another chunk.
163    ///
164    /// This operation has O(1) complexity as it creates a new `Concat` variant
165    /// that references both chunks through [`Rc`]s.
166    ///
167    /// # Performance Optimization
168    /// If either chunk is empty, returns the other chunk instead of creating
169    /// a new `Concat` variant.
170    ///
171    /// # Examples
172    /// ```
173    /// use gqlforge_chunk::Chunk;
174    ///
175    /// let chunk1 = Chunk::default().append(1).append(2);
176    /// let chunk2 = Chunk::default().append(3).append(4);
177    /// let combined = chunk1.concat(chunk2);
178    /// assert_eq!(combined.as_vec(), vec![1, 2, 3, 4]);
179    /// ```
180    #[must_use]
181    pub fn concat(self, other: Chunk<A>) -> Chunk<A> {
182        match (self, other) {
183            // Handle null cases
184            (Chunk::Empty, other) => other,
185            (this, Chunk::Empty) => this,
186            (Chunk::Single(a), Chunk::Single(b)) => {
187                Chunk::Collect(Rc::new(RefCell::new(vec![a, b])))
188            }
189            (Chunk::Collect(vec), Chunk::Single(a)) => {
190                if Rc::strong_count(&vec) == 1 {
191                    // Only clone if there are no other references
192                    vec.borrow_mut().push(a);
193                    Chunk::Collect(vec)
194                } else {
195                    Chunk::Concat(Rc::new(Chunk::Collect(vec)), Rc::new(Chunk::Single(a)))
196                }
197            }
198            // Handle all other cases with Concat
199            (this, that) => Chunk::Concat(Rc::new(this), Rc::new(that)),
200        }
201    }
202
203    /// Transforms each element in the chunk using the provided function.
204    ///
205    /// This method creates a lazy representation of the transformation without
206    /// actually performing it. The transformation is only executed when
207    /// [`as_vec`](Chunk::as_vec) or [`as_vec_mut`](Chunk::as_vec_mut) is
208    /// called.
209    ///
210    /// # Performance
211    /// - Creating the transformation: O(1)
212    /// - Executing the transformation (during [`as_vec`](Chunk::as_vec)): O(n)
213    ///
214    /// # Arguments
215    /// * `f` - A function that takes a reference to an element of type `A` and
216    ///   returns a new element of type `A`
217    ///
218    /// # Examples
219    /// ```
220    /// use gqlforge_chunk::Chunk;
221    ///
222    /// let chunk = Chunk::default().append(1).append(2).append(3);
223    /// // This operation is O(1) and doesn't actually transform the elements
224    /// let doubled = chunk.transform(|x| x * 2);
225    /// // The transformation happens here, when we call as_vec()
226    /// assert_eq!(doubled.as_vec(), vec![2, 4, 6]);
227    /// ```
228    #[must_use]
229    pub fn transform(self, f: impl Fn(A) -> A + 'static) -> Self {
230        self.transform_flatten(move |a| Chunk::new(f(a)))
231    }
232
233    /// Materializes a chunk by converting it into a collected form.
234    ///
235    /// This method evaluates any lazy transformations and creates a new chunk
236    /// containing all elements in a `Collect` variant. This can be useful
237    /// for performance when you plan to reuse the chunk multiple times, as
238    /// it prevents re-evaluation of transformations.
239    ///
240    /// # Performance
241    /// - Time complexity: O(n) where n is the number of elements
242    /// - Space complexity: O(n) as it creates a new vector containing all
243    ///   elements
244    ///
245    /// # Examples
246    /// ```
247    /// use gqlforge_chunk::Chunk;
248    ///
249    /// let chunk = Chunk::default()
250    ///     .append(1)
251    ///     .append(2)
252    ///     .transform(|x| x * 2);  // Lazy transformation
253    ///
254    /// // Materialize the chunk to evaluate the transformation once
255    /// let materialized = chunk.materialize();
256    ///
257    /// assert_eq!(materialized.as_vec(), vec![2, 4]);
258    /// ```
259    #[must_use]
260    pub fn materialize(self) -> Chunk<A>
261    where
262        A: Clone,
263    {
264        Chunk::Collect(Rc::new(RefCell::new(self.as_vec())))
265    }
266
267    /// Transforms each element in the chunk into a new chunk and flattens the
268    /// result.
269    ///
270    /// This method creates a lazy representation of the transformation without
271    /// actually performing it. The transformation is only executed when
272    /// [`as_vec`](Chunk::as_vec) or [`as_vec_mut`](Chunk::as_vec_mut) is
273    /// called.
274    ///
275    /// # Performance
276    /// - Creating the transformation: O(1)
277    /// - Executing the transformation (during [`as_vec`](Chunk::as_vec)): O(n)
278    ///
279    /// # Arguments
280    /// * `f` - A function that takes an element of type `A` and returns a new
281    ///   `Chunk<A>`
282    ///
283    /// # Examples
284    /// ```
285    /// use gqlforge_chunk::Chunk;
286    ///
287    /// let chunk = Chunk::default().append(1).append(2);
288    /// // Transform each number x into a chunk containing [x, x+1]
289    /// let expanded = chunk.transform_flatten(|x| {
290    ///     Chunk::default().append(x).append(x + 1)
291    /// });
292    /// assert_eq!(expanded.as_vec(), vec![1, 2, 2, 3]);
293    /// ```
294    #[must_use]
295    pub fn transform_flatten(self, f: impl Fn(A) -> Chunk<A> + 'static) -> Self {
296        Chunk::TransformFlatten(Rc::new(self), Rc::new(f))
297    }
298
299    /// Converts the chunk into a vector of references to its elements.
300    ///
301    /// This operation has O(n) complexity where n is the number of elements
302    /// in the chunk.
303    ///
304    /// # Examples
305    /// ```
306    /// use gqlforge_chunk::Chunk;
307    ///
308    /// let chunk = Chunk::default().append(1).append(2).append(3);
309    /// assert_eq!(chunk.as_vec(), vec![1, 2, 3]);
310    /// ```
311    pub fn as_vec(&self) -> Vec<A>
312    where
313        A: Clone,
314    {
315        let mut vec = Vec::new();
316        self.as_vec_mut(&mut vec);
317        vec
318    }
319
320    /// Helper method that populates a vector with references to the chunk's
321    /// elements.
322    ///
323    /// This method is used internally by [`as_vec`](Chunk::as_vec) to avoid
324    /// allocating multiple vectors during the traversal.
325    ///
326    /// # Arguments
327    /// * `buf` - A mutable reference to a vector that will be populated with
328    ///   references to the chunk's elements
329    pub fn as_vec_mut(&self, buf: &mut Vec<A>)
330    where
331        A: Clone,
332    {
333        match self {
334            Chunk::Empty => {}
335            Chunk::Single(a) => {
336                buf.push(a.clone());
337            }
338            Chunk::Concat(a, b) => {
339                a.as_vec_mut(buf);
340                b.as_vec_mut(buf);
341            }
342            Chunk::TransformFlatten(a, f) => {
343                let mut tmp = Vec::new();
344                a.as_vec_mut(&mut tmp);
345                for elem in tmp {
346                    f(elem).as_vec_mut(buf);
347                }
348            }
349            Chunk::Collect(vec) => {
350                buf.extend(vec.borrow().iter().cloned());
351            }
352        }
353    }
354}
355
356impl<A> FromIterator<A> for Chunk<A> {
357    /// Creates a chunk from an iterator.
358    ///
359    /// # Examples
360    /// ```
361    /// use gqlforge_chunk::Chunk;
362    ///
363    /// let vec = vec![1, 2, 3];
364    /// let chunk: Chunk<_> = vec.into_iter().collect();
365    /// assert_eq!(chunk.as_vec(), vec![1, 2, 3]);
366    /// ```
367    fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
368        let vec: Vec<_> = iter.into_iter().collect();
369
370        Chunk::Collect(Rc::new(RefCell::new(vec)))
371    }
372}
373
374#[cfg(test)]
375mod tests {
376    use super::*;
377
378    #[test]
379    fn test_new() {
380        let chunk: Chunk<i32> = Chunk::default();
381        assert!(chunk.is_null());
382    }
383
384    #[test]
385    fn test_default() {
386        let chunk: Chunk<i32> = Chunk::default();
387        assert!(chunk.is_null());
388    }
389
390    #[test]
391    fn test_is_null() {
392        let empty: Chunk<i32> = Chunk::default();
393        assert!(empty.is_null());
394
395        let non_empty = empty.append(1);
396        assert!(!non_empty.is_null());
397    }
398
399    #[test]
400    fn test_append() {
401        let chunk = Chunk::default().append(1).append(2).append(3);
402        assert_eq!(chunk.as_vec(), vec![1, 2, 3]);
403
404        // Test that original chunk remains unchanged (persistence)
405        let chunk1 = Chunk::default().append(1);
406        let chunk2 = chunk1.clone().append(2);
407        assert_eq!(chunk1.as_vec(), vec![1]);
408        assert_eq!(chunk2.as_vec(), vec![1, 2]);
409    }
410
411    #[test]
412    fn test_concat() {
413        let chunk1 = Chunk::default().append(1).append(2);
414        let chunk2 = Chunk::default().append(3).append(4);
415        let combined = chunk1.clone().concat(chunk2.clone());
416
417        assert_eq!(combined.as_vec(), vec![1, 2, 3, 4]);
418
419        // Test concatenation with empty chunks
420        let empty = Chunk::default();
421        assert_eq!(
422            empty.clone().concat(chunk1.clone()).as_vec(),
423            chunk1.as_vec()
424        );
425        assert_eq!(
426            chunk1.clone().concat(empty.clone()).as_vec(),
427            chunk1.as_vec()
428        );
429        assert_eq!(empty.clone().concat(empty).as_vec(), Vec::<i32>::new());
430    }
431
432    #[test]
433    fn test_as_vec() {
434        // Test empty chunk
435        let empty: Chunk<i32> = Chunk::default();
436        assert_eq!(empty.as_vec(), Vec::<i32>::new());
437
438        // Test single element
439        let single = Chunk::default().append(42);
440        assert_eq!(single.as_vec(), vec![42]);
441
442        // Test multiple elements
443        let multiple = Chunk::default().append(1).append(2).append(3);
444        assert_eq!(multiple.as_vec(), vec![1, 2, 3]);
445
446        // Test complex structure with concatenation
447        let chunk1 = Chunk::default().append(1).append(2);
448        let chunk2 = Chunk::default().append(3).append(4);
449        let complex = chunk1.concat(chunk2);
450        assert_eq!(complex.as_vec(), vec![1, 2, 3, 4]);
451    }
452
453    #[test]
454    fn test_structural_sharing() {
455        let chunk1 = Chunk::default().append(1).append(2);
456        let chunk2 = chunk1.clone().append(3);
457        let chunk3 = chunk1.clone().append(4);
458
459        // Verify that modifications create new structures while preserving the original
460        assert_eq!(chunk1.as_vec(), vec![1, 2]);
461        assert_eq!(chunk2.as_vec(), vec![1, 2, 3]);
462        assert_eq!(chunk3.as_vec(), vec![1, 2, 4]);
463    }
464
465    #[test]
466    fn test_with_different_types() {
467        // Test with strings
468        let string_chunk = Chunk::default()
469            .append(String::from("hello"))
470            .append(String::from("world"));
471        assert_eq!(string_chunk.as_vec().len(), 2);
472
473        // Test with floating point numbers - using standard constants
474        let float_chunk = Chunk::default()
475            .append(std::f64::consts::PI)
476            .append(std::f64::consts::E);
477        assert_eq!(
478            float_chunk.as_vec(),
479            vec![std::f64::consts::PI, std::f64::consts::E]
480        );
481
482        // Test with boolean values
483        let bool_chunk = Chunk::default().append(true).append(false).append(true);
484        assert_eq!(bool_chunk.as_vec(), vec![true, false, true]);
485    }
486
487    #[test]
488    fn test_transform() {
489        // Test transform on empty chunk
490        let empty: Chunk<i32> = Chunk::default();
491        let transformed_empty = empty.transform(|x| x * 2);
492        assert_eq!(transformed_empty.as_vec(), Vec::<i32>::new());
493
494        // Test transform on single element
495        let single = Chunk::default().append(5);
496        let doubled = single.transform(|x| x * 2);
497        assert_eq!(doubled.as_vec(), vec![10]);
498
499        // Test transform on multiple elements
500        let multiple = Chunk::default().append(1).append(2).append(3);
501        let doubled = multiple.transform(|x| x * 2);
502        assert_eq!(doubled.as_vec(), vec![2, 4, 6]);
503
504        // Test transform with string manipulation
505        let string_chunk = Chunk::default()
506            .append(String::from("hello"))
507            .append(String::from("world"));
508        let uppercase = string_chunk.transform(|s| s.to_uppercase());
509        assert_eq!(uppercase.as_vec(), vec!["HELLO", "WORLD"]);
510
511        // Test chaining multiple transforms
512        let numbers = Chunk::default().append(1).append(2).append(3);
513        let result = numbers
514            .transform(|x| x * 2)
515            .transform(|x| x + 1)
516            .transform(|x| x * 3);
517        assert_eq!(result.as_vec(), vec![9, 15, 21]);
518    }
519
520    #[test]
521    fn test_transform_flatten() {
522        // Test transform_flatten on empty chunk
523        let empty: Chunk<i32> = Chunk::default();
524        let transformed_empty = empty.transform_flatten(|x| Chunk::new(x * 2));
525        assert_eq!(transformed_empty.as_vec(), Vec::<i32>::new());
526
527        // Test transform_flatten on single element
528        let single = Chunk::default().append(5);
529        let doubled = single.transform_flatten(|x| Chunk::new(x * 2));
530        assert_eq!(doubled.as_vec(), vec![10]);
531
532        // Test expanding each element into multiple elements
533        let numbers = Chunk::default().append(1).append(2);
534        let expanded = numbers.transform_flatten(|x| Chunk::default().append(x + 1).append(x));
535        assert_eq!(expanded.as_vec(), vec![2, 1, 3, 2]);
536
537        // Test with nested chunks
538        let chunk = Chunk::default().append(1).append(2).append(3);
539        let nested = chunk.transform_flatten(|x| {
540            if x % 2 == 0 {
541                // Even numbers expand to [x, x+1]
542                Chunk::default().append(x).append(x + 1)
543            } else {
544                // Odd numbers expand to [x]
545                Chunk::new(x)
546            }
547        });
548        assert_eq!(nested.as_vec(), vec![1, 2, 3, 3]);
549
550        // Test chaining transform_flatten operations
551        let numbers = Chunk::default().append(1).append(2);
552        let result = numbers
553            .transform_flatten(|x| Chunk::default().append(x).append(x))
554            .transform_flatten(|x| Chunk::default().append(x).append(x + 1));
555        assert_eq!(result.as_vec(), vec![1, 2, 1, 2, 2, 3, 2, 3]);
556
557        // Test with empty chunk results
558        let chunk = Chunk::default().append(1).append(2);
559        let filtered = chunk.transform_flatten(|x| {
560            if x % 2 == 0 {
561                Chunk::new(x)
562            } else {
563                Chunk::default() // Empty chunk for odd numbers
564            }
565        });
566        assert_eq!(filtered.as_vec(), vec![2]);
567    }
568
569    #[test]
570    fn test_prepend() {
571        let chunk = Chunk::default().prepend(1).prepend(2).prepend(3);
572        assert_eq!(chunk.as_vec(), vec![3, 2, 1]);
573
574        // Test that original chunk remains unchanged (persistence)
575        let chunk1 = Chunk::default().prepend(1);
576        let chunk2 = chunk1.clone().prepend(2);
577        assert_eq!(chunk1.as_vec(), vec![1]);
578        assert_eq!(chunk2.as_vec(), vec![2, 1]);
579
580        // Test mixing prepend and append
581        let mixed = Chunk::default()
582            .prepend(1) // [1]
583            .append(2) // [1, 2]
584            .prepend(3); // [3, 1, 2]
585        assert_eq!(mixed.as_vec(), vec![3, 1, 2]);
586    }
587
588    #[test]
589    fn test_from_iterator() {
590        // Test collecting from an empty iterator
591        let empty_vec: Vec<i32> = vec![];
592        let empty_chunk: Chunk<i32> = empty_vec.into_iter().collect();
593        assert!(empty_chunk.is_null());
594
595        // Test collecting from a vector
596        let vec = vec![1, 2, 3];
597        let chunk: Chunk<_> = vec.into_iter().collect();
598        assert_eq!(chunk.as_vec(), vec![1, 2, 3]);
599
600        // Test collecting from a range
601        let range_chunk: Chunk<_> = (1..=5).collect();
602        assert_eq!(range_chunk.as_vec(), vec![1, 2, 3, 4, 5]);
603
604        // Test collecting from map iterator
605        let doubled: Chunk<_> = vec![1, 2, 3].into_iter().map(|x| x * 2).collect();
606        assert_eq!(doubled.as_vec(), vec![2, 4, 6]);
607    }
608
609    #[test]
610    fn test_concat_optimization() {
611        // Create a collected chunk
612        let collected: Chunk<i32> = vec![1, 2, 3].into_iter().collect();
613
614        // Concat a single element
615        let result = collected.concat(Chunk::Single(4));
616
617        // Verify the result
618        assert_eq!(result.as_vec(), vec![1, 2, 3, 4]);
619
620        // Verify it's still a Collect variant (not a Concat)
621        match result {
622            Chunk::Collect(_) => (), // This is what we want
623            _ => panic!("Expected Collect variant after optimization"),
624        }
625    }
626}