I wrote this many moons ago, when I needed to transverse arrays both directions, in a single loop, and sometimes needed to cycle (go to the first after reaching the last and vice-versa).
I know, too verbose …
// counts from start to end, ascending or descending, inclusive or exclusive
// values of start and end determines if Counter is ascending or descending
// the increment/decrement of each iteration is step, which must be a positive number
// next() function starts the Counter and also freezes (if not cyclical) it when it gets to the last possible number (<= end)
// when value receives the last possible number, the next call to next() returns false and ended becomes true and value remains frozen
// cycling means that if is at the last possible value, a call to next() gets to first possible number and ended never gets true
// reset starts over the Counter, with value receiving the first possible number and ended becomes false
// T is the type for: start, end, step and value. should be any int or float
// P is the type for: i and cycles. should be an unsigned integer
pub fn Counter(comptime T: type, comptime P: type) type {
// TODO: check for types of T (int or float) and P (unsigned int)
return struct {
// start indicates the reference for the initial value (will also depend if it is inclusive or not)
start: T,
// end indicates the reference for the last velue (will also depend if it is inclusive or not)
end: T,
// step (always positive) indicates the increment/decrement (infered from start and end) for each iteration
step: T,
// value is the current iteration value, according to start/end/step set
value: T,
// i counts (starts at 0) the iterations. is always ascending, not affected by value or cycling
i: P,
// ended indicates with true if Counter got to the last possible value. it is always false when cycling
ended: bool,
// cycling indicates if Counter should cycle, i.e., calling next() after last possible value, it starts over
cycling: bool,
// cycles is the number of COMPLETED cycles
cycles: P,
// first call to next() starts the Counter and makes on == true
on: bool,
// constructs a default Counter: from start (inclusive) to end (exclusive), step == 1, cycling == false
pub fn init(start: T, end: T) !Counter(T, P) {
return initFull(start, true, end, false, 1, false);
}
// constructs a Counter, according to the passed parameters
// if starts_inclusive is true, Counter starts from start, else starts from next possible number towards end
// end, if reachable, depending on step, is where Counter stops counting
// if end_inclusive is true it means that the end is a possible number (will be reacheable, depending on step)
// step is the positive increment/decrement per iteration
// cycling means that after Counter reachs the last value (<= end), it will start over
// on is initialized to false, being true only after the first call to next()
pub fn initFull(start: T, start_inclusive: bool, end: T, end_inclusive: bool, step: T, cycling: bool) !Counter(T, P) {
if (step < 0) return error.StepMustBePositive;
if ((start == end) and (!start_inclusive or !end_inclusive)) return error.InconsistentArgs;
const start_aux = if (start_inclusive) start else math.nextAfter(T, start, end);
const end_aux = if (end_inclusive) end else math.nextAfter(T, end, start);
if (((step == 0) and (start_aux != end_aux))) return error.InconsistentArgs;
return .{
.start = start_aux,
.end = end_aux,
.step = step,
.value = start_aux,
.i = 0,
.ended = false,
.cycling = cycling,
.cycles = 0,
.on = false,
};
}
// next() is the main function of Counter, is what makes it start, run and stop
// first call to next() makes value = first possible numnber, i = 0, cycles = 0 and on = true
// returns true if Counter got a next value and false otherwise (always true if cycling)
// for a cycling Counter, after reaching the last possible number, it starts over
pub fn next(self: *Counter(T, P)) bool {
if (self.ended) return false;
if (!(self.on)) {
self.on = true;
if (self.step == 0) self.ended = true;
return true;
}
if (self.start < self.end) {
if ((self.value + self.step) <= self.end) {
self.value += self.step;
self.i += 1;
return true;
}
} else {
if (self.value >= (self.end + self.step)) {
self.value -= self.step;
self.i += 1;
return true;
}
}
if (self.cycling) {
self.value = self.start;
self.i += 1;
self.cycles += 1;
return true;
}
self.ended = true;
self.cycles = 1;
return false;
}
// resets the Counter to its initial state
pub fn reset(self: *Counter(T, P)) void {
self.value = self.start;
self.i = 0;
self.ended = false;
self.cycles = 0;
self.on = false;
}
};
}
pub fn main() !void {
// 1 to 11 (ascending), end exclusive, step 1, not cycling
var counter1 = try Counter(i64, u64).init(1, 11);
try expect(!counter1.on);
while (counter1.next()) {}
try expect(counter1.i == 9);
try expect(counter1.value == 10);
// 1 to 70 (ascending), end inclusive, step 3, not cycling
var counter2 = try Counter(u128, u64).initFull(0, true, 69, false, 3, true);
for (0..1000) |i| { // just to test it is counting OK
_ = counter2.next();
try expect(counter2.i == i);
}
try expect(counter2.i == 999);
try expect(counter2.cycles == 43); // 999/69
// 37 to -10 (descending), start inclusive, step 0.25, cycling
var counter3 = try Counter(f128, u32).initFull(37.0, true, -10, true, 0.25, true);
while (counter3.next()) {
if (counter3.i == 350) break;
}
try expect(counter3.i == 350);
try expect(counter3.cycles == 1);
counter3.reset();
try expect(!counter3.on);
// 100 to -37 (descending), start inclusive, step 0.5, not cycling
var counter4 = try Counter(f64, u64).initFull(100.0, true, -37, true, 0.5, false);
while (counter4.next()) {}
try expect(counter4.i == 274); // 137 * 2
try expect(counter4.cycles == 1);
try expect(counter4.value == -37);
}