4e1153a6c2ca — Nathan Michaels tip 4 years ago
Add iterators, fix some bugs, add fold.

New iterators: Repeat, Count
Fix: SliceIter, Range.

First is fold, because it's a function. Map will have to either
allocate memory or be a type. Maybe I'll do both.
3 files changed, 262 insertions(+), 39 deletions(-)

A => .hgignore
M src/iterators.zig
M src/rangespeed.zig
A => .hgignore +3 -0
@@ 0,0 1,3 @@ 
+syntax: glob
+zig-cache
+rangespeed*

          
M src/iterators.zig +253 -33
@@ 1,7 1,6 @@ 
-//! This module contains iterators for use in while loops. Some
-//! iterators accept other iterators. Any struct that exposes a "next"
-//! method can be used with most of those, though some also require
-//! "reset".
+//! This module contains iterators for use in while loops, and some
+//! functions that operate on them. Some iterators accept other
+//! iterators.
 //!
 //! To use any iterator in this module, follow this pattern:
 //! var specialized = IterType(args).init();

          
@@ 15,29 14,138 @@ 
 //! shared across threads.
 const std = @import("std");
 
+/// Iterator interface. Just passes calls straight on from next to
+/// nextFn and from reset to resetFn.
 pub fn Iterator(comptime T: type) type {
     return struct {
         const Self = @This();
+
+        /// Returns null when iterator is exhausted.
         nextFn: fn (self: *Self) ?T,
+
+        /// Resets the iterator to its initial state.
         resetFn: fn (self: *Self) void,
+
+        /// Returns null when iterator is exhausted.
         pub fn next(self: *Self) ?T {
             return self.nextFn(self);
         }
+
+        /// Resets the iterator to its initial state.
         pub fn reset(self: *Self) void {
             return self.resetFn(self);
         }
     };
 }
 
-/// Half-open range type. Call next() on it to get values out. Example
-/// usage:
+/// An iterator that repeats a single value, either forever or a
+/// certain number of times.
+pub fn Repeat(comptime T: type) type {
+    return struct {
+        const Self = @This();
+
+        /// Take the address of this to get a usable Iterator.
+        iterator: Iterator(T),
+        val: T,
+        limit: ?struct { count: usize, sent: usize },
+
+        /// Return the value, or null if finite and exhausted.
+        pub fn next(iterator: *Iterator(T)) ?T {
+            const self = @fieldParentPtr(Self, "iterator", iterator);
+            if (self.limit) |limit| {
+                if (limit.count == limit.sent) {
+                    return null;
+                }
+                self.limit = .{ .count = limit.count, .sent = limit.sent + 1 };
+            }
+            return self.val;
+        }
+
+        /// Does nothing if the iterator is infinite. Otherwise resets
+        /// the count.
+        pub fn reset(iterator: *Iterator(T)) void {
+            const self = @fieldParentPtr(Self, "iterator", iterator);
+            if (self.limit) |limit| {
+                self.limit = .{ .count = limit.count, .sent = 0 };
+            }
+        }
+
+        /// val is the value repeated. If count is null, this iterator
+        /// is infinete. Otherwise, it repeats count times.
+        pub fn init(val: T, count: ?usize) Self {
+            if (count) |cnt| {
+                return Self{
+                    .iterator = Iterator(T){
+                        .nextFn = next,
+                        .resetFn = reset,
+                    },
+                    .val = val,
+                    .limit = .{ .count = cnt, .sent = 0 },
+                };
+            } else {
+                return Self{
+                    .iterator = Iterator(T){
+                        .nextFn = next,
+                        .resetFn = reset,
+                    },
+                    .val = val,
+                    .limit = null,
+                };
+            }
+        }
+    };
+}
+
+/// Count unboundedly, starting at start by step. Note that this will
+/// invoke safety-checked undefined behavior if T is allowed to overflow.
+pub fn Count(comptime T: type) type {
+    return struct {
+        const Self = @This();
+
+        /// Take the address of this to get a usable Iterator.
+        iterator: Iterator(T),
+        next_val: T,
+        start: T,
+        step: T,
+
+        /// This is an "infinite" counter, so it never returns
+        /// null. It will, however, invoke safety-checked illegal
+        /// behavior if T is allowed to overflow.
+        pub fn next(iterator: *Iterator(T)) ?T {
+            const self = @fieldParentPtr(Self, "iterator", iterator);
+            const v = self.next_val;
+            self.next_val += self.step;
+            return v;
+        }
+
+        /// Start over at whatever the initial value of start was.
+        pub fn reset(iterator: *Iterator(T)) void {
+            const self = @fieldParentPtr(Self, "iterator", iterator);
+            self.next_val = self.start;
+        }
+
+        /// Return an instance that starts with start and counts by step.
+        /// For example, init(7, 3) would yield 7, 10, 13...
+        pub fn init(start: T, step: T) Self {
+            return Self{
+                .iterator = Iterator(T){ .nextFn = next, .resetFn = reset },
+                .next_val = start,
+                .start = start,
+                .step = step,
+            };
+        }
+    };
+}
+
+/// Half-open range type. Call next() on its iterator to get values
+/// out. Example usage:
 ///
