Browse Source

Add many untested modifiers

Billy Barrow 1 week ago
parent
commit
e25fce625d

+ 51 - 2
src/lib/Enumerable.vala

@@ -327,8 +327,11 @@ namespace Invercargill {
 
 
         public virtual Enumerable<Grouping<TKey, T>> group_by<TKey>(owned TransformDelegate<T, TKey> key_selector, owned EqualityDelegate<TKey>? key_equality = null) {
         public virtual Enumerable<Grouping<TKey, T>> group_by<TKey>(owned TransformDelegate<T, TKey> key_selector, owned EqualityDelegate<TKey>? key_equality = null) {
             var equality = key_equality ?? Operators.equality<TKey>();
             var equality = key_equality ?? Operators.equality<TKey>();
-            var keys = select<TKey>(i => key_selector(i)).distinct((a, b) => equality(a, b));
-            return keys.select<Grouping<TKey, T>>(g => new Grouping<TKey, T>(g, this.where(i => equality(g, key_selector(i)))));
+            return Iterate.deferred<Grouping<TKey, T>>(() => {
+                var items  = this.cache();
+                var keys = items.select<TKey>(i => key_selector(i)).distinct((a, b) => equality(a, b));
+                return keys.select<Grouping<TKey, T>>(g => new Grouping<TKey, T>(g, items.where(i => equality(g, key_selector(i)))));
+            });
         }
         }
 
 
         public virtual Enumerable<T> combine_by<TKey>(Enumerable<T> other, owned TransformDelegate<T, TKey> key_selector, owned HashDelegate<TKey>? hash_func = null, owned EqualityDelegate<TKey>? equal_func = null) {
         public virtual Enumerable<T> combine_by<TKey>(Enumerable<T> other, owned TransformDelegate<T, TKey> key_selector, owned HashDelegate<TKey>? hash_func = null, owned EqualityDelegate<TKey>? equal_func = null) {
@@ -560,6 +563,52 @@ namespace Invercargill {
             return this;
             return this;
         }
         }
 
 
+        public virtual Enumerable<Enumerable<T>> chunk(int chunk_size) {
+            return new Chunk<T>(this, chunk_size);
+        }
+
+        public virtual Enumerable<T> cycle(int cycles = -1) {
+            return new Cycle<T>(this, cycles);
+        }
+
+        public virtual Enumerable<Grouping<TKey, T>> group_adjacent_by<TKey>(owned TransformDelegate<T, TKey> key_selector, owned EqualityDelegate<TKey>? key_equality = null) {
+            return new GroupSequential<T, TKey>(this, key_selector, key_equality);
+        }
+
+        public virtual Enumerable<KeyCountPair<TKey>> histogram_by<TKey>(owned TransformDelegate<T, TKey> key_selector, owned EqualityDelegate<TKey>? key_equality = null) {
+            var equality = key_equality ?? Operators.equality<TKey>();
+            return Iterate.deferred<KeyCountPair<TKey>>(() => {
+                var items  = this.cache();
+                var keys = items.select<TKey>(i => key_selector(i)).distinct((a, b) => equality(a, b));
+                return keys.select<KeyCountPair<TKey>>(k => new KeyCountPair<TKey>(k, items.where(i => equality(k, key_selector(i))).count()));
+            });
+        }
+
+        public virtual Enumerable<KeyCountPair<T>> histogram(owned EqualityDelegate<T>? key_equality = null) {
+            return histogram_by<T>(i => i, (owned)key_equality);
+        }
+
+        public virtual Enumerable<T> reverse() {
+            return new Reverse<T>(this);
+        }
+
+        public virtual Enumerable<Enumerable<T>> window(uint window_size) {
+            return new Window<T>(this, window_size);
+        }
+
+        public virtual Enumerable<T> pad_end(uint minimum_length, T pad_item) {
+            return new Padding<T>(this, pad_item, minimum_length);
+        }
+
+        public virtual Enumerable<T> pad_start(uint minimum_length, T pad_item) {
+            // This is cursed
+            return reverse().pad_end(minimum_length, pad_item).reverse();
+        }
+
+        public virtual Enumerable<TOut> scan<TOut>(TOut initial_value, owned AggregateDelegate<T, TOut> scanning_delegate) {
+            return new Modifiers.Scanner<T, TOut>(this, initial_value, (owned)scanning_delegate);
+        }
+
         public virtual void debug_dump(string additional_message = "", StringifyDelegate<T>? stringifier = null, DebugOutputDelegate? output_func = null, bool formatting = true) {
         public virtual void debug_dump(string additional_message = "", StringifyDelegate<T>? stringifier = null, DebugOutputDelegate? output_func = null, bool formatting = true) {
             DebugPrinter.print_enumerable_dump<T>(this, stringifier, additional_message, output_func, formatting);
             DebugPrinter.print_enumerable_dump<T>(this, stringifier, additional_message, output_func, formatting);
         }
         }

+ 17 - 0
src/lib/KeyCountPair.vala

@@ -0,0 +1,17 @@
+namespace Invercargill {
+
+    public class KeyCountPair<TKey> {
+
+        public TKey key { get; private set; }
+        public uint count { get; private set; }
+
+        public KeyCountPair(TKey key, uint count) {
+            this.key = key;
+            this.count = count;
+        }
+
+        public Type key_type { get { return typeof(TKey); }}
+
+    }
+
+}

+ 40 - 0
src/lib/Modifiers/Chunk.vala

@@ -0,0 +1,40 @@
+namespace Invercargill.Modifiers {
+
+    public class Chunk<T> : Enumerable<Enumerable<T>> {
+        private int chunk_size;
+        private Enumerable<T> input;
+
+        public Chunk(Enumerable<T> input, int chunk_size) {
+            this.input = input;
+            this.chunk_size = chunk_size;
+        }
+
+        public override int? peek_count() {
+            var inner = peek_count();
+            if(inner == null) {
+                return null;
+            }
+
+            return (inner / chunk_size) + (inner % chunk_size == 0 ? 0 : 1);
+        }
+
+        public override EnumerableInfo get_info() {
+            return new EnumerableInfo.infer_single(this, EnumerableCategory.COMPUTED, input);
+        }        
+
+        public override Tracker<Enumerable<T>> get_tracker() {
+            var tracker = input.get_tracker();
+
+            return new LambdaTracker<Enumerable<T>>(
+                () => tracker.has_next(),
+                () => {
+                    var items = new DataStructures.Series<T>();
+                    for(int i = 0; i < chunk_size && tracker.has_next(); i++) {
+                        items.add(tracker.get_next());
+                    }
+                    return items.seal();
+                });
+        }
+    }
+
+}

+ 53 - 0
src/lib/Modifiers/Cycle.vala

@@ -0,0 +1,53 @@
+namespace Invercargill.Modifiers {
+
+    public class Cycle<T> : Enumerable<T> {
+        private int cycles;
+        private Enumerable<T> input;
+
+        public Cycle(Enumerable<T> input, int cycles = -1) {
+            this.input = input;
+            this.cycles = cycles;
+        }
+
+        public override int? peek_count() {
+            var inner = peek_count();
+            if(inner == null || cycles < 0) {
+                return null;
+            }
+            return inner * cycles;
+        }
+
+        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 count = 0;
+
+            return new LambdaTracker<T>(
+                () => {
+                    var has_next = tracker.has_next();
+                    if(has_next) {
+                        return true;
+                    }
+                    // Infinate cycle stops if a fresh tracker has no elements
+                    if(cycles < 0) {
+                        tracker = input.get_tracker();
+                        return tracker.has_next();
+                    }
+                    // If a new tracker has no elements, keep refreshing until we run out of cycles
+                    while(count < cycles) {
+                        tracker = input.get_tracker();
+                        count++;
+                        if(tracker.has_next()) {
+                            return true;
+                        }
+                    }
+                    return false;
+                },
+                () => tracker.get_next());
+        }
+    }
+
+}

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

@@ -0,0 +1,50 @@
+namespace Invercargill.Modifiers {
+
+    public class GroupSequential<T,TKey> : Enumerable<Grouping<TKey, T>> {
+        private EqualityDelegate<TKey> equal_func;
+        private TransformDelegate<T, TKey> key_selector;
+        private Enumerable<T> input;
+
+        public GroupSequential(Enumerable<T> input, owned TransformDelegate<T, TKey> key_selector, owned EqualityDelegate<T>? equal_func = null) {
+            this.input = input;
+            this.key_selector = (owned)key_selector;
+            this.equal_func = (owned)equal_func ?? Operators.equality<TKey>();
+        }
+
+        public override int? peek_count() {
+            return null;
+        }
+
+        public override EnumerableInfo get_info() {
+            return new EnumerableInfo.infer_single(this, EnumerableCategory.COMPUTED, input);
+        }        
+
+        public override Tracker<Grouping<TKey, T>> get_tracker() {
+            var tracker = input.get_tracker();
+
+            return new AdvanceTracker<Grouping<TKey, T>>((out obj) => {
+                var items = new DataStructures.Series<T>();
+                TKey key = null;
+                while(tracker.next()) {
+                    var item = tracker.get();
+                    items.add(item);
+                    if(items.length == 1) {
+                        key = key_selector(item);
+                        continue;
+                    }
+                    if(!equal_func(key, key_selector(item))) {
+                        break;
+                    }
+                }
+                
+                if(items.length == 0) {
+                    obj = null;
+                    return false;
+                }
+                obj = new Grouping<TKey, T>(key, items.seal());
+                return true;
+            });
+        }
+    }
+
+}

+ 0 - 0
src/lib/Modifiers/Histogram.vala


+ 42 - 0
src/lib/Modifiers/Padding.vala

@@ -0,0 +1,42 @@
+namespace Invercargill.Modifiers {
+
+    public class Padding<T> : Enumerable<T> {
+        private uint min_length;
+        private Enumerable<T> input;
+        private T pad_item;
+
+        public Padding(Enumerable<T> input, T pad_item, uint min_length) {
+            this.input = input;
+            this.min_length = min_length;
+            this.pad_item = pad_item;
+        }
+
+        public override int? peek_count() {
+            var inner = peek_count();
+            if(inner == null) {
+                return null;
+            }
+            return int.max(inner, (int)min_length);
+        }
+
+        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 count = 0;
+
+            return new LambdaTracker<T>(
+                () => count < min_length || tracker.has_next(),
+                () => {
+                    count++;
+                    if(tracker.has_next()) {
+                        return tracker.get_next();
+                    }
+                    return pad_item;
+                });
+        }
+    }
+
+}

