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}