├── .gitignore ├── src ├── empty_iter.zig ├── slice_iter.zig ├── once_iter.zig ├── repeat_iter.zig ├── successors_iter.zig ├── skip_iter.zig ├── cycle_iter.zig ├── range_iter.zig ├── take_while_iter.zig ├── take_iter.zig ├── enumerate_iter.zig ├── skip_while_iter.zig ├── peekable_iter.zig ├── zip_iter.zig ├── flatten_iter.zig ├── filter_iter.zig ├── to_slice.zig ├── reduce.zig ├── map_iter.zig ├── chain_iter.zig ├── filter_map_iter.zig └── main.zig └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*/ 3 | !.gitignore 4 | 5 | !build.zig 6 | !src/**/*.zig 7 | 8 | !README.md 9 | -------------------------------------------------------------------------------- /src/empty_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const Item = itertools.Item; 6 | 7 | /// An iterator that yields nothing. 8 | /// 9 | /// See `empty` for more info. 10 | pub fn EmptyIter(comptime T: type) type { 11 | return struct { 12 | const Self = @This(); 13 | 14 | pub fn next(self: Self) ?T { 15 | _ = self; 16 | return null; 17 | } 18 | }; 19 | } 20 | 21 | /// Creates an iterator that yields nothing. 22 | pub fn empty(comptime T: type) EmptyIter(T) { 23 | return .{}; 24 | } 25 | 26 | test "Empty" { 27 | var iter = empty(u32); 28 | try std.testing.expectEqual(u32, Item(@TypeOf(iter))); 29 | try testing.expectEqual(@as(?u32, null), iter.next()); 30 | try testing.expectEqual(@as(?u32, null), iter.next()); 31 | } 32 | -------------------------------------------------------------------------------- /src/slice_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const Item = itertools.Item; 6 | 7 | /// Iter type for iterating over slice values 8 | pub fn SliceIter(comptime T: type) type { 9 | return struct { 10 | const Self = @This(); 11 | 12 | slice: []const T, 13 | index: usize, 14 | 15 | pub fn next(self: *Self) ?T { 16 | if (self.index >= self.slice.len) 17 | return null; 18 | self.index += 1; 19 | return self.slice[self.index - 1]; 20 | } 21 | }; 22 | } 23 | 24 | /// Returns an iterator iterating over the values in the slice. 25 | pub fn sliceIter(comptime T: anytype, slice: []const T) SliceIter(T) { 26 | return .{ .slice = slice, .index = 0 }; 27 | } 28 | 29 | test "sliceIter" { 30 | const slice: []const u32 = &.{ 1, 2, 3, 4 }; 31 | var iter = sliceIter(u32, slice); 32 | 33 | try testing.expectEqual(u32, Item(@TypeOf(iter))); 34 | try testing.expectEqual(@as(?u32, 1), iter.next()); 35 | try testing.expectEqual(@as(?u32, 2), iter.next()); 36 | try testing.expectEqual(@as(?u32, 3), iter.next()); 37 | try testing.expectEqual(@as(?u32, 4), iter.next()); 38 | try testing.expectEqual(@as(?u32, null), iter.next()); 39 | try testing.expectEqual(@as(?u32, null), iter.next()); 40 | } 41 | -------------------------------------------------------------------------------- /src/once_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const Item = itertools.Item; 6 | 7 | /// An iterator that yields an element exactly once. 8 | /// 9 | /// See `empty` for more info. 10 | pub fn OnceIter(comptime T: type) type { 11 | return struct { 12 | const Self = @This(); 13 | 14 | value: ?T, 15 | 16 | pub fn next(self: *Self) ?T { 17 | const value = self.value orelse return null; 18 | self.value = null; 19 | return value; 20 | } 21 | }; 22 | } 23 | 24 | /// Creates an iterator that yields an element exactly once. 25 | /// 26 | /// This is commonly used to adapt a single value into a `chain()` of other 27 | /// kinds of iteration. Maybe you have an iterator that covers almost 28 | /// everything, but you need an extra special case. Maybe you have a function 29 | /// which works on iterators, but you only need to process one value. 30 | pub fn once(value: anytype) OnceIter(@TypeOf(value)) { 31 | return .{ .value = value }; 32 | } 33 | 34 | test "Once" { 35 | var iter = once(@as(u32, 42)); 36 | try std.testing.expectEqual(u32, Item(@TypeOf(iter))); 37 | try testing.expectEqual(@as(?u32, 42), iter.next()); 38 | try testing.expectEqual(@as(?u32, null), iter.next()); 39 | try testing.expectEqual(@as(?u32, null), iter.next()); 40 | } 41 | -------------------------------------------------------------------------------- /src/repeat_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const IterError = itertools.IterError; 6 | const Item = itertools.Item; 7 | const range = itertools.range; 8 | 9 | /// An iterator that repeats an element endlessly. 10 | /// 11 | /// See `repeat` for more info. 12 | pub fn RepeatIter(comptime T: type) type { 13 | return struct { 14 | const Self = @This(); 15 | 16 | value: T, 17 | 18 | pub fn next(self: *Self) ?T { 19 | return self.value; 20 | } 21 | }; 22 | } 23 | 24 | /// Creates a new iterator that endlessly repeats a single element. 25 | /// 26 | /// The `repeat()` function repeats a single value over and over again. 27 | /// 28 | /// Infinite iterators like repeat() are often used with adapters like 29 | /// `take`, in order to make them finite. 30 | pub fn repeat(value: anytype) RepeatIter(@TypeOf(value)) { 31 | return .{ .value = value }; 32 | } 33 | 34 | test "Cycle" { 35 | var iter = repeat(@as(u32, 42)); 36 | try std.testing.expectEqual(u32, Item(@TypeOf(iter))); 37 | try testing.expectEqual(@as(?u32, 42), iter.next()); 38 | try testing.expectEqual(@as(?u32, 42), iter.next()); 39 | try testing.expectEqual(@as(?u32, 42), iter.next()); 40 | try testing.expectEqual(@as(?u32, 42), iter.next()); 41 | try testing.expectEqual(@as(?u32, 42), iter.next()); 42 | try testing.expectEqual(@as(?u32, 42), iter.next()); 43 | try testing.expectEqual(@as(?u32, 42), iter.next()); 44 | try testing.expectEqual(@as(?u32, 42), iter.next()); 45 | } 46 | -------------------------------------------------------------------------------- /src/successors_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const IterError = itertools.IterError; 6 | const Item = itertools.Item; 7 | const range = itertools.range; 8 | 9 | /// An new iterator where each successive item is computed based on the preceding one. 10 | /// 11 | /// See `successors` for more info. 12 | pub fn SuccessorsIter(comptime T: type) type { 13 | return struct { 14 | const Self = @This(); 15 | 16 | current: ?T, 17 | func: *const fn (T) ?T, 18 | 19 | pub fn next(self: *Self) ?T { 20 | const current = self.current orelse return null; 21 | self.current = self.func(current); 22 | return current; 23 | } 24 | }; 25 | } 26 | 27 | /// Creates a new iterator where each successive item is computed based on the 28 | /// preceding one. 29 | /// 30 | /// The iterator starts with the given first item (if any) and calls the given 31 | /// function to compute each item’s successor. 32 | pub fn successors( 33 | init: anytype, 34 | func: *const fn (@TypeOf(init)) ?@TypeOf(init), 35 | ) SuccessorsIter(@TypeOf(init)) { 36 | return .{ .current = init, .func = func }; 37 | } 38 | 39 | test "successors" { 40 | const func = struct { 41 | fn func(x: u32) ?u32 { 42 | if (x >= 5) return null; 43 | return x + 1; 44 | } 45 | }.func; 46 | var iter = successors(@as(u32, 0), func); 47 | try testing.expectEqual(u32, Item(@TypeOf(iter))); 48 | try testing.expectEqual(@as(?u32, 0), iter.next()); 49 | try testing.expectEqual(@as(?u32, 1), iter.next()); 50 | try testing.expectEqual(@as(?u32, 2), iter.next()); 51 | try testing.expectEqual(@as(?u32, 3), iter.next()); 52 | try testing.expectEqual(@as(?u32, 4), iter.next()); 53 | try testing.expectEqual(@as(?u32, 5), iter.next()); 54 | try testing.expectEqual(@as(?u32, null), iter.next()); 55 | try testing.expectEqual(@as(?u32, null), iter.next()); 56 | try testing.expectEqual(@as(?u32, null), iter.next()); 57 | } 58 | -------------------------------------------------------------------------------- /src/skip_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const IterError = itertools.IterError; 6 | const Item = itertools.Item; 7 | const range = itertools.range; 8 | 9 | /// An iterator that skips over n elements of iter. 10 | /// 11 | /// See `skip` for more info. 12 | pub fn SkipIter(comptime BaseIter: type) type { 13 | return struct { 14 | const Self = @This(); 15 | 16 | base_iter: BaseIter, 17 | to_skip: usize, 18 | 19 | pub const Next = if (IterError(BaseIter)) |ES| ES!?Item(BaseIter) else ?Item(BaseIter); 20 | 21 | pub fn next(self: *Self) Next { 22 | const has_error = IterError(BaseIter) != null; 23 | if (self.to_skip > 0) { 24 | for (0..self.to_skip) |_| { 25 | if (has_error) { 26 | _ = try self.base_iter.next(); 27 | } else { 28 | _ = self.base_iter.next(); 29 | } 30 | } 31 | self.to_skip = 0; 32 | } 33 | return if (has_error) 34 | try self.base_iter.next() 35 | else 36 | self.base_iter.next(); 37 | } 38 | }; 39 | } 40 | 41 | /// Creates an iterator that skips the first n elements. 42 | /// 43 | /// skip(iter, n) skips elements until n elements are skipped or the end of the 44 | /// iterator is reached (whichever happens first). After that, all the 45 | /// remaining elements are yielded. In particular, if the original iterator is 46 | /// too short, then the returned iterator is empty. 47 | pub fn skip(iter: anytype, to_skip: usize) SkipIter(@TypeOf(iter)) { 48 | return .{ .base_iter = iter, .to_skip = to_skip }; 49 | } 50 | 51 | test "skip" { 52 | var base_iter = range(u32, 5, 10); 53 | var iter = skip(base_iter, 2); 54 | try testing.expectEqual(Item(@TypeOf(base_iter)), Item(@TypeOf(iter))); 55 | try testing.expectEqual(@as(?u32, 7), iter.next()); 56 | try testing.expectEqual(@as(?u32, 8), iter.next()); 57 | try testing.expectEqual(@as(?u32, 9), iter.next()); 58 | try testing.expectEqual(@as(?u32, null), iter.next()); 59 | } 60 | -------------------------------------------------------------------------------- /src/cycle_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const IterError = itertools.IterError; 6 | const Item = itertools.Item; 7 | const range = itertools.range; 8 | 9 | /// An iterator that repeats endlessly. 10 | /// 11 | /// See `cycle` for more info. 12 | pub fn CycleIter(comptime BaseIter: type) type { 13 | return struct { 14 | const Self = @This(); 15 | 16 | orig_iter: BaseIter, 17 | base_iter: BaseIter, 18 | 19 | pub const Next = if (IterError(BaseIter)) |ES| ES!?Item(BaseIter) else ?Item(BaseIter); 20 | 21 | pub fn next(self: *Self) Next { 22 | const has_error = comptime IterError(BaseIter) != null; 23 | 24 | const maybe_item = if (has_error) 25 | try self.base_iter.next() 26 | else 27 | self.base_iter.next(); 28 | 29 | if (maybe_item) |item| { 30 | return item; 31 | } else { 32 | self.base_iter = self.orig_iter; 33 | return if (has_error) 34 | try self.base_iter.next() 35 | else 36 | self.base_iter.next(); 37 | } 38 | } 39 | }; 40 | } 41 | 42 | /// Repeats an iterator endlessly. 43 | /// 44 | /// Instead of stopping at None, the iterator will instead start again, from the 45 | /// beginning. After iterating again, it will start at the beginning again. 46 | /// And again. And again. Forever. Note that in case the original iterator is 47 | /// empty, the resulting iterator will also be empty. 48 | pub fn cycle(iter: anytype) CycleIter(@TypeOf(iter)) { 49 | return .{ .orig_iter = iter, .base_iter = iter }; 50 | } 51 | 52 | test "Cycle" { 53 | var base_iter = range(u32, 0, 3); 54 | var iter = cycle(base_iter); 55 | try std.testing.expectEqual(u32, Item(@TypeOf(iter))); 56 | try testing.expectEqual(@as(?u32, 0), iter.next()); 57 | try testing.expectEqual(@as(?u32, 1), iter.next()); 58 | try testing.expectEqual(@as(?u32, 2), iter.next()); 59 | try testing.expectEqual(@as(?u32, 0), iter.next()); 60 | try testing.expectEqual(@as(?u32, 1), iter.next()); 61 | try testing.expectEqual(@as(?u32, 2), iter.next()); 62 | try testing.expectEqual(@as(?u32, 0), iter.next()); 63 | try testing.expectEqual(@as(?u32, 1), iter.next()); 64 | } 65 | -------------------------------------------------------------------------------- /src/range_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const Item = itertools.Item; 6 | 7 | /// A (half-open) range iterator bounded inclusively below and exclusively above [start, end). 8 | pub fn RangeIter(comptime T: type) type { 9 | if (!std.meta.trait.isNumber(T)) 10 | @compileError("RangeIter Item type must be a number"); 11 | 12 | return struct { 13 | const Self = @This(); 14 | 15 | current: T, 16 | end: T, 17 | step: T, 18 | 19 | pub fn stepBy(self: Self, step: T) Self { 20 | return .{ 21 | .current = self.current, 22 | .end = self.end, 23 | .step = step, 24 | }; 25 | } 26 | 27 | pub fn next(self: *Self) ?T { 28 | if (self.step > 0 and self.current >= self.end or 29 | self.step < 0 and self.current <= self.end) 30 | return null; 31 | const result = self.current; 32 | self.current += self.step; 33 | return result; 34 | } 35 | }; 36 | } 37 | 38 | /// Creates a `RangeIter`. See its documentation for more info. 39 | pub fn range(comptime T: type, start: T, end: T) RangeIter(T) { 40 | return .{ .current = start, .end = end, .step = 1 }; 41 | } 42 | 43 | test "RangeIter" { 44 | var iter = range(u32, 0, 5); 45 | try testing.expectEqual(u32, Item(@TypeOf(iter))); 46 | try testing.expectEqual(@as(?u32, 0), iter.next()); 47 | try testing.expectEqual(@as(?u32, 1), iter.next()); 48 | try testing.expectEqual(@as(?u32, 2), iter.next()); 49 | try testing.expectEqual(@as(?u32, 3), iter.next()); 50 | try testing.expectEqual(@as(?u32, 4), iter.next()); 51 | try testing.expectEqual(@as(?u32, null), iter.next()); 52 | try testing.expectEqual(@as(?u32, null), iter.next()); 53 | } 54 | 55 | test "RangeIter reverse" { 56 | var iter = range(i32, 5, 0).stepBy(-1); 57 | try testing.expectEqual(i32, Item(@TypeOf(iter))); 58 | try testing.expectEqual(@as(?i32, 5), iter.next()); 59 | try testing.expectEqual(@as(?i32, 4), iter.next()); 60 | try testing.expectEqual(@as(?i32, 3), iter.next()); 61 | try testing.expectEqual(@as(?i32, 2), iter.next()); 62 | try testing.expectEqual(@as(?i32, 1), iter.next()); 63 | try testing.expectEqual(@as(?i32, null), iter.next()); 64 | try testing.expectEqual(@as(?i32, null), iter.next()); 65 | } 66 | -------------------------------------------------------------------------------- /src/take_while_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const IterError = itertools.IterError; 6 | const Item = itertools.Item; 7 | const range = itertools.range; 8 | 9 | /// An iterator that only accepts elements while predicate returns true. 10 | /// 11 | /// See `takeWhile` for more info. 12 | pub fn TakeWhileIter(comptime BaseIter: type) type { 13 | return struct { 14 | const Self = @This(); 15 | 16 | base_iter: BaseIter, 17 | predicate: ?*const fn (Item(BaseIter)) bool, 18 | 19 | pub const Next = if (IterError(BaseIter)) |ES| ES!?Item(BaseIter) else ?Item(BaseIter); 20 | 21 | pub fn next(self: *Self) Next { 22 | const predicate = self.predicate orelse return null; 23 | 24 | const has_error = IterError(BaseIter) != null; 25 | const maybe_item = if (has_error) 26 | try self.base_iter.next() 27 | else 28 | self.base_iter.next(); 29 | const item = maybe_item orelse return null; 30 | if (predicate(item)) return item; 31 | self.predicate = null; 32 | return null; 33 | } 34 | }; 35 | } 36 | 37 | /// Creates an iterator that yields elements based on a predicate. 38 | /// 39 | /// `take_while()` takes a function as an argument. It will call this function 40 | /// on each element of the iterator, and yield elements while it returns true. 41 | /// 42 | /// After false is returned, `take_while()`’s job is over, and the rest of the 43 | /// elements are ignored. 44 | pub fn takeWhile(iter: anytype, predicate: *const fn (Item(@TypeOf(iter))) bool) TakeWhileIter(@TypeOf(iter)) { 45 | return .{ .base_iter = iter, .predicate = predicate }; 46 | } 47 | 48 | test "takeWhile" { 49 | var base_iter = range(u32, 0, 10); 50 | const predicate = struct { 51 | fn predicate(x: u32) bool { 52 | return x < 5; 53 | } 54 | }.predicate; 55 | var iter = takeWhile(base_iter, predicate); 56 | try testing.expectEqual(Item(@TypeOf(base_iter)), Item(@TypeOf(iter))); 57 | try testing.expectEqual(@as(?u32, 0), iter.next()); 58 | try testing.expectEqual(@as(?u32, 1), iter.next()); 59 | try testing.expectEqual(@as(?u32, 2), iter.next()); 60 | try testing.expectEqual(@as(?u32, 3), iter.next()); 61 | try testing.expectEqual(@as(?u32, 4), iter.next()); 62 | try testing.expectEqual(@as(?u32, null), iter.next()); 63 | } 64 | -------------------------------------------------------------------------------- /src/take_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const IterError = itertools.IterError; 6 | const Item = itertools.Item; 7 | const range = itertools.range; 8 | 9 | /// An iterator that only iterates over the first n iterations of iter. 10 | /// 11 | /// See `take` for more info. 12 | pub fn TakeIter(comptime BaseIter: type) type { 13 | return struct { 14 | const Self = @This(); 15 | 16 | base_iter: BaseIter, 17 | to_take: usize, 18 | 19 | pub const Next = if (IterError(BaseIter)) |ES| ES!?Item(BaseIter) else ?Item(BaseIter); 20 | 21 | pub fn next(self: *Self) Next { 22 | const has_error = IterError(BaseIter) != null; 23 | if (self.to_take == 0) return null; 24 | self.to_take -= 1; 25 | return if (has_error) 26 | try self.base_iter.next() 27 | else 28 | self.base_iter.next(); 29 | } 30 | }; 31 | } 32 | 33 | /// Creates an iterator that yields the first n elements, or fewer if the 34 | /// underlying iterator ends sooner. 35 | /// 36 | /// `take(iter, n)` yields elements until n elements are yielded or the end of 37 | /// the iterator is reached (whichever happens first). The returned iterator 38 | /// is a prefix of length n if the original iterator contains at least n 39 | /// elements, otherwise it contains all of the (fewer than n) elements of the 40 | /// original iterator. 41 | pub fn take(iter: anytype, to_take: usize) TakeIter(@TypeOf(iter)) { 42 | return .{ .base_iter = iter, .to_take = to_take }; 43 | } 44 | 45 | test "take" { 46 | var base_iter = range(u32, 0, 10); 47 | var iter = take(base_iter, 5); 48 | try testing.expectEqual(Item(@TypeOf(base_iter)), Item(@TypeOf(iter))); 49 | try testing.expectEqual(@as(?u32, 0), iter.next()); 50 | try testing.expectEqual(@as(?u32, 1), iter.next()); 51 | try testing.expectEqual(@as(?u32, 2), iter.next()); 52 | try testing.expectEqual(@as(?u32, 3), iter.next()); 53 | try testing.expectEqual(@as(?u32, 4), iter.next()); 54 | try testing.expectEqual(@as(?u32, null), iter.next()); 55 | } 56 | 57 | test "take small iter" { 58 | var base_iter = range(u32, 0, 2); 59 | var iter = take(base_iter, 5); 60 | try testing.expectEqual(Item(@TypeOf(base_iter)), Item(@TypeOf(iter))); 61 | try testing.expectEqual(@as(?u32, 0), iter.next()); 62 | try testing.expectEqual(@as(?u32, 1), iter.next()); 63 | try testing.expectEqual(@as(?u32, null), iter.next()); 64 | } 65 | -------------------------------------------------------------------------------- /src/enumerate_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const IterError = itertools.IterError; 6 | const Item = itertools.Item; 7 | const range = itertools.range; 8 | 9 | /// An iterator that yields the current count and the element during iteration. 10 | /// 11 | /// See `enumerate` for more info. 12 | pub fn EnumerateIter(comptime BaseIter: type) type { 13 | return struct { 14 | const Self = @This(); 15 | 16 | base_iter: BaseIter, 17 | count: usize, 18 | 19 | pub const Next = if (IterError(BaseIter)) |ES| ES!?Self.Item else ?Self.Item; 20 | pub const Item = struct { 21 | item: itertools.Item(BaseIter), 22 | index: usize, 23 | }; 24 | 25 | pub fn next(self: *Self) Next { 26 | const has_error = IterError(BaseIter) != null; 27 | const maybe_item = if (has_error) 28 | try self.base_iter.next() 29 | else 30 | self.base_iter.next(); 31 | const item = maybe_item orelse return null; 32 | self.count += 1; 33 | return .{ 34 | .item = item, 35 | .index = self.count - 1, 36 | }; 37 | } 38 | }; 39 | } 40 | 41 | /// Creates an iterator which gives the current iteration count as well as 42 | /// the next value. 43 | /// 44 | /// The iterator returned yields structs `.{ .index = i, .item = val }`, where `i` is the 45 | /// current index of iteration and `val` is the value returned by the 46 | /// iterator. 47 | /// 48 | /// # Overflow Behavior 49 | /// 50 | /// The method does no guarding against overflows, so enumerating more than 51 | /// `std.math.maxInt(usize)` elements produces safety checked undefined behavior. 52 | pub fn enumerate(iter: anytype) EnumerateIter(@TypeOf(iter)) { 53 | return .{ .base_iter = iter, .count = 0 }; 54 | } 55 | 56 | test "enumerate" { 57 | var base_iter = range(u32, 5, 10).stepBy(2); 58 | var iter = enumerate(base_iter); 59 | try testing.expectEqual(@TypeOf(iter).Item, Item(@TypeOf(iter))); 60 | const v1 = iter.next().?; 61 | try testing.expectEqual(@as(u32, v1.item), 5); 62 | try testing.expectEqual(@as(usize, v1.index), 0); 63 | const v2 = iter.next().?; 64 | try testing.expectEqual(@as(u32, v2.item), 7); 65 | try testing.expectEqual(@as(usize, v2.index), 1); 66 | const v3 = iter.next().?; 67 | try testing.expectEqual(@as(u32, v3.item), 9); 68 | try testing.expectEqual(@as(usize, v3.index), 2); 69 | try testing.expectEqual(@as(?@TypeOf(iter).Item, null), iter.next()); 70 | } 71 | -------------------------------------------------------------------------------- /src/skip_while_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const IterError = itertools.IterError; 6 | const Item = itertools.Item; 7 | const range = itertools.range; 8 | 9 | /// An iterator that rejects elements while predicate returns true. 10 | /// 11 | /// See `skipWhile` for more info. 12 | pub fn SkipWhileIter(comptime BaseIter: type) type { 13 | return struct { 14 | const Self = @This(); 15 | 16 | base_iter: BaseIter, 17 | predicate: ?*const fn (Item(BaseIter)) bool, 18 | 19 | pub const Next = if (IterError(BaseIter)) |ES| ES!?Item(BaseIter) else ?Item(BaseIter); 20 | 21 | pub fn next(self: *Self) Next { 22 | const has_error = IterError(BaseIter) != null; 23 | if (self.predicate) |predicate| { 24 | self.predicate = null; 25 | while (true) { 26 | const maybe_item = if (has_error) 27 | try self.base_iter.next() 28 | else 29 | self.base_iter.next(); 30 | const item = maybe_item orelse return null; 31 | if (!predicate(item)) return item; 32 | } 33 | } 34 | return if (has_error) 35 | try self.base_iter.next() 36 | else 37 | self.base_iter.next(); 38 | } 39 | }; 40 | } 41 | 42 | // Creates an iterator that skips elements based on a predicate. 43 | // 44 | // `skipWhile()` takes a function as an argument. It will call this function 45 | /// on each element of the iterator, and ignore elements until it returns 46 | /// false. 47 | // 48 | // After false is returned, `skipWhile()`’s job is over, and the rest of the 49 | /// elements are yielded. 50 | pub fn skipWhile(iter: anytype, predicate: *const fn (Item(@TypeOf(iter))) bool) SkipWhileIter(@TypeOf(iter)) { 51 | return .{ .base_iter = iter, .predicate = predicate }; 52 | } 53 | 54 | test "skipWhile" { 55 | var base_iter = range(u32, 0, 10); 56 | const predicate = struct { 57 | fn predicate(x: u32) bool { 58 | return x < 5; 59 | } 60 | }.predicate; 61 | var iter = skipWhile(base_iter, predicate); 62 | try testing.expectEqual(Item(@TypeOf(base_iter)), Item(@TypeOf(iter))); 63 | try testing.expectEqual(@as(?u32, 5), iter.next()); 64 | try testing.expectEqual(@as(?u32, 6), iter.next()); 65 | try testing.expectEqual(@as(?u32, 7), iter.next()); 66 | try testing.expectEqual(@as(?u32, 8), iter.next()); 67 | try testing.expectEqual(@as(?u32, 9), iter.next()); 68 | try testing.expectEqual(@as(?u32, null), iter.next()); 69 | } 70 | -------------------------------------------------------------------------------- /src/peekable_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const Item = itertools.Item; 6 | const IterError = itertools.IterError; 7 | 8 | /// An iterator with a peek() that returns an optional to the next element. 9 | /// 10 | /// See `peekable` for more info. 11 | pub fn PeekableIter(comptime BaseIter: type) type { 12 | return struct { 13 | const Self = @This(); 14 | 15 | base_iter: BaseIter, 16 | peeked: ??Item(BaseIter), 17 | 18 | pub const Next = if (IterError(BaseIter)) |ES| ES!?Item(BaseIter) else ?Item(BaseIter); 19 | 20 | pub fn next(self: *Self) Next { 21 | return if (self.peeked) |peeked| blk: { 22 | self.peeked = null; 23 | break :blk peeked; 24 | } else self.base_iter.next(); 25 | } 26 | 27 | /// Returns the `next()` value without advancing the iterator. 28 | /// 29 | /// Like `next`, if there is a value, it is wrapped in an optional `?T`. 30 | /// But if the iteration is over, null is returned. 31 | pub fn peek(self: *Self) Next { 32 | if (self.peeked) |peeked| return peeked; 33 | 34 | const has_error = comptime IterError(BaseIter) != null; 35 | 36 | self.peeked = if (has_error) 37 | try self.base_iter.next() 38 | else 39 | self.base_iter.next(); 40 | 41 | return self.peeked.?; 42 | } 43 | }; 44 | } 45 | 46 | /// Creates an iterator which can use the `peek` method to look at the next 47 | /// element of the iterator without consuming it. See its documentation for more information. 48 | /// 49 | /// Note that the underlying iterator is still advanced when peek is called for 50 | /// the first time: In order to retrieve the next element, next is called on 51 | /// the underlying iterator, hence any side effects (i.e. anything other than 52 | /// fetching the next value) of the next method will occur. 53 | pub fn peekable(iter: anytype) PeekableIter(@TypeOf(iter)) { 54 | return .{ .base_iter = iter, .peeked = null }; 55 | } 56 | 57 | test "Peekable" { 58 | var range = itertools.range(u32, 0, 5); 59 | var iter = peekable(range); 60 | try std.testing.expectEqual(u32, Item(@TypeOf(iter))); 61 | try testing.expectEqual(@as(?u32, 0), iter.next()); 62 | try testing.expectEqual(@as(?u32, 1), iter.next()); 63 | try testing.expectEqual(@as(?u32, 2), iter.next()); 64 | try testing.expectEqual(@as(?u32, 3), iter.peek()); 65 | try testing.expectEqual(@as(?u32, 3), iter.peek()); 66 | try testing.expectEqual(@as(?u32, 3), iter.next()); 67 | try testing.expectEqual(@as(?u32, 4), iter.next()); 68 | try testing.expectEqual(@as(?u32, null), iter.peek()); 69 | try testing.expectEqual(@as(?u32, null), iter.peek()); 70 | try testing.expectEqual(@as(?u32, null), iter.next()); 71 | try testing.expectEqual(@as(?u32, null), iter.next()); 72 | } 73 | -------------------------------------------------------------------------------- /src/zip_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const Item = itertools.Item; 6 | const IterError = itertools.IterError; 7 | const sliceIter = itertools.sliceIter; 8 | const range = itertools.range; 9 | 10 | /// An iterator that iterates two other iterators simultaneously. 11 | /// 12 | /// See `zip` for more info. 13 | pub fn ZipIter(comptime A: type, comptime B: type) type { 14 | return struct { 15 | const Self = @This(); 16 | 17 | a: A, 18 | b: B, 19 | 20 | pub const Item = struct { itertools.Item(A), itertools.Item(B) }; 21 | 22 | pub const Next = if (IterError(A)) |ESA| 23 | if (IterError(B)) |ESB| 24 | (ESA || ESB)!?Self.Item 25 | else 26 | ESA!?Self.Item 27 | else if (IterError(B)) |ESB| 28 | ESB!?Self.Item 29 | else 30 | ?Self.Item; 31 | 32 | pub fn next(self: *Self) Next { 33 | const a_has_error = comptime IterError(A) != null; 34 | const b_has_error = comptime IterError(B) != null; 35 | const maybe_a = if (a_has_error) 36 | try self.a.next() 37 | else 38 | self.a.next(); 39 | const a = maybe_a orelse return null; 40 | const maybe_b = if (b_has_error) 41 | try self.b.next() 42 | else 43 | self.b.next(); 44 | const b = maybe_b orelse return null; 45 | return .{ a, b }; 46 | } 47 | }; 48 | } 49 | 50 | /// ‘Zips up’ two iterators into a single iterator of pairs. 51 | /// 52 | /// `zip()` returns a new iterator that will iterate over two other iterators, 53 | /// returning a tuple where the first element comes from the first iterator, 54 | /// and the second element comes from the second iterator. 55 | /// 56 | /// In other words, it zips two iterators together, into a single one. 57 | /// 58 | /// If either iterator returns null, next from the zipped iterator will return 59 | /// null. If the zipped iterator has no more elements to return then each 60 | /// further attempt to advance it will first try to advance the first iterator 61 | /// at most one time and if it still yielded an item try to advance the second 62 | /// iterator at most one time. 63 | pub fn zip(iter1: anytype, iter2: anytype) ZipIter(@TypeOf(iter1), @TypeOf(iter2)) { 64 | return .{ .a = iter1, .b = iter2 }; 65 | } 66 | 67 | test "zip" { 68 | var iter1 = sliceIter(u32, &.{ 1, 2, 3 }); 69 | var iter2 = range(u64, 5, 8); 70 | var iter = zip(iter1, iter2); 71 | try testing.expectEqual(@TypeOf(iter).Item, Item(@TypeOf(iter))); 72 | const v1 = iter.next().?; 73 | try testing.expectEqual(@as(u32, 1), v1.@"0"); 74 | try testing.expectEqual(@as(u64, 5), v1.@"1"); 75 | const v2 = iter.next().?; 76 | try testing.expectEqual(@as(u32, 2), v2.@"0"); 77 | try testing.expectEqual(@as(u64, 6), v2.@"1"); 78 | const v3 = iter.next().?; 79 | try testing.expectEqual(@as(u32, 3), v3.@"0"); 80 | try testing.expectEqual(@as(u64, 7), v3.@"1"); 81 | try testing.expectEqual(@as(?Item(@TypeOf(iter)), null), iter.next()); 82 | } 83 | -------------------------------------------------------------------------------- /src/flatten_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const Item = itertools.Item; 6 | const IterError = itertools.IterError; 7 | const RangeIter = itertools.RangeIter; 8 | 9 | /// An iterator that flattens one level of nesting in an iterator of things that 10 | /// can be turned into iterators. 11 | /// 12 | /// see `flatten` for more info. 13 | pub fn FlattenIter(comptime BaseIters: type) type { 14 | return struct { 15 | const Self = @This(); 16 | 17 | base_iters: BaseIters, 18 | current_iter: ?Item(BaseIters), 19 | 20 | const Next = if (IterError(BaseIters)) |ESB| 21 | if (IterError(Item(BaseIters))) |ESI| 22 | (ESB || ESI)!?Item(Item(BaseIters)) 23 | else 24 | ESB!?Item(Item(BaseIters)) 25 | else if (IterError(Item(BaseIters))) |ESI| 26 | ESI!?Item(Item(BaseIters)) 27 | else 28 | ?Item(Item(BaseIters)); 29 | 30 | pub fn next(self: *Self) Next { 31 | const base_has_error = comptime IterError(BaseIters) != null; 32 | const item_has_error = comptime IterError(Item(BaseIters)) != null; 33 | 34 | if (self.current_iter) |*iter| { 35 | const maybe_item = if (item_has_error) 36 | try iter.next() 37 | else 38 | iter.next(); 39 | if (maybe_item) |item| 40 | return item; 41 | 42 | self.current_iter = if (base_has_error) 43 | try self.base_iters.next() 44 | else 45 | self.base_iters.next(); 46 | return self.next(); 47 | } else { 48 | return null; 49 | } 50 | } 51 | }; 52 | } 53 | 54 | /// Creates an iterator that flattens nested structure. 55 | /// 56 | /// This is useful when you have an iterator of iterators or an iterator of 57 | /// things that can be turned into iterators and you want to remove one level of 58 | /// indirection. 59 | pub fn flatten(iter: anytype) FlattenIter(@TypeOf(iter)) { 60 | var iters = iter; 61 | const current = if (IterError(@TypeOf(iter)) != null) 62 | try iters.next() 63 | else 64 | iters.next(); 65 | return .{ .base_iters = iters, .current_iter = current }; 66 | } 67 | 68 | test "Flatten" { 69 | const func = struct { 70 | fn func(x: u32) RangeIter(u32) { 71 | return itertools.range(u32, 0, x); 72 | } 73 | }.func; 74 | 75 | var iters = itertools.map(itertools.range(u32, 0, 5), func); 76 | var iter = flatten(iters); 77 | 78 | try testing.expectEqual(u32, Item(@TypeOf(iter))); 79 | try testing.expectEqual(@as(?u32, 0), iter.next()); 80 | try testing.expectEqual(@as(?u32, 0), iter.next()); 81 | try testing.expectEqual(@as(?u32, 1), iter.next()); 82 | try testing.expectEqual(@as(?u32, 0), iter.next()); 83 | try testing.expectEqual(@as(?u32, 1), iter.next()); 84 | try testing.expectEqual(@as(?u32, 2), iter.next()); 85 | try testing.expectEqual(@as(?u32, 0), iter.next()); 86 | try testing.expectEqual(@as(?u32, 1), iter.next()); 87 | try testing.expectEqual(@as(?u32, 2), iter.next()); 88 | try testing.expectEqual(@as(?u32, 3), iter.next()); 89 | try testing.expectEqual(@as(?u32, null), iter.next()); 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zig Itertools 2 | 3 | A toy iterator package for [zig](ziglang.org). Check the tests for examples. 4 | 5 | This package is heavily based on rust's `std::iter` module. 6 | 7 | ## Usage 8 | 9 | To use this package, use the zig package manager. For example in your build.zig.zon file, put: 10 | ```zig 11 | .{ 12 | .name = "app", 13 | .version = "0.1.0", 14 | .dependencies = .{ 15 | .itertools = .{ 16 | .url = "https://github.com/KilianVounckx/zitertools/archive/$COMMIT_YOU_WANT_TO_USE.tar.gz", 17 | }, 18 | }, 19 | } 20 | ``` 21 | 22 | When running zig build now, zig will tell you you need a hash for the dependency and provide one. 23 | Put it in you dependency so it looks like: 24 | ```zig 25 | .{ 26 | .itertools = .{ 27 | .url = "https://github.com/KilianVounckx/zitertools/archive/$COMMIT_YOU_WANT_TO_USE.tar.gz", 28 | .hash = "$HASH_ZIG_GAVE_YOU", 29 | }, 30 | } 31 | ``` 32 | 33 | With the dependency in place, you can now put the following in your build.zig file: 34 | ```zig 35 | // This will create a `std.build.Dependency` which you can use to fetch 36 | // the itertools module. The first argument is the dependency name. It 37 | // should be the same as the one you used in build.zig.zon. 38 | const itertools = b.dependency("itertools", .{}); 39 | // This will create a module which you can use in your zig code. The first 40 | // argument is the name you want your module to have in your zig code. It 41 | // can be anything you want. In your zig code you can use `@import` with 42 | // the same name to use it. The second argument is a module. You can 43 | // fetch it from the dependency with its `module` method. This method 44 | // takes one argument which is the name of the module. This time, the 45 | // name is the one the itertools package uses. It must be exactly the 46 | // same string as below: "itertools". The reason for needing this name is 47 | // that some packages can expose multiple modules. Therefor, you need to 48 | // specify which one you want. This package only exposes one module though, 49 | // so it will always be the same. 50 | exe.addModule("itertools", itertools.module("itertools")); 51 | ``` 52 | 53 | In the above change `exe` to whatever CompileStep you are using. For an executable it will 54 | probably be exe, but `main_tests` or lib are also common. 55 | 56 | With the build file in order, you can now use the module in your zig source. For example: 57 | 58 | ```zig 59 | const std = @import("std"); 60 | // If you named the module something else in `build.zig`, use the other name here 61 | // E.g. if in `build.zig` your did `exe.addModule("foobar", itertools.module("itertools"));` 62 | // Then use `const itertools = @import("foobar");` here. 63 | const itertools = @import("itertools"); 64 | 65 | pub fn main() void { 66 | var iter = itertools.range(u32, 0, 5); 67 | std.debug.print("{s}\n", .{ @typeName(itertools.Item(@TypeOf(iter))) }); // Output: u32 68 | std.debug.print("{?}\n", .{ iter.next()) }; // Output: 0 69 | std.debug.print("{?}\n", .{ iter.next()) }; // Output: 1 70 | std.debug.print("{?}\n", .{ iter.next()) }; // Output: 2 71 | std.debug.print("{?}\n", .{ iter.next()) }; // Output: 3 72 | std.debug.print("{?}\n", .{ iter.next()) }; // Output: 4 73 | std.debug.print("{?}\n", .{ iter.next()) }; // Output: null 74 | } 75 | ``` 76 | 77 | Check the tests for more examples on how to use this package. 78 | -------------------------------------------------------------------------------- /src/filter_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const Item = itertools.Item; 6 | const IterError = itertools.IterError; 7 | const sliceIter = itertools.sliceIter; 8 | 9 | /// Iter type for filtering another iterator with a predicate 10 | /// 11 | /// See `filter` for more info. 12 | pub fn FilterIter(comptime BaseIter: type) type { 13 | return struct { 14 | const Self = @This(); 15 | 16 | base_iter: BaseIter, 17 | predicate: *const fn (Item(BaseIter)) bool, 18 | 19 | pub const Next = if (IterError(BaseIter)) |ES| ES!?Item(BaseIter) else ?Item(BaseIter); 20 | 21 | pub fn next(self: *Self) Next { 22 | const has_error = comptime IterError(BaseIter) != null; 23 | const maybe_item = if (has_error) 24 | try self.base_iter.next() 25 | else 26 | self.base_iter.next(); 27 | const item = maybe_item orelse return null; 28 | if (self.predicate(item)) 29 | return item; 30 | return self.next(); 31 | } 32 | }; 33 | } 34 | 35 | /// Returns a new iterator which filters items in iter with predicate 36 | /// 37 | /// iter must be an iterator, meaning it has to be a type containing a next method which returns 38 | /// an optional. 39 | pub fn filter( 40 | iter: anytype, 41 | predicate: *const fn (Item(@TypeOf(iter))) bool, 42 | ) FilterIter(@TypeOf(iter)) { 43 | return .{ .base_iter = iter, .predicate = predicate }; 44 | } 45 | 46 | test "FilterIter" { 47 | const slice: []const u32 = &.{ 1, 2, 3, 4, 5, 6, 7, 8 }; 48 | var slice_iter = sliceIter(u32, slice); 49 | 50 | const predicates = struct { 51 | pub fn even(x: u32) bool { 52 | return x % 2 == 0; 53 | } 54 | 55 | pub fn big(x: u32) bool { 56 | return x > 4; 57 | } 58 | }; 59 | 60 | var iter = filter(filter(slice_iter, predicates.even), predicates.big); 61 | 62 | try testing.expectEqual(u32, Item(@TypeOf(iter))); 63 | try testing.expectEqual(@as(?u32, 6), iter.next()); 64 | try testing.expectEqual(@as(?u32, 8), iter.next()); 65 | try testing.expectEqual(@as(?u32, null), iter.next()); 66 | try testing.expectEqual(@as(?u32, null), iter.next()); 67 | } 68 | 69 | test "FilterIter error" { 70 | var test_iter = TestErrorIter.init(5); 71 | 72 | const even = struct { 73 | pub fn even(x: usize) bool { 74 | return x % 2 == 0; 75 | } 76 | }.even; 77 | 78 | var iter = filter(test_iter, even); 79 | 80 | try testing.expectEqual(usize, Item(@TypeOf(iter))); 81 | try testing.expectEqual(@as(?usize, 0), try iter.next()); 82 | try testing.expectEqual(@as(?usize, 2), try iter.next()); 83 | try testing.expectEqual(@as(?usize, 4), try iter.next()); 84 | try testing.expectError(error.TestErrorIterError, iter.next()); 85 | } 86 | 87 | const TestErrorIter = struct { 88 | const Self = @This(); 89 | 90 | counter: usize = 0, 91 | until_err: usize, 92 | 93 | pub fn init(until_err: usize) Self { 94 | return .{ .until_err = until_err }; 95 | } 96 | 97 | pub fn next(self: *Self) !?usize { 98 | if (self.counter >= self.until_err) return error.TestErrorIterError; 99 | self.counter += 1; 100 | return self.counter - 1; 101 | } 102 | }; 103 | -------------------------------------------------------------------------------- /src/to_slice.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | const Child = std.meta.Child; 4 | const Allocator = std.mem.Allocator; 5 | const ArrayList = std.ArrayList; 6 | 7 | const itertools = @import("main.zig"); 8 | const Item = itertools.Item; 9 | const IterError = itertools.IterError; 10 | const sliceIter = itertools.sliceIter; 11 | 12 | /// Returns the return type to be used in `toSlice` 13 | pub fn ToSlice(comptime Iter: type) type { 14 | return if (IterError(Iter)) |ES| 15 | (error{IterTooLong} || ES)![]Item(Iter) 16 | else 17 | error{IterTooLong}![]Item(Iter); 18 | } 19 | 20 | /// Collects the items of an iterator in a buffer slice. 21 | /// 22 | /// If the buffer is not big enough `error.IterTooLong` is returned. Otherwise, the slice containing 23 | /// all items is returned. This slice will always be at the start of buffer. 24 | pub fn toSlice( 25 | iter: anytype, 26 | buffer: []Item(Child(@TypeOf(iter))), 27 | ) ToSlice(Child(@TypeOf(iter))) { 28 | const has_error = comptime IterError(Child(@TypeOf(iter))) != null; 29 | var i: usize = 0; 30 | while (if (has_error) try iter.next() else iter.next()) |item| : (i += 1) { 31 | if (i >= buffer.len) return error.IterTooLong; 32 | buffer[i] = item; 33 | } 34 | return buffer[0..i]; 35 | } 36 | 37 | test "toSlice" { 38 | const slice: []const u32 = &.{ 1, 2, 3, 4 }; 39 | var iter = sliceIter(u32, slice); 40 | 41 | var buffer: [10]u32 = undefined; 42 | 43 | try testing.expectEqualSlices(u32, slice, try toSlice(&iter, &buffer)); 44 | 45 | var empty_buffer: [0]u32 = .{}; 46 | var iter2 = sliceIter(u32, slice); 47 | try testing.expectError(error.IterTooLong, toSlice(&iter2, &empty_buffer)); 48 | } 49 | 50 | test "toSlice error" { 51 | var iter = TestErrorIter.init(5); 52 | var buffer: [10]usize = undefined; 53 | 54 | try testing.expectError(error.TestErrorIterError, toSlice(&iter, &buffer)); 55 | } 56 | 57 | /// Returns the return type to be used in `toSliceAlloc` 58 | pub fn ToSliceAlloc(comptime Iter: type) type { 59 | return if (IterError(Iter)) |ES| 60 | (Allocator.Error || ES)![]Item(Iter) 61 | else 62 | Allocator.Error![]Item(Iter); 63 | } 64 | 65 | /// Collects the items of an iterator in an allocated slice. 66 | pub fn toSliceAlloc( 67 | iter: anytype, 68 | allocator: Allocator, 69 | ) ToSliceAlloc(Child(@TypeOf(iter))) { 70 | const has_error = comptime IterError(Child(@TypeOf(iter))) != null; 71 | var list = ArrayList(Item(Child(@TypeOf(iter)))).init(allocator); 72 | defer list.deinit(); 73 | while (if (has_error) try iter.next() else iter.next()) |item| { 74 | try list.append(item); 75 | } 76 | return try list.toOwnedSlice(); 77 | } 78 | 79 | test "toSliceAlloc" { 80 | const slice: []const u32 = &.{ 1, 2, 3, 4 }; 81 | var iter = sliceIter(u32, slice); 82 | 83 | const allocated = try toSliceAlloc(&iter, testing.allocator); 84 | defer testing.allocator.free(allocated); 85 | 86 | try testing.expectEqualSlices(u32, slice, allocated); 87 | } 88 | 89 | test "toSliceAlloc error" { 90 | var iter = TestErrorIter.init(5); 91 | 92 | try testing.expectError(error.TestErrorIterError, toSliceAlloc(&iter, testing.allocator)); 93 | } 94 | 95 | const TestErrorIter = struct { 96 | const Self = @This(); 97 | 98 | counter: usize = 0, 99 | until_err: usize, 100 | 101 | pub fn init(until_err: usize) Self { 102 | return .{ .until_err = until_err }; 103 | } 104 | 105 | pub fn next(self: *Self) !?usize { 106 | if (self.counter >= self.until_err) return error.TestErrorIterError; 107 | self.counter += 1; 108 | return self.counter - 1; 109 | } 110 | }; 111 | -------------------------------------------------------------------------------- /src/reduce.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | const Child = std.meta.Child; 4 | 5 | const itertools = @import("main.zig"); 6 | const Item = itertools.Item; 7 | const IterError = itertools.IterError; 8 | const sliceIter = itertools.sliceIter; 9 | 10 | /// Returns the return type to be used in `reduce` 11 | pub fn Reduce(comptime Iter: type, comptime T: type) type { 12 | return if (IterError(Iter)) |ES| 13 | ES!T 14 | else 15 | T; 16 | } 17 | 18 | /// Applies a binary operator between all items in iter with an initial element. 19 | /// 20 | /// Also know as fold in functional languages. 21 | pub fn reduce( 22 | iter: anytype, 23 | comptime T: type, 24 | func: *const fn (T, Item(Child(@TypeOf(iter)))) T, 25 | init: T, 26 | ) Reduce(Child(@TypeOf(iter)), T) { 27 | const has_error = comptime IterError(Child(@TypeOf(iter))) != null; 28 | var res = init; 29 | while (if (has_error) try iter.next() else iter.next()) |item| { 30 | res = func(res, item); 31 | } 32 | return res; 33 | } 34 | 35 | test "reduce" { 36 | const slice: []const u32 = &.{ 1, 2, 3, 4 }; 37 | var iter = sliceIter(u32, slice); 38 | 39 | const add = struct { 40 | fn add(x: u64, y: u32) u64 { 41 | return x + y; 42 | } 43 | }.add; 44 | 45 | try testing.expectEqual(@as(u64, 10), reduce(&iter, u64, add, 0)); 46 | } 47 | 48 | test "reduce error" { 49 | var iter = TestErrorIter.init(5); 50 | 51 | const add = struct { 52 | fn add(x: u64, y: usize) u64 { 53 | return x + y; 54 | } 55 | }.add; 56 | 57 | try testing.expectError(error.TestErrorIterError, reduce(&iter, u64, add, 0)); 58 | } 59 | 60 | /// Returns the return type to be used in `reduce1` 61 | pub fn Reduce1(comptime Iter: type) type { 62 | return if (IterError(Iter)) |ES| 63 | (error{EmptyIterator} || ES)!Item(Iter) 64 | else 65 | error{EmptyIterator}!Item(Iter); 66 | } 67 | 68 | /// Applies a binary operator between all items in iter with no initial element. 69 | /// 70 | /// If the iterator is empty `error.EmptyIterator` is returned. 71 | /// 72 | /// Also know as fold1 in functional languages. 73 | pub fn reduce1( 74 | iter: anytype, 75 | func: *const fn ( 76 | Item(Child(@TypeOf(iter))), 77 | Item(Child(@TypeOf(iter))), 78 | ) Item(Child(@TypeOf(iter))), 79 | ) Reduce1(Child(@TypeOf(iter))) { 80 | const has_error = comptime IterError(Child(@TypeOf(iter))) != null; 81 | const maybe_init = if (has_error) try iter.next() else iter.next(); 82 | const init = maybe_init orelse return error.EmptyIterator; 83 | return reduce(iter, Item(Child(@TypeOf(iter))), func, init); 84 | } 85 | 86 | test "reduce1" { 87 | const slice: []const u32 = &.{ 1, 2, 3, 4 }; 88 | var iter = sliceIter(u32, slice); 89 | 90 | const add = struct { 91 | fn add(x: u32, y: u32) u32 { 92 | return x + y; 93 | } 94 | }.add; 95 | 96 | try testing.expectEqual(@as(u32, 10), try reduce1(&iter, add)); 97 | try testing.expectError(error.EmptyIterator, reduce1(&iter, add)); 98 | } 99 | 100 | test "reduce1 error" { 101 | var iter = TestErrorIter.init(5); 102 | 103 | const add = struct { 104 | fn add(x: usize, y: usize) usize { 105 | return x + y; 106 | } 107 | }.add; 108 | 109 | try testing.expectError(error.TestErrorIterError, reduce1(&iter, add)); 110 | } 111 | 112 | const TestErrorIter = struct { 113 | const Self = @This(); 114 | 115 | counter: usize = 0, 116 | until_err: usize, 117 | 118 | pub fn init(until_err: usize) Self { 119 | return .{ .until_err = until_err }; 120 | } 121 | 122 | pub fn next(self: *Self) !?usize { 123 | if (self.counter >= self.until_err) return error.TestErrorIterError; 124 | self.counter += 1; 125 | return self.counter - 1; 126 | } 127 | }; 128 | -------------------------------------------------------------------------------- /src/map_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const Item = itertools.Item; 6 | const IterError = itertools.IterError; 7 | const sliceIter = itertools.sliceIter; 8 | 9 | /// Iter type for mapping another iterator with a function 10 | /// 11 | /// See `map` for more info. 12 | pub fn MapIter(comptime BaseIter: type, comptime Dest: type) type { 13 | return struct { 14 | const Self = @This(); 15 | 16 | base_iter: BaseIter, 17 | func: *const fn (Item(BaseIter)) Dest, 18 | 19 | pub const Next = if (IterError(BaseIter)) |ES| ES!?Dest else ?Dest; 20 | 21 | pub fn next(self: *Self) Next { 22 | const has_error = comptime IterError(BaseIter) != null; 23 | const maybe_item = if (has_error) 24 | try self.base_iter.next() 25 | else 26 | self.base_iter.next(); 27 | return if (maybe_item) |item| 28 | self.func(item) 29 | else 30 | null; 31 | } 32 | }; 33 | } 34 | 35 | /// Returns the destination type for a given base iterator and function type 36 | pub fn MapDestType(comptime BaseIter: type, comptime Func: type) type { 37 | const Source = Item(BaseIter); 38 | 39 | const func = switch (@typeInfo(Func)) { 40 | .Fn => |func| func, 41 | else => @compileError("map func must be a function"), 42 | }; 43 | 44 | if (func.params.len != 1) 45 | @compileError("map func must be a unary function"); 46 | 47 | if (func.params[0].type.? != Source) 48 | @compileError("map func's argument must be iter's item type"); 49 | 50 | return func.return_type.?; 51 | } 52 | 53 | /// Returns a new iterator which maps items in iter using func as a function. 54 | /// 55 | /// iter must be an iterator, meaning it has to be a type containing a next method which returns 56 | /// an optional. func must be a unary function for which it's first argument type is the iterator's 57 | /// item type. 58 | pub fn map( 59 | iter: anytype, 60 | func: anytype, 61 | ) MapIter(@TypeOf(iter), MapDestType(@TypeOf(iter), @TypeOf(func))) { 62 | return .{ .base_iter = iter, .func = func }; 63 | } 64 | 65 | test "MapIter" { 66 | const slice: []const u32 = &.{ 1, 2, 3, 4 }; 67 | var slice_iter = sliceIter(u32, slice); 68 | 69 | const functions = struct { 70 | pub fn double(x: u32) u64 { 71 | return 2 * x; 72 | } 73 | 74 | pub fn addOne(x: u64) u65 { 75 | return x + 1; 76 | } 77 | }; 78 | 79 | var iter = map(map(slice_iter, functions.double), functions.addOne); 80 | 81 | try testing.expectEqual(u65, Item(@TypeOf(iter))); 82 | try testing.expectEqual(@as(?u65, 3), iter.next()); 83 | try testing.expectEqual(@as(?u65, 5), iter.next()); 84 | try testing.expectEqual(@as(?u65, 7), iter.next()); 85 | try testing.expectEqual(@as(?u65, 9), iter.next()); 86 | try testing.expectEqual(@as(?u65, null), iter.next()); 87 | try testing.expectEqual(@as(?u65, null), iter.next()); 88 | } 89 | 90 | test "MapIter error" { 91 | var test_iter = TestErrorIter.init(3); 92 | 93 | const double = struct { 94 | pub fn double(x: usize) u64 { 95 | return 2 * x; 96 | } 97 | }.double; 98 | 99 | var iter = map(test_iter, double); 100 | 101 | try testing.expectEqual(u64, Item(@TypeOf(iter))); 102 | try testing.expectEqual(@as(?u64, 0), try iter.next()); 103 | try testing.expectEqual(@as(?u64, 2), try iter.next()); 104 | try testing.expectEqual(@as(?u64, 4), try iter.next()); 105 | try testing.expectError(error.TestErrorIterError, iter.next()); 106 | } 107 | 108 | const TestErrorIter = struct { 109 | const Self = @This(); 110 | 111 | counter: usize = 0, 112 | until_err: usize, 113 | 114 | pub fn init(until_err: usize) Self { 115 | return .{ .until_err = until_err }; 116 | } 117 | 118 | pub fn next(self: *Self) !?usize { 119 | if (self.counter >= self.until_err) return error.TestErrorIterError; 120 | self.counter += 1; 121 | return self.counter - 1; 122 | } 123 | }; 124 | -------------------------------------------------------------------------------- /src/chain_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const Item = itertools.Item; 6 | const IterError = itertools.IterError; 7 | const sliceIter = itertools.sliceIter; 8 | const range = itertools.range; 9 | 10 | /// An iterator that links two iterators together, in a chain. 11 | /// 12 | /// See `chain` for more info. 13 | pub fn ChainIter(comptime A: type, comptime B: type) type { 14 | if (Item(A) != Item(B)) 15 | @compileError("Both chain iterators must yield the same type"); 16 | const Dest = Item(A); 17 | 18 | return struct { 19 | const Self = @This(); 20 | 21 | a: A, 22 | b: B, 23 | a_done: bool, 24 | 25 | pub const Next = if (IterError(A)) |ESA| 26 | if (IterError(B)) |ESB| 27 | (ESA || ESB)!?Dest 28 | else 29 | ESA!?Dest 30 | else if (IterError(B)) |ESB| 31 | ESB!?Dest 32 | else 33 | ?Dest; 34 | 35 | pub fn next(self: *Self) Next { 36 | const a_has_error = comptime IterError(A) != null; 37 | const b_has_error = comptime IterError(B) != null; 38 | if (!self.a_done) { 39 | const maybe_a = if (a_has_error) 40 | try self.a.next() 41 | else 42 | self.a.next(); 43 | if (maybe_a) |a| 44 | return a; 45 | self.a_done = true; 46 | } 47 | return if (b_has_error) 48 | try self.b.next() 49 | else 50 | self.b.next(); 51 | } 52 | }; 53 | } 54 | 55 | /// Takes two iterators and creates a new iterator over both in sequence. 56 | /// 57 | /// chain() will return a new iterator which will first iterate over values from 58 | /// the first iterator and then over values from the second iterator. 59 | /// 60 | /// In other words, it links two iterators together, in a chain. 🔗 61 | pub fn chain(iter1: anytype, iter2: anytype) ChainIter(@TypeOf(iter1), @TypeOf(iter2)) { 62 | return .{ .a = iter1, .b = iter2, .a_done = false }; 63 | } 64 | 65 | test "Chain" { 66 | var iter1 = sliceIter(u32, &.{ 1, 2, 3 }); 67 | var iter2 = range(u32, 5, 8); 68 | var iter = chain(iter1, iter2); 69 | try testing.expectEqual(u32, Item(@TypeOf(iter))); 70 | try testing.expectEqual(@as(?u32, 1), iter.next()); 71 | try testing.expectEqual(@as(?u32, 2), iter.next()); 72 | try testing.expectEqual(@as(?u32, 3), iter.next()); 73 | try testing.expectEqual(@as(?u32, 5), iter.next()); 74 | try testing.expectEqual(@as(?u32, 6), iter.next()); 75 | try testing.expectEqual(@as(?u32, 7), iter.next()); 76 | try testing.expectEqual(@as(?u32, null), iter.next()); 77 | } 78 | 79 | test "Chain error in iter1" { 80 | var iter1 = TestErrorIter.init(3); 81 | var iter2 = range(usize, 5, 8); 82 | var iter = chain(iter1, iter2); 83 | try testing.expectEqual(usize, Item(@TypeOf(iter))); 84 | try testing.expectEqual(@as(?usize, 0), try iter.next()); 85 | try testing.expectEqual(@as(?usize, 1), try iter.next()); 86 | try testing.expectEqual(@as(?usize, 2), try iter.next()); 87 | try testing.expectError(error.TestErrorIterError, iter.next()); 88 | } 89 | 90 | test "Chain error in iter2" { 91 | var iter1 = range(usize, 5, 8); 92 | var iter2 = TestErrorIter.init(3); 93 | var iter = chain(iter1, iter2); 94 | try testing.expectEqual(usize, Item(@TypeOf(iter))); 95 | try testing.expectEqual(@as(?usize, 5), try iter.next()); 96 | try testing.expectEqual(@as(?usize, 6), try iter.next()); 97 | try testing.expectEqual(@as(?usize, 7), try iter.next()); 98 | try testing.expectEqual(@as(?usize, 0), try iter.next()); 99 | try testing.expectEqual(@as(?usize, 1), try iter.next()); 100 | try testing.expectEqual(@as(?usize, 2), try iter.next()); 101 | try testing.expectError(error.TestErrorIterError, iter.next()); 102 | } 103 | 104 | const TestErrorIter = struct { 105 | const Self = @This(); 106 | 107 | counter: usize = 0, 108 | until_err: usize, 109 | 110 | pub fn init(until_err: usize) Self { 111 | return .{ .until_err = until_err }; 112 | } 113 | 114 | pub fn next(self: *Self) !?usize { 115 | if (self.counter >= self.until_err) return error.TestErrorIterError; 116 | self.counter += 1; 117 | return self.counter - 1; 118 | } 119 | }; 120 | -------------------------------------------------------------------------------- /src/filter_map_iter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const itertools = @import("main.zig"); 5 | const Item = itertools.Item; 6 | const IterError = itertools.IterError; 7 | const sliceIter = itertools.sliceIter; 8 | 9 | /// An iterator that uses f to both filter and map elements from iter. 10 | /// 11 | /// See `map` for more info. 12 | pub fn FilterMapIter(comptime BaseIter: type, comptime Dest: type) type { 13 | return struct { 14 | const Self = @This(); 15 | 16 | base_iter: BaseIter, 17 | func: *const fn (Item(BaseIter)) ?Dest, 18 | 19 | pub const Next = if (IterError(BaseIter)) |ES| ES!?Dest else ?Dest; 20 | 21 | pub fn next(self: *Self) Next { 22 | const has_error = comptime IterError(BaseIter) != null; 23 | const maybe_item = if (has_error) 24 | try self.base_iter.next() 25 | else 26 | self.base_iter.next(); 27 | 28 | const item = maybe_item orelse return null; 29 | return if (self.func(item)) |transformed| 30 | transformed 31 | else if (has_error) 32 | try self.next() 33 | else 34 | self.next(); 35 | } 36 | }; 37 | } 38 | 39 | /// Returns the destination type for a given base iterator and function type 40 | pub fn FilterMapDestType(comptime BaseIter: type, comptime Func: type) type { 41 | const Source = Item(BaseIter); 42 | 43 | const func = switch (@typeInfo(Func)) { 44 | .Fn => |func| func, 45 | else => @compileError("filterMap func must be a function"), 46 | }; 47 | 48 | if (func.params.len != 1) 49 | @compileError("filterMap func must be a unary function"); 50 | 51 | if (func.params[0].type.? != Source) 52 | @compileError("filterMap func's argument must be iter's item type"); 53 | 54 | return switch (@typeInfo(func.return_type.?)) { 55 | .Optional => |optional| optional.child, 56 | else => @compileError("filterMap func's return type must be an optional"), 57 | }; 58 | } 59 | 60 | /// Creates an iterator that both filters and maps. 61 | /// 62 | /// The returned iterator yields only the values for which the supplied function does not return null. 63 | /// 64 | /// filterMap can be used to make chains of filter and map more concise. 65 | pub fn filterMap( 66 | iter: anytype, 67 | func: anytype, 68 | ) FilterMapIter(@TypeOf(iter), FilterMapDestType(@TypeOf(iter), @TypeOf(func))) { 69 | return .{ .base_iter = iter, .func = func }; 70 | } 71 | 72 | test "FilterMapIter" { 73 | const slice: []const u32 = &.{ 1, 2, 3, 4, 5, 6, 7, 8 }; 74 | var slice_iter = sliceIter(u32, slice); 75 | 76 | const func = struct { 77 | pub fn func(x: u32) ?u64 { 78 | return if (x % 2 == 0) 79 | x / 2 80 | else 81 | null; 82 | } 83 | }.func; 84 | 85 | var iter = filterMap(slice_iter, func); 86 | 87 | try testing.expectEqual(u64, Item(@TypeOf(iter))); 88 | try testing.expectEqual(@as(?u64, 1), iter.next()); 89 | try testing.expectEqual(@as(?u64, 2), iter.next()); 90 | try testing.expectEqual(@as(?u64, 3), iter.next()); 91 | try testing.expectEqual(@as(?u64, 4), iter.next()); 92 | try testing.expectEqual(@as(?u64, null), iter.next()); 93 | try testing.expectEqual(@as(?u64, null), iter.next()); 94 | } 95 | 96 | test "MapIter error" { 97 | var test_iter = TestErrorIter.init(3); 98 | 99 | const func = struct { 100 | pub fn func(x: usize) ?u64 { 101 | return if (x % 2 == 0) 102 | x / 2 103 | else 104 | null; 105 | } 106 | }.func; 107 | 108 | var iter = filterMap(test_iter, func); 109 | 110 | try testing.expectEqual(u64, Item(@TypeOf(iter))); 111 | try testing.expectEqual(@as(?u64, 0), try iter.next()); 112 | try testing.expectEqual(@as(?u64, 1), try iter.next()); 113 | try testing.expectError(error.TestErrorIterError, iter.next()); 114 | } 115 | 116 | const TestErrorIter = struct { 117 | const Self = @This(); 118 | 119 | counter: usize = 0, 120 | until_err: usize, 121 | 122 | pub fn init(until_err: usize) Self { 123 | return .{ .until_err = until_err }; 124 | } 125 | 126 | pub fn next(self: *Self) !?usize { 127 | if (self.counter >= self.until_err) return error.TestErrorIterError; 128 | self.counter += 1; 129 | return self.counter - 1; 130 | } 131 | }; 132 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | const slice_iter = @import("slice_iter.zig"); 5 | pub const SliceIter = slice_iter.SliceIter; 6 | pub const sliceIter = slice_iter.sliceIter; 7 | const range_iter = @import("range_iter.zig"); 8 | pub const range = range_iter.range; 9 | pub const RangeIter = range_iter.RangeIter; 10 | const chain_iter = @import("chain_iter.zig"); 11 | pub const chain = chain_iter.chain; 12 | pub const ChainIter = chain_iter.ChainIter; 13 | const cycle_iter = @import("cycle_iter.zig"); 14 | pub const cycle = cycle_iter.cycle; 15 | pub const CycleIter = cycle_iter.CycleIter; 16 | const enumerate_iter = @import("enumerate_iter.zig"); 17 | pub const enumerate = enumerate_iter.enumerate; 18 | pub const EnumerateIter = enumerate_iter.EnumerateIter; 19 | const empty_iter = @import("empty_iter.zig"); 20 | pub const empty = empty_iter.empty; 21 | pub const EmptyIter = empty_iter.EmptyIter; 22 | const map_iter = @import("map_iter.zig"); 23 | pub const map = map_iter.map; 24 | pub const MapIter = map_iter.MapIter; 25 | pub const MapDestType = map_iter.MapDestType; 26 | const filter_iter = @import("filter_iter.zig"); 27 | pub const filter = filter_iter.filter; 28 | pub const FilterIter = filter_iter.FilterIter; 29 | const to_slice = @import("to_slice.zig"); 30 | pub const ToSlice = to_slice.ToSlice; 31 | pub const toSlice = to_slice.toSlice; 32 | pub const ToSliceAlloc = to_slice.ToSliceAlloc; 33 | pub const toSliceAlloc = to_slice.toSliceAlloc; 34 | const reduce_namespace = @import("reduce.zig"); 35 | pub const reduce = reduce_namespace.reduce; 36 | pub const Reduce = reduce_namespace.Reduce; 37 | pub const reduce1 = reduce_namespace.reduce1; 38 | pub const Reduce1 = reduce_namespace.Reduce1; 39 | const filter_map_iter = @import("filter_map_iter.zig"); 40 | pub const filterMap = filter_map_iter.filterMap; 41 | pub const FilterMapIter = filter_map_iter.FilterMapIter; 42 | pub const FilterMapDestType = filter_map_iter.FilterMapDestType; 43 | const flatten_iter = @import("flatten_iter.zig"); 44 | pub const flatten = flatten_iter.flatten; 45 | pub const FlattenIter = flatten_iter.FlattenIter; 46 | const once_iter = @import("once_iter.zig"); 47 | pub const once = once_iter.once; 48 | pub const OnceIter = once_iter.OnceIter; 49 | const peekable_iter = @import("peekable_iter.zig"); 50 | pub const peekable = peekable_iter.peekable; 51 | pub const PeekableIter = peekable_iter.PeekableIter; 52 | const repeat_iter = @import("repeat_iter.zig"); 53 | pub const repeat = repeat_iter.repeat; 54 | pub const RepeatIter = repeat_iter.RepeatIter; 55 | const skip_iter = @import("skip_iter.zig"); 56 | pub const skip = skip_iter.skip; 57 | pub const SkipIter = skip_iter.SkipIter; 58 | const skip_while_iter = @import("skip_while_iter.zig"); 59 | pub const skipWhile = skip_while_iter.skipWhile; 60 | pub const SkipWhileIter = skip_while_iter.SkipWhileIter; 61 | const successors_iter = @import("successors_iter.zig"); 62 | pub const successors = successors_iter.successors; 63 | pub const SuccessorsIter = successors_iter.SuccessorsIter; 64 | const take_iter = @import("take_iter.zig"); 65 | pub const take = take_iter.take; 66 | pub const TakeIter = take_iter.TakeIter; 67 | const take_while_iter = @import("take_while_iter.zig"); 68 | pub const takeWhile = take_while_iter.takeWhile; 69 | pub const TakeWhileIter = take_while_iter.TakeWhileIter; 70 | const zip_iter = @import("zip_iter.zig"); 71 | pub const zip = zip_iter.zip; 72 | pub const ZipIter = zip_iter.ZipIter; 73 | 74 | test { 75 | testing.refAllDeclsRecursive(@This()); 76 | } 77 | 78 | /// Returns the type of item the iterator holds 79 | /// 80 | /// # example 81 | /// ``` 82 | /// var iter = std.mem.tokenize(u8, "hi there world", " "); 83 | /// std.debug.assert(Item(@TypeOf(iter)) == []const u8); 84 | /// ``` 85 | pub fn Item(comptime Iter: type) type { 86 | if (!@hasDecl(Iter, "next")) 87 | @compileError("iterator must have 'next' function"); 88 | 89 | const func = switch (@typeInfo(@TypeOf(Iter.next))) { 90 | .Fn => |func| func, 91 | else => @compileError("iterator 'next' declaration must be a function"), 92 | }; 93 | 94 | if (func.params.len != 1) 95 | @compileError("iterator 'next' function must take exactly one argument"); 96 | 97 | const Param = func.params[0].type.?; 98 | if (Param != Iter and Param != *Iter and Param != *const Iter) 99 | @compileError( 100 | "iterator 'next' function's parameter type must be itself or a pointer to itself", 101 | ); 102 | 103 | return switch (@typeInfo(func.return_type.?)) { 104 | .ErrorUnion => |eu| switch (@typeInfo(eu.payload)) { 105 | .Optional => |opt| opt.child, 106 | else => @compileError( 107 | "iterator 'next' function return type must be an optional" ++ 108 | " or an error union with optional payload", 109 | ), 110 | }, 111 | .Optional => |opt| opt.child, 112 | else => @compileError( 113 | "iterator 'next' function return type must be an optional" ++ 114 | " or an error union with optional payload", 115 | ), 116 | }; 117 | } 118 | 119 | test "Item" { 120 | var iter = std.mem.tokenize(u8, "hi there world", " "); 121 | try testing.expectEqual([]const u8, Item(@TypeOf(iter))); 122 | } 123 | 124 | /// Returns the error set of the iterator's `next` function 125 | /// 126 | /// This library supports iterators which can fail. This function will return the error set of 127 | /// such iterators or `null` if they can't fail. 128 | /// 129 | /// # examples 130 | /// ``` 131 | /// var iter = std.mem.tokenize(u8, "hi there world", " "); 132 | /// std.debug.assert(IterError(@TypeOf(iter)) == null); 133 | /// ``` 134 | /// 135 | /// ``` 136 | /// var dir = someIterableDirFromSomewhere(); 137 | /// const walker = try dir.walk(); 138 | /// std.debug.assert(IterError(@TypeOf(walker)) == std.mem.Allocator.Error); 139 | /// ``` 140 | pub fn IterError(comptime Iter: type) ?type { 141 | if (!@hasDecl(Iter, "next")) 142 | @compileError("iterator must have 'next' function"); 143 | 144 | const func = switch (@typeInfo(@TypeOf(Iter.next))) { 145 | .Fn => |func| func, 146 | else => @compileError("iterator 'next' declaration must be a function"), 147 | }; 148 | 149 | if (func.params.len != 1) 150 | @compileError("iterator 'next' function must take exactly one argument"); 151 | 152 | const Param = func.params[0].type.?; 153 | if (Param != Iter and Param != *Iter and Param != *const Iter) 154 | @compileError( 155 | "iterator 'next' function's parameter type must be itself or a pointer to itself", 156 | ); 157 | 158 | return switch (@typeInfo(func.return_type.?)) { 159 | .ErrorUnion => |eu| switch (@typeInfo(eu.payload)) { 160 | .Optional => eu.error_set, 161 | else => @compileError( 162 | "iterator 'next' function return type must be an optional" ++ 163 | " or an error union with optional payload", 164 | ), 165 | }, 166 | .Optional => null, 167 | else => @compileError( 168 | "iterator 'next' function return type must be an optional" ++ 169 | " or an error union with optional payload", 170 | ), 171 | }; 172 | } 173 | 174 | test "IterError" { 175 | var iter = std.mem.tokenize(u8, "hi there world", " "); 176 | try testing.expectEqual(@as(?type, null), IterError(@TypeOf(iter))); 177 | 178 | const dir = testing.tmpIterableDir(.{}).iterable_dir; 179 | var walker = try dir.walk(testing.allocator); 180 | defer walker.deinit(); 181 | try testing.expectEqual( 182 | @as(?type, @typeInfo( 183 | @typeInfo(@TypeOf(std.fs.IterableDir.Walker.next)).Fn.return_type.?, 184 | ).ErrorUnion.error_set), 185 | IterError(@TypeOf(walker)), 186 | ); 187 | } 188 | 189 | test "tokenize" { 190 | const string = "hi there world"; 191 | var tokens = std.mem.tokenizeAny(u8, string, " "); 192 | 193 | const length = struct { 194 | fn length(x: []const u8) usize { 195 | return x.len; 196 | } 197 | }.length; 198 | 199 | var iter = map(tokens, length); 200 | var buffer: [10]usize = undefined; 201 | const lengths = try toSlice(&iter, &buffer); 202 | 203 | try testing.expectEqualSlices(usize, &.{ 2, 5, 5 }, lengths); 204 | } 205 | --------------------------------------------------------------------------------