+ 24 - 0
src/lib/Modifiers/Reverse.vala

@@ -0,0 +1,24 @@
+namespace Invercargill.Modifiers {
+
+    public class Reverse<T> : Enumerable<T> {
+        private Enumerable<T> input;
+
+        public Reverse(Enumerable<T> input) {
+            this.input = input;
+        }
+
+        public override int? peek_count() {
+            return input.peek_count();
+        }
+
+        public override EnumerableInfo get_info() {
+            return new EnumerableInfo.infer_single(this, EnumerableCategory.COMPUTED, input);
+        }        
+
+        public override Tracker<T> get_tracker() {
+            var items = input.to_immutable_buffer();
+            return Iterate.range((int)items.length - 1, -1, -1).select<T>(i => items.get_or_default(i)).get_tracker();
+        }
+    }
+
+}

+ 32 - 0
src/lib/Modifiers/Scanner.vala

@@ -0,0 +1,32 @@
+namespace Invercargill.Modifiers {
+
+    public class Scanner<TIn, TOut> : Enumerable<TOut> {
+        private Enumerable<TIn> input;
+        private TOut initial;
+        private AggregateDelegate<TIn, TOut> aggregating_func;
+
+        public Scanner(Enumerable<TIn> input, TOut initial, owned AggregateDelegate<TIn, TOut> aggregating_func) {
+            this.input = input;
+            this.initial = initial;
+            aggregating_func = (owned)aggregating_func;
+        }
+
+        public override int? peek_count() {
+            return input.peek_count();
+        }
+
+        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();
+            var head = initial;
+            return new LambdaTracker<TOut>(
+                () => tracker.has_next(),
+                () => head = aggregating_func(tracker.get_next(), head)
+            );
+        }
+    }
+
+}

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

