Hello.
Let’s say you want a loop counting from .. say .. 10 to 0. Type is usize. What is your favorite way to iterate through that?
How about:
var i: usize = 10;
while (i <= 10) : (i -%= 1) {
std.debug.print("> {d}\n", .{i});
}
Hello.
Let’s say you want a loop counting from .. say .. 10 to 0. Type is usize. What is your favorite way to iterate through that?
How about:
var i: usize = 10;
while (i <= 10) : (i -%= 1) {
std.debug.print("> {d}\n", .{i});
}
var index: usize = 11;
while (index > 0) {
index -= 1;
std.debug.print("> {d}\n", .{i});
}
The 11 is ugly in this example, but in the real code the bound is usually a count anyway:
I would hate to have to code-review such code. Even if it is 100% defined / guaranteed that your i -%= 1 will wrap around when i is 0, and therefore will fail the check. I am sure every single time I looked at this line of code, I would have to re-parse and re-convince myself it does what I believe it does.
I would rather write this:
for (0..11) |n| {
const r = 10 - n;
// do stuff with r
}
I wish zig had support for writing reverse for loops, and for loops with variable types other than usize, and maybe other things (support for negative values, for example). But (so far) it doesn’t, so I make do with what I think is the most legible way of writing this sort of thing.
Cheers!
For doing anything complicated with array indices, I’m a big fan of defining methods on non-exhaustive enums. They’re already useful as safer index types, may as well use that.
pub fn main() void {
const string: []const u8 = "Hello World!";
const Index = TypedIndex(usize, @TypeOf(string));
for (0..string.len) |n| {
const index = Index.at(n).fromEndOf(string);
log.debug("{d}: {c}", .{ n, index.in(string).getElement() });
}
}
pub fn TypedIndex(
I: type, // index backing type
C: type, // type that is indexed
) type {
return enum(I) {
_,
pub fn at(raw: I) TypedIndex(I, C) {
return @enumFromInt(raw);
}
pub fn asInt(index: TypedIndex(I, C)) I {
return @intFromEnum(index);
}
pub fn fromEndOf(index: TypedIndex(I, C), array: C) TypedIndex(I, C) {
return @enumFromInt(array.len - index.asInt() - 1);
}
pub fn in(index: TypedIndex(I, C), array: C) Indexed {
return .create(index, array);
}
pub const Indexed = struct {
base: C,
index: TypedIndex(I, C),
pub const E = switch (@typeInfo(C)) {
inline else => |case| case.child,
};
pub fn create(index: TypedIndex(I, C), array: C) Indexed {
return .{
.base = array,
.index = index,
};
}
pub fn getElement(array: Indexed) E {
return array.base[array.index.asInt()];
}
};
};
}
~/zigtmp: zig run demo.zig ->
debug(demo): 0: !
debug(demo): 1: d
debug(demo): 2: l
debug(demo): 3: r
debug(demo): 4: o
debug(demo): 5: W
debug(demo): 6:
debug(demo): 7: o
debug(demo): 8: l
debug(demo): 9: l
debug(demo): 10: e
debug(demo): 11: H
I know this isn’t strictly speaking counting, but it feels in the spirit of the question. ![]()
Another idiomatic and flexible approach is to add a numeric iterator to your project, allowing you to pick underlying type, ranges, direction, step amount, if ranges are half-open, etc. It’s just a handful of lines. Example iterator from my own collection:
var it_down = numericIterator(i8, 10, -10);
while (it_down.next()) |i| {
std.debug.print("val: {}\n", .{ i });
}
var it_up = numericIterator(usize, 0, 11);
while (it_up.next()) |i| {
std.debug.print("val: {}\n", .{ i });
}
The usage looks ergonomic. I think many would like to see the implementation of numericIterator.
yeah, dunno - I honestly usually end up doing raw loops, but figured I’d mention it since iterators allow some nice extensions like step size/direction/etc with little code. Here’s one basic way, added some comments: https://zigbin.io/d7606d
I use that also. I think it’s very easy to read. I see the 11 and the 0 up front, which are the normal bounds in a range. So I just translate that mentally to a reversed 0..11.
For slices this is the best way I found:
var i = slice.len;
while (i > 0) : (i -= 1) {
const item = slice[i-1];
}
I wish we would get some nice syntax for it at some point because I don’t like polluting the scope with i.
Agreed. I would wrap the “10” and “10+1” (aka “11”) into a const variable for “10” (something like downcount_upper_limit to flag that this is a touch unusual) so I don’t have to change it in two places, but otherwise I concur. On modern machines, that subtraction probably wouldn’t matter and may even completely unroll at compile time.
Another advantage is that compilers are generally better at unrolling upcounts, especially when they have constant bounds.
If the bounds were runtime values, I might think about something else as that subtraction might actually matter, but probably not without solid profiling information.
As someone who does a lot of embedded, I very, very, very rarely downcount in a loop nowadays. In my experience, downcounts produce an excessive amount of off-by-one errors.
I feel the pain of not having a scoped do { } while() construct FAR more than anything else because of Zig not allowing shadowing of variable names. In order to contain the variable scope, I have a lot of dual nested code like this:
{
const limit_count = 8;
var ui: usize = 0;
while (true) {
// do something
if (ui >= limit_count) break; // exit condition
ui += 1;
}
}
That almost certainly blows a bunch of optimization opportunities (especially unrolling) because the iteration now looks variable when it really isn’t.
For slices there is actually a backwardsIteratir (or something, I’m on th phone) in std.mem. Its ranges that are annoying
edit : reverseIterator
This is how I like to do it as well, but I wish there was an easy way to disallow the use of n within the loop after defining r, to avoid mistakes. Perhaps by changing the discard syntax _ = n to mean “n cannot be used after this point.”
I’m guessing that’s very unlikely. But you could name n something that makes it difficult to use accidentally, such as forward_n. And then use n (instead of r) as the value you use in the body. (I would use forward_i and i.)
contender:
var i: usize = 10;
while (true) : (i -= 1) {
std.debug.print("> {d}\n", .{ i });
if (i == 0) break;
}
benefits mostly that you don’t have to write the 10 twice. This is actually much clearer to methan the 10 - n version.
Edit: do while strikes again
The code I wrote above was written to strictly fit your description of the problem: counting from 10 to 0 (both inclusive). The way I would think of that requirement is count from 0 to 11 in reverse order, where the 11 is implicitly exclusive – by now I am used to that. And then I would write that as:
const top = 11;
// much later
for (0..top) |n| {
const r = top - n - 1;
// use r
}
I swear I’m not making this up just to make it look nicer. It is really the way I think about this.
Finally, I prefer to use for in this case, because it is an explicit signifier that the loop is over a fixed range.