-/// var range = try Range(u32, 1).init(0, 10);
+/// var range = try Range(u32).init(0, 10, 1);
 /// var iter = &range.iterator;
 /// while (iter.next()) |n| {
 ///     std.debug.warn("{}\n", .{n});
 /// }
-pub fn Range(comptime T: type, step: T) type {
+pub fn Range(comptime T: type) type {
     // Some profiling results, as run on an i5-9600KF: This is
     // consistently a good deal slower than
     // u32 i = 0; while (i < limit) : (i += 1) {}

          
@@ 56,18 164,20 @@ pub fn Range(comptime T: type, step: T) 
     // loops down by about a factor of 2. These benchmarks should
     // probably be re-run when the compiler is more stable.
     return struct {
+        /// Take the address of this to get a usable Iterator.
         iterator: Iterator(T),
         next_val: T,
         start: T,
+        step: T,
         end: T,
         const Self = @This();
 
-        /// Return the next element in the range, or null if the range
-        /// has been exhausted.
+        /// Return the next element in the range, or null if the last
+        /// element in the range has already been returned.
         pub fn next(iterator: *Iterator(T)) ?T {
             const self = @fieldParentPtr(Self, "iterator", iterator);
             const rv = self.next_val;
-            if (step < 0) {
+            if (self.step < 0) {
                 if (rv <= self.end) {
                     return null;
                 }

          
@@ 77,7 187,7 @@ pub fn Range(comptime T: type, step: T) 
                 }
             }
 
-            self.next_val += step;
+            self.next_val += self.step;
             return rv;
         }
 

          
@@ 87,8 197,11 @@ pub fn Range(comptime T: type, step: T) 
             self.next_val = self.start;
         }
 
-        /// Initialize. Returns error if step size is invalid.
-        pub fn init(start: T, end: T) !Self {
+        /// Initialize. Returns error if step size is invalid.  Range
+        /// runs from start (inclusive) to end (exclusive),
+        /// incrementing by step. So init(2, 7, 3) would yield 2, then
+        /// 5, then null.
+        pub fn init(start: T, end: T, step: T) !Self {
             if (step == 0) {
                 return error.ZeroStepSize;
             }

          
@@ 96,6 209,7 @@ pub fn Range(comptime T: type, step: T) 
                 .next_val = start,
                 .start = start,
                 .end = end,
+                .step = step,
                 .iterator = Iterator(T){
                     .nextFn = next,
                     .resetFn = reset,

          
@@ 152,29 266,40 @@ pub fn Reversed(comptime T: type) type {
 pub fn SliceIter(comptime T: type) type {
     return struct {
         const Self = @This();
-        idx = ?usize,
+        idx: ?usize,
         slice: []const T,
+        iterator: Iterator(T),
 
         /// Initialize with a slice.
         pub fn init(slice: []const T) Self {
-            return Self{ .idx = 0, .slice = slice };
+            return Self{
+                .idx = 0,
+                .slice = slice,
+                .iterator = Iterator(T){
+                    .nextFn = next,
+                    .resetFn = reset,
+                },
+            };
         }
 
         /// Reset the iterator to the start.
-        pub fn reset(self: *Self) void {
+        pub fn reset(iterator: *Iterator(T)) void {
+            const self = @fieldParentPtr(Self, "iterator", iterator);
             self.idx = 0;
         }
 
         /// Return the next element in the slice, or null if the whole
         /// slice has been consumed.
-        pub fn next(self: *Self) ?T {
-            if (idx) |i| {
+        pub fn next(iterator: *Iterator(T)) ?T {
+            const self = @fieldParentPtr(Self, "iterator", iterator);
+            if (self.idx) |i| {
                 if (i < self.slice.len) {
-                    defer self.idx += 1;
+                    self.idx = i + 1;
+                    return self.slice[i];
                 } else {
-                    defer self.idx = null;
+                    self.idx = null;
+                    return null;
                 }
-                return self.slice[i];
             } else {
                 return null;
             }

          
@@ 187,8 312,8 @@ pub fn SliceIter(comptime T: type) type 
 /// iterators to process is passed as num.
 ///
 /// Sample usage:
-/// var a = try Range(u32, 1).init(0, 2);
-/// var b = try Range(u32, 2).init(4, 8);
+/// var a = try Range(u32).init(0, 2, 1);
+/// var b = try Range(u32).init(4, 8, 2);
 /// var iterators = [_]*Iterator(u32){ &a.iterator, &b.iterator };
 /// var product = Product(u32, 2).init(iterators);
 /// var iter = &product.iterator;

          
@@ 203,11 328,14 @@ pub fn Product(comptime T: type, comptim
         // all return the same type.
         const Result = [num]T;
 
+        /// Take the address of this to get a usable Iterator.
         iterator: Iterator(Result),
         children: [num]*Iterator(T),
         next: [num]T,
         done: bool = false,
 
+        /// Return the next array of combinations, or null if the last
+        /// one has been returned.
         pub fn next(iterator: *Iterator(Result)) ?Result {
             const self = @fieldParentPtr(Self, "iterator", iterator);
             if (self.done)

          
@@ 228,6 356,7 @@ pub fn Product(comptime T: type, comptim
             return prev;
         }
 
+        /// Start over from the beginning.
         pub fn reset(iterator: *Iterator(Result)) void {
             const self = @fieldParentPtr(Self, "iterator", iterator);
             for (self.children) |child| {

          
@@ 235,8 364,9 @@ pub fn Product(comptime T: type, comptim
             }
         }
 
-        /// Invokes safety-checked illegal behavior if any iterators
-        /// are empty.
+        /// Initialize and return structure. args is an array of
+        /// iterators to combine. Invokes safety-checked illegal
+        /// behavior if any iterators in args are empty.
         pub fn init(args: [num]*Iterator(T)) Self {
             var rv = Self{
                 .iterator = Iterator(Result){

          
@@ 255,9 385,42 @@ pub fn Product(comptime T: type, comptim
     };
 }
 
+/// Apply fun to successive elements in iter. Initial value of acc is
+/// init.
+pub fn fold(
+    comptime T: type,
+    fun: fn (acc: T, val: T) T,
+    iter: *Iterator(T),
+    init: T,
+) T {
+    var accumulator: T = init;
+    while (iter.next()) |val| {
+        accumulator = fun(accumulator, val);
+    }
+    return accumulator;
+}
+
+/// Don't use this. It's broken.
+pub fn ffold(
+    comptime T: type,
+    fun: fn (acc: T, val: T) T,
+    iter: *Iterator(T),
+    init: T,
+) T {
+    if (iter.next()) |val| {
+        return @call(
+            .{ .modifier = .always_tail },
+            ffold,
+            .{ T, fun, iter, val },
+        );
+    } else {
+        return init;
+    }
+}
+
 const testing = std.testing;
 test "range ascend" {
-    var range = try Range(u32, 1).init(0, 10);
+    var range = try Range(u32).init(0, 10, 1);
     var iter = &range.iterator;
     var correct: u32 = 0;
     while (iter.next()) |n| {

          
@@ 269,7 432,7 @@ test "range ascend" {
 }
 
 test "range descend" {
-    var range = try Range(i32, -1).init(10, 0);
+    var range = try Range(i32).init(10, 0, -1);
     var iter = &range.iterator;
     var correct: i32 = 10;
     while (iter.next()) |n| {

          
@@ 281,7 444,7 @@ test "range descend" {
 }
 
 test "range skip" {
-    var range = try Range(u32, 2).init(0, 10);
+    var range = try Range(u32).init(0, 10, 2);
     var iter = &range.iterator;
     var correct: u32 = 0;
     while (iter.next()) |n| {

          
@@ 295,11 458,12 @@ test "range skip" {
 test "range runtime" {
     var start: u32 = 0;
     while (start < 10) : (start += 1) {
-        var range = try Range(u32, 1).init(start, 10);
+        var range = try Range(u32).init(start, 10, 1);
         var iter = &range.iterator;
         var correct: u32 = start;
         while (iter.next()) |n| {
             testing.expectEqual(correct, n);
+
             correct += 1;
         }
         testing.expectEqual(correct, 10);

          
@@ 319,9 483,9 @@ test "reverse" {
 }
 
 test "product" {
-    const R = Range(u32, 1);
-    var a = try R.init(0, 10);
-    var b = try R.init(10, 25);
+    const R = Range(u32);
+    var a = try R.init(0, 10, 1);
+    var b = try R.init(10, 25, 1);
     var iterators = [_]*Iterator(u32){ &a.iterator, &b.iterator };
     var product = Product(u32, 2).init(iterators);
 

          
@@ 341,3 505,59 @@ test "product" {
     testing.expectEqual(correct_b, 25);
     testing.expectEqual(pi.next(), null);
 }
+
+test "count" {
+    var count = Count(u64).init(0, 3);
+    var iter = &count.iterator;
+    var correct: u64 = 0;
+    while (iter.next()) |c| {
+        testing.expectEqual(correct, c);
+        correct += 3;
+        if (correct > 150) break;
+    }
+    testing.expectEqual(correct, 153);
+}
+
+test "repeat" {
+    var repeat = Repeat(u64).init(8, 100);
+    var iter = &repeat.iterator;
+    var count: u64 = 0;
+    while (iter.next()) |eight| {
+        testing.expectEqual(eight, 8);
+        count += 1;
+    }
+    testing.expectEqual(count, 100);
+
+    repeat = Repeat(u64).init(7, null);
+    iter = &repeat.iterator;
+    count = 0;
+    while (iter.next()) |seven| {
+        testing.expectEqual(seven, 7);
+        count += 1;
+        if (count == 1000) break;
+    }
+    testing.expectEqual(count, 1000);
+}
+
+fn add(a: u32, b: u32) u32 {
+    return a + b;
+}
+
+test "fold over range" {
+    var range = try Range(u32).init(1, 10, 1);
+    var iter = &range.iterator;
+    const total = fold(u32, add, iter, 12);
+    testing.expectEqual(total, 12 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9);
+}
+
+test "SliceIter" {
+    const slice: []const u8 = "Text";
+    var slice_iter = SliceIter(u8).init(slice);
+    var iter = &slice_iter.iterator;
+    var idx: usize = 0;
+    while (iter.next()) |c| {
+        testing.expectEqual(c, slice[idx]);
+        idx += 1;
+    }
+    testing.expectEqual(idx, slice.len);
+}

          
M src/rangespeed.zig +6 -6
@@ 16,7 16,7 @@ fn standardLoop() void {
 }
 
 fn iterLoop() !void {
-    var range = try iterators.Range(u64, 1).init(0, test_loops);
+    var range = try iterators.Range(u64).init(0, test_loops, 1);
     var iter = &range.iterator;
     while (iter.next()) |_| {
         asm volatile (""

          
@@ 28,8 28,8 @@ fn iterLoop() !void {
 }
 
 fn hackyFastLoop() !void {
-    const Range = iterators.Range(u64, 1);
-    var range = try Range.init(0, test_loops);
+    const Range = iterators.Range(u64);
+    var range = try Range.init(0, test_loops, 1);
     var iter = &range.iterator;
     while (Range.next(iter)) |_| {
         asm volatile (""

          
@@ 53,7 53,7 @@ pub fn main() anyerror!void {
 
     n = 0;
     const iterstart = std.time.nanoTimestamp();
-    var range = try iterators.Range(u64, 1).init(0, test_loops);
+    var range = try iterators.Range(u64).init(0, test_loops, 1);
     var iter = &range.iterator;
     while (iter.next()) |_| {
         try iterLoop();

          
@@ 63,8 63,8 @@ pub fn main() anyerror!void {
 
     n = 0;
     const hackystart = std.time.nanoTimestamp();
-    const Range = iterators.Range(u64, 1);
-    range = try Range.init(0, test_loops);
+    const Range = iterators.Range(u64);
+    range = try Range.init(0, test_loops, 1);
     iter = &range.iterator;
     while (Range.next(iter)) |_| {
         try hackyFastLoop();