@@ -0,0 +1,68 @@
+namespace Invercargill.Modifiers {
+
+    public class Window<T> : Enumerable<Enumerable<T>> {
+        private uint window_size;
+        private Enumerable<T> input;
+
+        public Window(Enumerable<T> input, uint window_size) requires (window_size > 0) {
+            this.input = input;
+            this.window_size = window_size;
+        }
+
+        public override int? peek_count() {
+            var result = input.peek_count();
+            if(result == null){
+                return null;
+            }
+            if(result == 0) {
+                return 0;
+            }
+            if(result < window_size) {
+                return 1;
+            }
+            return (result - (int)window_size) + 1;
+        }
+
+        public override EnumerableInfo get_info() {
+            return new EnumerableInfo.infer_single(this, EnumerableCategory.COMPUTED, input);
+        }        
+
+        public override Tracker<Enumerable<T>> get_tracker() {
+            var items = new DataStructures.RingBuffer<T>(window_size);
+            var tracker = input.get_tracker();
+            var read_index = 0;
+            var write_index = 0;
+
+            // Prefill the ring buffer to a full window except for one item
+            for(write_index = 0; write_index < window_size - 1; write_index++) {
+                if(!tracker.has_next()) {
+                    // Premature exit, less items in input than the window size,
+                    // return empty enumerable if no items.
+                    if (write_index == 0) {
+                        return Iterate.nothing<Enumerable<T>>().get_tracker();
+                    }
+                    break;
+                }
+                non_error(() => items[write_index] = tracker.get_next());
+            }
+
+            // If there are no more items from the upstream tracker, then we don't have a full window.
+            // Return single truncated window instead.
+            if(!tracker.has_next()) {
+                return Iterate.single<Enumerable<T>>(items.take(write_index)).get_tracker();
+            }
+
+            return new LambdaTracker<Enumerable<T>>(
+                () => tracker.has_next(),
+                () => {
+                    non_error(() => items[write_index] = tracker.get_next());
+                    var enumerable = items.skip(read_index).take((int)window_size);
+                    read_index++;
+                    write_index++;
+                    return enumerable;
+                }
+            );
+        }
+    }
+
+}

