Billy Barrow 6 days ago
parent
commit
f6a3173f7e

+ 4 - 13
src/lib/Enumerable.vala

@@ -50,7 +50,7 @@ namespace Invercargill {
             if(peeked == null)
             if(peeked == null)
                 array = new T[1024];
                 array = new T[1024];
             else
             else
-                array = new T[(int)peeked];
+                array = new T[int.max(1,(int)peeked)];
 
 
             var index = 0;
             var index = 0;
             foreach (var item in this) {
             foreach (var item in this) {
@@ -221,19 +221,11 @@ namespace Invercargill {
         }
         }
 
 
         public virtual Enumerable<T> skip_last(int count) {
         public virtual Enumerable<T> skip_last(int count) {
-            return Iterate.deferred<T>(() => {
-                var buffer = to_immutable_buffer();
-                return buffer.take(int.max(0, (int)buffer.length - count));
-            });
+            return new Modifiers.SkipLast<T>(this, count);
         }
         }
 
 
-        // TODO: This one could realistically be a modifier using a ring buffer so we don't have to
-        // buffer the whole sequence
         public virtual Enumerable<T> take_last(int count) {
         public virtual Enumerable<T> take_last(int count) {
-            return Iterate.deferred<T>(() => {
-                var buffer = to_immutable_buffer();
-                return buffer.skip(int.max(0, (int)buffer.length - count));
-            });
+            return new Modifiers.TakeLast<T>(this, count);
         }
         }
 
 
         public virtual Enumerable<TOut> cast<TOut>() {
         public virtual Enumerable<TOut> cast<TOut>() {
@@ -351,8 +343,7 @@ namespace Invercargill {
         }
         }
 
 
         public virtual Enumerable<TOut> fork<TOut>(owned TransformDelegate<T, TOut> fork1, owned TransformDelegate<T, TOut> fork2) {
         public virtual Enumerable<TOut> fork<TOut>(owned TransformDelegate<T, TOut> fork1, owned TransformDelegate<T, TOut> fork2) {
-            var seq = to_series();
-            return seq.select<TOut>((owned)fork1).interleave(seq.select<TOut>((owned)fork2));
+            return new Modifiers.Fork<T, TOut>(this, (owned)fork1, (owned)fork2);
         }
         }
 
 
         public virtual Enumerable<TOut> fork_many<TOut>(owned TransformDelegate<T, Enumerable<TOut>> fork1, owned TransformDelegate<T, Enumerable<TOut>> fork2) {
         public virtual Enumerable<TOut> fork_many<TOut>(owned TransformDelegate<T, Enumerable<TOut>> fork1, owned TransformDelegate<T, Enumerable<TOut>> fork2) {

+ 68 - 0
src/lib/Modifiers/Fork.vala

@@ -0,0 +1,68 @@
+namespace Invercargill.Modifiers {
+
+    public class Fork<T, TOut> : Enumerable<TOut> {
+        private Enumerable<T> input;
+        private TransformDelegate<T, TOut> fork1;
+        private TransformDelegate<T, TOut> fork2;
+        private bool use_first;
+
+        public Fork(Enumerable<T> input, owned TransformDelegate<T, TOut> fork1, owned TransformDelegate<T, TOut> fork2) {
+            this.input = input;
+            this.fork1 = (owned)fork1;
+            this.fork2 = (owned)fork2;
+            this.use_first = true;
+        }
+
+        public override int? peek_count() {
+            var c = input.peek_count();
+            if(c == null)
+                return null;
+            return c * 2; // We'll produce two items for each input item
+        }
+
+        public override EnumerableInfo get_info() {
+            return new EnumerableInfo.infer_single(this, EnumerableCategory.COMPUTED, input);
+        }
+
+        public override Tracker<TOut> get_tracker() {
+            var tracker = input.get_tracker();
+            TOut next1 = null;
+            TOut next2 = null;
+            bool has_next1 = false;
+            bool has_next2 = false;
+
+            return new AdvanceTracker<TOut>((out obj) => {
+                obj = null;
+                
+                // If we don't have the next pair of items, try to get them
+                if(!has_next1 && !has_next2) {
+                    if(tracker.has_next()) {
+                        var item = tracker.get_next();
+                        next1 = fork1(item);
+                        next2 = fork2(item);
+                        has_next1 = true;
+                        has_next2 = true;
+                    }
+                }
+                
+                // Return the first item if available
+                if(has_next1) {
+                    obj = next1;
+                    has_next1 = false;
+                    return true;
+                }
+                
+                // Return the second item if available
+                if(has_next2) {
+                    obj = next2;
+                    has_next2 = false;
+                    return true;
+                }
+                
+                // No more items
+                return false;
+            });
+        }
+    }
+
+}

+ 50 - 0
src/lib/Modifiers/SkipLast.vala

@@ -0,0 +1,50 @@
+namespace Invercargill.Modifiers {
+
+    public class SkipLast<T> : Enumerable<T> {
+
+        private Enumerable<T> input;
+        private int n_items {get; set;}
+
+        public SkipLast(Enumerable<T> input, int skip) {
+            this.input = input;
+            n_items = skip;
+        }
+
+        public override int? peek_count() {
+            var c = input.peek_count();
+            if(c == null)
+                return null;
+            return int.max(0, c - n_items);
+        }
+
+        public override EnumerableInfo get_info() {
+            return new EnumerableInfo.infer_single(this, EnumerableCategory.COMPUTED, input);
+        }
+
+        public override Tracker<T> get_tracker() {
+            var tracker = input.get_tracker();
+            var buffer = new DataStructures.Vector<T>();
+            
+            return new AdvanceTracker<T>((out obj) => {
+                obj = null;
+                
+                // Keep filling the buffer until we have n_items more than we need to return
+                while(tracker.has_next()) {
+                    var item = tracker.get_next();
+                    buffer.add(item);
+                    
+                    // Once we have more than n_items in buffer, we can start returning items
+                    if(buffer.count() > n_items) {
+                        obj = buffer[0];
+                        buffer.remove_at(0);
+                        return true;
+                    }
+                }
+                
+                // If we reach here, we've exhausted the input
+                return false;
+            });
+        }
+    }
+
+}

+ 80 - 0
src/lib/Modifiers/TakeLast.vala

@@ -0,0 +1,80 @@
+namespace Invercargill.Modifiers {
+
+    public class TakeLast<T> : Enumerable<T> {
+        private Enumerable<T> input;
+        private int n_items {get; set;}
+
+        public TakeLast(Enumerable<T> input, int count) {
+            this.input = input;
+            n_items = count;
+        }
+
+        public override int? peek_count() {
+            var c = input.peek_count();
+            if(c == null)
+                return null;
+            return int.min(c, n_items);
+        }
+
+        public override EnumerableInfo get_info() {
+            return new EnumerableInfo.infer_single(this, EnumerableCategory.COMPUTED, input);
+        }
+
+        public override Tracker<T> get_tracker() {
+            var tracker = input.get_tracker();
+            DataStructures.RingBuffer<T>? buffer = null;
+            var index = 0;
+            var input_exhausted = false;
+            var output_index = 0;
+            var output_started = false;
+            var actual_count = 0;
+            
+            return new AdvanceTracker<T>((out obj) => {
+                obj = null;
+                
+                // First, fill the buffer with all items from the input
+                if (!input_exhausted) {
+                    // Initialize buffer with the requested size
+                    buffer = new DataStructures.RingBuffer<T>((uint)n_items);
+                    
+                    while(tracker.has_next()) {
+                        var item = tracker.get_next();
+                        
+                        // Add item to buffer, overwriting oldest if buffer is full
+                        buffer.set((uint)index, item);
+                        index = (index + 1) % n_items;
+                        
+                        // Track how many items we've actually seen
+                        if (actual_count < n_items) {
+                            actual_count++;
+                        }
+                    }
+                    input_exhausted = true;
+                    
+                    // Set the output index to the oldest item
+                    if (actual_count == n_items) {
+                        // Buffer is full, oldest item is at current index
+                        output_index = index;
+                    } else {
+                        // Buffer not full, start from beginning
+                        output_index = 0;
+                    }
+                }
+                
+                // After exhausting the input, return items from the buffer
+                if (!output_started) {
+                    output_started = true;
+                }
+                
+                if(output_index < actual_count) {
+                    obj = buffer.get((uint)output_index);
+                    output_index++;
+                    return true;
+                }
+                
+                return false;
+            });
+        }
+    }
+
+}

+ 3 - 0
src/lib/meson.build

@@ -40,6 +40,9 @@ sources += files('Modifiers/Merge.vala')
 sources += files('Modifiers/Sort.vala')
 sources += files('Modifiers/Sort.vala')
 sources += files('Modifiers/Skip.vala')
 sources += files('Modifiers/Skip.vala')
 sources += files('Modifiers/Take.vala')
 sources += files('Modifiers/Take.vala')
+sources += files('Modifiers/SkipLast.vala')
+sources += files('Modifiers/TakeLast.vala')
+sources += files('Modifiers/Fork.vala')
 sources += files('Modifiers/ParallelTransform.vala')
 sources += files('Modifiers/ParallelTransform.vala')
 sources += files('Modifiers/ParallelFilter.vala')
 sources += files('Modifiers/ParallelFilter.vala')
 sources += files('Modifiers/Unique.vala')
 sources += files('Modifiers/Unique.vala')