+ 9 - 0
src/lib/meson.build

@@ -19,6 +19,7 @@ sources += files('Promotion.vala')
 sources += files('Grouping.vala')
 sources += files('Grouping.vala')
 sources += files('Interfaces.vala')
 sources += files('Interfaces.vala')
 sources += files('KeyValuePair.vala')
 sources += files('KeyValuePair.vala')
+sources += files('KeyCountPair.vala')
 sources += files('Attempt.vala')
 sources += files('Attempt.vala')
 sources += files('Cache.vala')
 sources += files('Cache.vala')
 sources += files('Proxy.vala')
 sources += files('Proxy.vala')
@@ -56,6 +57,14 @@ sources += files('Modifiers/Union.vala')
 sources += files('Modifiers/Difference.vala')
 sources += files('Modifiers/Difference.vala')
 sources += files('Modifiers/Intersect.vala')
 sources += files('Modifiers/Intersect.vala')
 sources += files('Modifiers/SymmetricDifference.vala')
 sources += files('Modifiers/SymmetricDifference.vala')
+sources += files('Modifiers/Chunk.vala')
+sources += files('Modifiers/Cycle.vala')
+sources += files('Modifiers/GroupSequential.vala')
+sources += files('Modifiers/Histogram.vala')
+sources += files('Modifiers/Padding.vala')
+sources += files('Modifiers/Reverse.vala')
+sources += files('Modifiers/Scanner.vala')
+sources += files('Modifiers/Window.vala')
 
 
 sources += files('Wrappers/Array.vala')
 sources += files('Wrappers/Array.vala')
 sources += files('Wrappers/GeeIterable.vala')
 sources += files('Wrappers/GeeIterable.vala')