Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: KilianVounckx/zitertools
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: main
Choose a base ref
...
head repository: DanikVitek/zitertools
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
Able to merge. These branches can be automatically merged.

Commits on Dec 10, 2023

  1. Copy the full SHA
    00b1e46 View commit details
  2. Copy the full SHA
    5392931 View commit details
  3. Copy the full SHA
    07cc1fd View commit details
  4. Copy the full SHA
    cf5b5cb View commit details
  5. Copy the full SHA
    91bd97e View commit details
  6. reduce nesting

    DanikVitek committed Dec 10, 2023
    Copy the full SHA
    0b3814d View commit details
  7. Copy the full SHA
    c160950 View commit details
  8. Copy the full SHA
    6d6f152 View commit details
  9. rename reduce to fold and reduce1 to reduce. Add `foldContext…

    …` and `reduceContext`
    DanikVitek committed Dec 10, 2023
    Copy the full SHA
    15fdd8f View commit details
  10. Copy the full SHA
    1cd5cf5 View commit details
  11. Copy the full SHA
    fce7242 View commit details
  12. Remove usage of Child

    DanikVitek committed Dec 10, 2023
    Copy the full SHA
    39bd935 View commit details
  13. Copy the full SHA
    ca783c8 View commit details
  14. Add/Update docs

    DanikVitek committed Dec 10, 2023
    Copy the full SHA
    fe70160 View commit details
  15. Copy the full SHA
    30111c1 View commit details
  16. Copy the full SHA
    7eb0e04 View commit details

Commits on Dec 11, 2023

  1. Copy the full SHA
    fb8376d View commit details
  2. Update successors impl

    DanikVitek committed Dec 11, 2023
    Copy the full SHA
    600a882 View commit details
  3. Copy the full SHA
    7e953e5 View commit details
  4. Revert build.zig

    DanikVitek committed Dec 11, 2023
    Copy the full SHA
    5e57f3a View commit details
  5. Add forEach

    DanikVitek committed Dec 11, 2023
    Copy the full SHA
    a00b309 View commit details
  6. Fix for_each

    DanikVitek committed Dec 11, 2023
    Copy the full SHA
    b040e02 View commit details
  7. Copy the full SHA
    c9f14fd View commit details
  8. Copy the full SHA
    3c7e905 View commit details
  9. Copy the full SHA
    5682652 View commit details
  10. Fix error handling

    DanikVitek committed Dec 11, 2023
    Copy the full SHA
    71c4388 View commit details
  11. Update panics

    DanikVitek committed Dec 11, 2023
    Copy the full SHA
    1ce765e View commit details
  12. Create LICENSE

    DanikVitek authored Dec 11, 2023
    Copy the full SHA
    bd76840 View commit details
  13. Copy the full SHA
    8e91c81 View commit details
  14. Copy the full SHA
    6ea9dea View commit details
  15. Copy the full SHA
    fb0764c View commit details
  16. Update README.md

    DanikVitek committed Dec 11, 2023
    Copy the full SHA
    488a6de View commit details
  17. Update README.md

    DanikVitek authored Dec 11, 2023
    Copy the full SHA
    6d812d2 View commit details
  18. Copy the full SHA
    d446fd2 View commit details

Commits on Dec 12, 2023

  1. Copy the full SHA
    e9ed4a1 View commit details
  2. Refactoring and fixes

    - Remove type annotations in range functions.
    - Validate predicate functions to be able to return errors
    DanikVitek committed Dec 12, 2023
    Copy the full SHA
    a5e42e8 View commit details
  3. Copy the full SHA
    e9e3247 View commit details

Commits on Dec 13, 2023

  1. Copy the full SHA
    42e7057 View commit details

Commits on Dec 16, 2023

  1. Copy the full SHA
    f6eb95e View commit details

Commits on Mar 22, 2024

  1. Copy the full SHA
    ad1224c View commit details
  2. Copy the full SHA
    f9fed0f View commit details
  3. Revert "Rename main.zig to root.zig"

    This reverts commit f9fed0f.
    DanikVitek committed Mar 22, 2024
    Copy the full SHA
    a5876c8 View commit details
Showing with 1,979 additions and 288 deletions.
  1. +1 −0 .gitignore
  2. +21 −0 LICENSE
  3. +17 −14 README.md
  4. +3 −3 build.zig
  5. +5 −0 build.zig.zon
  6. +6 −6 src/chain_iter.zig
  7. +3 −7 src/cycle_iter.zig
  8. +1 −1 src/enumerate_iter.zig
  9. +143 −16 src/filter_iter.zig
  10. +189 −28 src/filter_map_iter.zig
  11. +103 −0 src/find.zig
  12. +15 −18 src/flatten_iter.zig
  13. +119 −0 src/fold.zig
  14. +50 −0 src/for_each.zig
  15. +225 −0 src/iter_methods.zig
  16. +54 −0 src/lines_iter.zig
  17. +80 −7 src/main.zig
  18. +144 −28 src/map_iter.zig
  19. +87 −0 src/nth.zig
  20. +1 −1 src/peekable_iter.zig
  21. +99 −0 src/product.zig
  22. +21 −7 src/range_iter.zig
  23. +61 −61 src/reduce.zig
  24. +1 −1 src/skip_iter.zig
  25. +105 −22 src/skip_while_iter.zig
  26. +14 −1 src/slice_iter.zig
  27. +65 −15 src/successors_iter.zig
  28. +103 −0 src/sum.zig
  29. +2 −2 src/take_iter.zig
  30. +80 −9 src/take_while_iter.zig
  31. +161 −41 src/zip_iter.zig
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
!.gitignore

!build.zig
!build.zig.zon
!src/**/*.zig

!README.md
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 Danik Vitek

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
31 changes: 17 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -7,47 +7,50 @@ This package is heavily based on rust's `std::iter` module.
## Usage

To use this package, use the zig package manager. For example in your build.zig.zon file, put:

```zig
.{
.name = "app",
.version = "0.1.0",
.dependencies = .{
.itertools = .{
.url = "https://github.com/KilianVounckx/zitertools/archive/$COMMIT_YOU_WANT_TO_USE.tar.gz",
.zitertools = .{
.url = "https://github.com/DanikVitek/zitertools/archive/$COMMIT_YOU_WANT_TO_USE.tar.gz",
},
},
}
```

When running zig build now, zig will tell you you need a hash for the dependency and provide one.
Put it in you dependency so it looks like:

```zig
.{
.itertools = .{
.url = "https://github.com/KilianVounckx/zitertools/archive/$COMMIT_YOU_WANT_TO_USE.tar.gz",
.zitertools = .{
.url = "https://github.com/DanikVitek/zitertools/archive/$COMMIT_YOU_WANT_TO_USE.tar.gz",
.hash = "$HASH_ZIG_GAVE_YOU",
},
}
```

With the dependency in place, you can now put the following in your build.zig file:

```zig
// This will create a `std.build.Dependency` which you can use to fetch
// the itertools module. The first argument is the dependency name. It
// the `zitertools` module. The first argument is the dependency name. It
// should be the same as the one you used in build.zig.zon.
const itertools = b.dependency("itertools", .{});
const zitertools = b.dependency("zitertools", .{});
// This will create a module which you can use in your zig code. The first
// argument is the name you want your module to have in your zig code. It
// can be anything you want. In your zig code you can use `@import` with
// the same name to use it. The second argument is a module. You can
// fetch it from the dependency with its `module` method. This method
// takes one argument which is the name of the module. This time, the
// name is the one the itertools package uses. It must be exactly the
// same string as below: "itertools". The reason for needing this name is
// name is the one the `zitertools` package uses. It must be exactly the
// same string as below: "zitertools". The reason for needing this name is
// that some packages can expose multiple modules. Therefor, you need to
// specify which one you want. This package only exposes one module though,
// so it will always be the same.
exe.addModule("itertools", itertools.module("itertools"));
exe.addModule("zitertools", zitertools.module("zitertools"));
```

In the above change `exe` to whatever CompileStep you are using. For an executable it will
@@ -58,13 +61,13 @@ With the build file in order, you can now use the module in your zig source. For
```zig
const std = @import("std");
// If you named the module something else in `build.zig`, use the other name here
// E.g. if in `build.zig` your did `exe.addModule("foobar", itertools.module("itertools"));`
// Then use `const itertools = @import("foobar");` here.
const itertools = @import("itertools");
// E.g. if in `build.zig` your did `exe.addModule("foobar", zitertools.module("zitertools"));`
// Then use `const zitertools = @import("foobar");` here.
const zitertools = @import("zitertools");
pub fn main() void {
var iter = itertools.range(u32, 0, 5);
std.debug.print("{s}\n", .{ @typeName(itertools.Item(@TypeOf(iter))) }); // Output: u32
var iter = zitertools.range(@as(u32, 0), 5);
std.debug.print("{s}\n", .{ @typeName(zitertools.Item(@TypeOf(iter))) }); // Output: u32
std.debug.print("{?}\n", .{ iter.next()) }; // Output: 0
std.debug.print("{?}\n", .{ iter.next()) }; // Output: 1
std.debug.print("{?}\n", .{ iter.next()) }; // Output: 2
6 changes: 3 additions & 3 deletions build.zig
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ pub fn build(b: *std.Build) void {
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});

const module = b.addModule("itertools", .{
const module = b.addModule("zitertools", .{
.source_file = .{ .path = "src/main.zig" },
});

@@ -28,8 +28,8 @@ pub fn build(b: *std.Build) void {
});

var dep_iter = module.dependencies.iterator();
while (dep_iter.next()) |entry| {
main_tests.addModule(entry.key_ptr.*, entry.value_ptr.*);
while (dep_iter.next()) |dep| {
main_tests.addModule(dep.key_ptr.*, dep.value_ptr.*);
}

const run_main_tests = b.addRunArtifact(main_tests);
5 changes: 5 additions & 0 deletions build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.{
.name = "zitertools",
.version = "0.0.1",
.dependencies = .{},
}
12 changes: 6 additions & 6 deletions src/chain_iter.zig
Original file line number Diff line number Diff line change
@@ -63,8 +63,8 @@ pub fn chain(iter1: anytype, iter2: anytype) ChainIter(@TypeOf(iter1), @TypeOf(i
}

test "Chain" {
var iter1 = sliceIter(u32, &.{ 1, 2, 3 });
var iter2 = range(u32, 5, 8);
const iter1 = sliceIter(u32, &.{ 1, 2, 3 });
const iter2 = range(@as(u32, 5), 8);
var iter = chain(iter1, iter2);
try testing.expectEqual(u32, Item(@TypeOf(iter)));
try testing.expectEqual(@as(?u32, 1), iter.next());
@@ -77,8 +77,8 @@ test "Chain" {
}

test "Chain error in iter1" {
var iter1 = TestErrorIter.init(3);
var iter2 = range(usize, 5, 8);
const iter1 = TestErrorIter.init(3);
const iter2 = range(@as(usize, 5), 8);
var iter = chain(iter1, iter2);
try testing.expectEqual(usize, Item(@TypeOf(iter)));
try testing.expectEqual(@as(?usize, 0), try iter.next());
@@ -88,8 +88,8 @@ test "Chain error in iter1" {
}

test "Chain error in iter2" {
var iter1 = range(usize, 5, 8);
var iter2 = TestErrorIter.init(3);
const iter1 = range(@as(usize, 5), 8);
const iter2 = TestErrorIter.init(3);
var iter = chain(iter1, iter2);
try testing.expectEqual(usize, Item(@TypeOf(iter)));
try testing.expectEqual(@as(?usize, 5), try iter.next());
10 changes: 3 additions & 7 deletions src/cycle_iter.zig
Original file line number Diff line number Diff line change
@@ -28,13 +28,9 @@ pub fn CycleIter(comptime BaseIter: type) type {

if (maybe_item) |item| {
return item;
} else {
self.base_iter = self.orig_iter;
return if (has_error)
try self.base_iter.next()
else
self.base_iter.next();
}
self.base_iter = self.orig_iter;
return @call(.always_tail, Self.next, .{self});
}
};
}
@@ -50,7 +46,7 @@ pub fn cycle(iter: anytype) CycleIter(@TypeOf(iter)) {
}

test "Cycle" {
var base_iter = range(u32, 0, 3);
const base_iter = range(@as(u32, 0), 3);
var iter = cycle(base_iter);
try std.testing.expectEqual(u32, Item(@TypeOf(iter)));
try testing.expectEqual(@as(?u32, 0), iter.next());
2 changes: 1 addition & 1 deletion src/enumerate_iter.zig
Original file line number Diff line number Diff line change
@@ -54,7 +54,7 @@ pub fn enumerate(iter: anytype) EnumerateIter(@TypeOf(iter)) {
}

test "enumerate" {
var base_iter = range(u32, 5, 10).stepBy(2);
const base_iter = range(@as(u32, 5), 10).stepBy(2);
var iter = enumerate(base_iter);
try testing.expectEqual(@TypeOf(iter).Item, Item(@TypeOf(iter)));
const v1 = iter.next().?;
159 changes: 143 additions & 16 deletions src/filter_iter.zig
Original file line number Diff line number Diff line change
@@ -9,25 +9,67 @@ const sliceIter = itertools.sliceIter;
/// Iter type for filtering another iterator with a predicate
///
/// See `filter` for more info.
pub fn FilterIter(comptime BaseIter: type) type {
pub fn FilterIter(comptime BaseIter: type, comptime predicate: anytype) type {
const validatedPredicate = validatePredicateFn(BaseIter, predicate);
return struct {
const Self = @This();

base_iter: BaseIter,
predicate: *const fn (Item(BaseIter)) bool,

pub const Next = if (IterError(BaseIter)) |ES| ES!?Item(BaseIter) else ?Item(BaseIter);

pub fn next(self: *Self) Next {
const has_error = comptime IterError(BaseIter) != null;
const predicate_has_error = comptime @typeInfo(@TypeOf(validatedPredicate)) == .ErrorUnion;

const maybe_item = if (has_error)
try self.base_iter.next()
else
self.base_iter.next();
const item = maybe_item orelse return null;
if (self.predicate(item))
return item;
return self.next();

const predicate_result = if (predicate_has_error)
try validatedPredicate(&item)
else
validatedPredicate(&item);

return if (predicate_result)
item
else
@call(.always_tail, Self.next, .{self});
}
};
}

pub fn FilterContextIter(comptime BaseIter: type, comptime Context: type, comptime predicate: anytype) type {
const validatedPredicate = validatePredicateContextFn(BaseIter, Context, predicate);
return struct {
const Self = @This();

base_iter: BaseIter,
context: Context,

pub const Next = if (IterError(BaseIter)) |ES| ES!?Item(BaseIter) else ?Item(BaseIter);

pub fn next(self: *Self) Next {
const iter_has_error = comptime IterError(BaseIter) != null;
const predicate_has_error = comptime @typeInfo(@TypeOf(validatedPredicate)) == .ErrorUnion;

const maybe_item = if (iter_has_error)
try self.base_iter.next()
else
self.base_iter.next();
const item = maybe_item orelse return null;

const predicate_result = if (predicate_has_error)
try validatedPredicate(self.context, &item)
else
validatedPredicate(self.context, &item);

return if (predicate_result)
item
else
@call(.always_tail, Self.next, .{self});
}
};
}
@@ -38,22 +80,69 @@ pub fn FilterIter(comptime BaseIter: type) type {
/// an optional.
pub fn filter(
iter: anytype,
predicate: *const fn (Item(@TypeOf(iter))) bool,
) FilterIter(@TypeOf(iter)) {
return .{ .base_iter = iter, .predicate = predicate };
comptime predicate: anytype,
) FilterIter(@TypeOf(iter), validatePredicateFn(@TypeOf(iter), predicate)) {
return .{ .base_iter = iter };
}

pub fn validatePredicateFn(
comptime BaseIter: type,
comptime predicate: anytype,
) fn (*const Item(BaseIter)) switch (@typeInfo(@TypeOf(predicate))) {
.Fn => |Fn| switch (@typeInfo(Fn.return_type orelse @compileError("Predicate must return a `bool` or `!bool`"))) {
.Bool => bool,
.ErrorUnion => |EU| switch (@typeInfo(EU.payload)) {
.Bool => bool,
else => @compileError("Only `bool` or `!bool` allowed as predicate return type, found '" ++ @typeName(EU.payload) ++ "'"),
},
else => |T| @compileError("Only `bool` or `!bool` allowed as predicate return type, found '" ++ @typeName(T) ++ "'"),
},
else => |T| @compileError("Only `bool` or `!bool` allowed as predicate return type, found '" ++ @typeName(T) ++ "'"),
} {
return predicate;
}

pub fn filterContext(
iter: anytype,
context: anytype,
comptime predicate: anytype,
) FilterContextIter(
@TypeOf(iter),
@TypeOf(context),
validatePredicateContextFn(@TypeOf(iter), @TypeOf(context), predicate),
) {
return .{ .base_iter = iter, .context = context };
}

pub fn validatePredicateContextFn(
comptime BaseIter: type,
comptime Context: type,
comptime predicate: anytype,
) fn (Context, *const Item(BaseIter)) switch (@typeInfo(@TypeOf(predicate))) {
.Fn => |Fn| switch (@typeInfo(Fn.return_type orelse @compileError("Predicate must return a `bool` or `!bool`"))) {
.Bool => bool,
.ErrorUnion => |EU| switch (@typeInfo(EU.payload)) {
.Bool => bool,
else => @compileError("Only `bool` or `!bool` allowed as predicate return type, found '" ++ @typeName(EU.payload) ++ "'"),
},
else => |T| @compileError("Only `bool` or `!bool` allowed as predicate return type, found '" ++ @typeName(T) ++ "'"),
},
else => |T| @compileError("Only `bool` or `!bool` allowed as predicate return type, found '" ++ @typeName(T) ++ "'"),
} {
return predicate;
}

test "FilterIter" {
const slice: []const u32 = &.{ 1, 2, 3, 4, 5, 6, 7, 8 };
var slice_iter = sliceIter(u32, slice);
const slice_iter = sliceIter(u32, slice);

const predicates = struct {
pub fn even(x: u32) bool {
return x % 2 == 0;
pub fn even(x: *const u32) bool {
return x.* % 2 == 0;
}

pub fn big(x: u32) bool {
return x > 4;
pub fn big(x: *const u32) bool {
return x.* > 4;
}
};

@@ -66,12 +155,50 @@ test "FilterIter" {
try testing.expectEqual(@as(?u32, null), iter.next());
}

test "FilterContextIter simple" {
const slice: []const u32 = &.{ 1, 2, 3, 4, 5, 6, 7, 8 };
const slice_iter = sliceIter(u32, slice);

const predicates = struct {
pub fn dividible(divisor: u32, x: *const u32) bool {
return x.* % divisor == 0;
}
};

var iter = filterContext(slice_iter, @as(u32, 3), predicates.dividible);

try testing.expectEqual(u32, Item(@TypeOf(iter)));
try testing.expectEqual(@as(?u32, 3), iter.next());
try testing.expectEqual(@as(?u32, 6), iter.next());
try testing.expectEqual(@as(?u32, null), iter.next());
try testing.expectEqual(@as(?u32, null), iter.next());
}

test "FilterContextIter closure" {
const slice: []const u32 = &.{ 1, 2, 3, 4, 5, 6, 7, 8 };
const slice_iter = sliceIter(u32, slice);

const Closure = struct {
divisor: u32,
pub fn dividible(self: @This(), x: *const u32) bool {
return x.* % self.divisor == 0;
}
};
var iter = filterContext(slice_iter, Closure{ .divisor = 3 }, Closure.dividible);

try testing.expectEqual(u32, Item(@TypeOf(iter)));
try testing.expectEqual(@as(?u32, 3), iter.next());
try testing.expectEqual(@as(?u32, 6), iter.next());
try testing.expectEqual(@as(?u32, null), iter.next());
try testing.expectEqual(@as(?u32, null), iter.next());
}

test "FilterIter error" {
var test_iter = TestErrorIter.init(5);
const test_iter = TestErrorIter.init(5);

const even = struct {
pub fn even(x: usize) bool {
return x % 2 == 0;
pub fn even(x: *const usize) bool {
return x.* % 2 == 0;
}
}.even;

217 changes: 189 additions & 28 deletions src/filter_map_iter.zig
Original file line number Diff line number Diff line change
@@ -9,14 +9,30 @@ const sliceIter = itertools.sliceIter;
/// An iterator that uses f to both filter and map elements from iter.
///
/// See `map` for more info.
pub fn FilterMapIter(comptime BaseIter: type, comptime Dest: type) type {
pub fn FilterMapIter(comptime BaseIter: type, comptime func: anytype) type {
const Fn = @typeInfo(@TypeOf(func)).Fn;
return struct {
const Self = @This();

base_iter: BaseIter,
func: *const fn (Item(BaseIter)) ?Dest,

pub const Next = if (IterError(BaseIter)) |ES| ES!?Dest else ?Dest;
pub const Dest: type = switch (@typeInfo(Fn.return_type.?)) {
.ErrorUnion => |EU| switch (@typeInfo(EU.payload)) {
.Optional => |Optional| Optional.child,
else => @compileError("FilterMapIter: function return type must be `?T` or `!?T`, found: " ++ @typeName(EU.payload)),
},
.Optional => |Optional| Optional.child,
else => @compileError("FilterMapIter: function return type must be `?T` or `!?T`, found: " ++ @typeName(Fn.return_type.?)),
};
pub const DestES: ?type = switch (@typeInfo(Fn.return_type.?)) {
.ErrorUnion => |EU| EU.error_set,
.Optional => null,
else => @compileError("FilterMapIter: function return type must be `?T` or `!?T`, found: " ++ @typeName(Fn.return_type.?)),
};
pub const Next: type = if (IterError(BaseIter)) |ES|
(if (DestES) |DES| (ES || DES)!?Dest else ES!?Dest)
else
(if (DestES) |DES| DES!?Dest else ?Dest);

pub fn next(self: *Self) Next {
const has_error = comptime IterError(BaseIter) != null;
@@ -26,52 +42,132 @@ pub fn FilterMapIter(comptime BaseIter: type, comptime Dest: type) type {
self.base_iter.next();

const item = maybe_item orelse return null;
return if (self.func(item)) |transformed|
const maybe_transformed = if (DestES != null) try func(item) else func(item);

return if (maybe_transformed) |transformed|
transformed
else if (has_error)
try self.next()
else
self.next();
@call(.always_tail, Self.next, .{self}); // no need to `try` because the error union, if any, stays the same
}
};
}

/// Returns the destination type for a given base iterator and function type
pub fn FilterMapDestType(comptime BaseIter: type, comptime Func: type) type {
const Source = Item(BaseIter);
pub fn FilterMapContextIter(
comptime BaseIter: type,
comptime Context: type,
comptime func: anytype,
) type {
const Fn = @typeInfo(@TypeOf(func)).Fn;
return struct {
const Self = @This();

const func = switch (@typeInfo(Func)) {
.Fn => |func| func,
else => @compileError("filterMap func must be a function"),
};
base_iter: BaseIter,
context: Context,

pub const Dest: type = switch (@typeInfo(Fn.return_type.?)) {
.ErrorUnion => |EU| switch (@typeInfo(EU.payload)) {
.Optional => |Optional| Optional.child,
else => @compileError("FilterMapIter: function return type must be `?T` or `!?T`, found: " ++ @typeName(EU.payload)),
},
.Optional => |Optional| Optional.child,
else => @compileError("FilterMapIter: function return type must be `?T` or `!?T`, found: " ++ @typeName(Fn.return_type.?)),
};
pub const DestES: ?type = switch (@typeInfo(Fn.return_type.?)) {
.ErrorUnion => |EU| EU.error_set,
.Optional => null,
else => @compileError("FilterMapIter: function return type must be `?T` or `!?T`, found: " ++ @typeName(Fn.return_type.?)),
};
pub const Next: type = if (IterError(BaseIter)) |ES|
(if (DestES) |DES| (ES || DES)!?Dest else ES!?Dest)
else
(if (DestES) |DES| DES!?Dest else ?Dest);

pub fn next(self: *Self) Next {
const has_error = comptime IterError(BaseIter) != null;
const maybe_item = if (has_error)
try self.base_iter.next()
else
self.base_iter.next();

if (func.params.len != 1)
@compileError("filterMap func must be a unary function");
const item = maybe_item orelse return null;

if (func.params[0].type.? != Source)
@compileError("filterMap func's argument must be iter's item type");
const maybe_transformed = if (DestES != null)
try func(self.context, item)
else
func(self.context, item);

return switch (@typeInfo(func.return_type.?)) {
.Optional => |optional| optional.child,
else => @compileError("filterMap func's return type must be an optional"),
return if (maybe_transformed) |transformed|
transformed
else
@call(.always_tail, Self.next, .{self}); // no need to `try` because the error union, if any, stays the same
}
};
}

/// Creates an iterator that both filters and maps.
///
/// The returned iterator yields only the values for which the supplied function does not return null.
///
/// filterMap can be used to make chains of filter and map more concise.
/// `filterMap` can be used to make chains of filter and map more concise.
pub fn filterMap(
iter: anytype,
func: anytype,
) FilterMapIter(@TypeOf(iter), FilterMapDestType(@TypeOf(iter), @TypeOf(func))) {
return .{ .base_iter = iter, .func = func };
comptime func: anytype,
) FilterMapIter(@TypeOf(iter), validateFilterMapFn(Item(@TypeOf(iter)), func)) {
return .{ .base_iter = iter };
}

pub fn validateFilterMapFn(
comptime Source: type,
comptime func: anytype,
) fn (Source) switch (@typeInfo(@typeInfo(@TypeOf(func)).Fn.return_type.?)) {
.Optional => |Optional| ?Optional.child,
.ErrorUnion => |EU| switch (@typeInfo(EU.payload)) {
.Optional => |Optional| EU.error_set!?Optional.child,
else => @compileError("[filterMap] function return type must be `?T` or `!?T`, found: " ++ @typeName(EU.payload)),
},
else => @compileError("[filterMap] function return type must be `?T` or `!?T`, found: " ++ @typeName(@typeInfo(@TypeOf(func)).Fn.return_type.?)),
} {
return func;
}

/// Creates an iterator that both filters and maps with a context.
///
/// The returned iterator yields only the values for which the supplied function does not return null.
///
/// `filterMapContext` can be used to make chains of filter and map with context more concise.
///
/// The context is passed as the first argument to the function. Context is useful for
/// when you want to pass in a function that behaves like a closure.
pub fn filterMapContext(
iter: anytype,
context: anytype,
comptime func: anytype,
) FilterMapContextIter(
@TypeOf(iter),
@TypeOf(context),
validateFilterMapContextFn(Item(@TypeOf(iter)), @TypeOf(context), func),
) {
return .{ .base_iter = iter, .context = context };
}

pub fn validateFilterMapContextFn(
comptime Source: type,
comptime Context: type,
comptime func: anytype,
) fn (Context, Source) switch (@typeInfo(@typeInfo(@TypeOf(func)).Fn.return_type.?)) {
.Optional => |Optional| ?Optional.child,
.ErrorUnion => |EU| switch (@typeInfo(EU.payload)) {
.Optional => |Optional| EU.error_set!?Optional.child,
else => @compileError("[filterMapContext] function return type must be `?T` or `!?T`, found: " ++ @typeName(EU.payload)),
},
else => @compileError("[filterMapContext] function return type must be `?T` or `!?T`, found: " ++ @typeName(@typeInfo(@TypeOf(func)).Fn.return_type.?)),
} {
return func;
}

test "FilterMapIter" {
const slice: []const u32 = &.{ 1, 2, 3, 4, 5, 6, 7, 8 };
var slice_iter = sliceIter(u32, slice);
const slice_iter = sliceIter(u32, slice);

const func = struct {
pub fn func(x: u32) ?u64 {
@@ -93,8 +189,56 @@ test "FilterMapIter" {
try testing.expectEqual(@as(?u64, null), iter.next());
}

test "MapIter error" {
var test_iter = TestErrorIter.init(3);
test "FilterMapContextIter simple" {
const slice: []const u32 = &.{ 1, 2, 3, 4, 5, 6, 7, 8 };
const slice_iter = sliceIter(u32, slice);

const func = struct {
pub fn func(context: u32, x: u32) ?u64 {
return if (x % context == 0)
x / context
else
null;
}
}.func;

var iter = filterMapContext(slice_iter, @as(u32, 2), func);

try testing.expectEqual(u64, Item(@TypeOf(iter)));
try testing.expectEqual(@as(?u64, 1), iter.next());
try testing.expectEqual(@as(?u64, 2), iter.next());
try testing.expectEqual(@as(?u64, 3), iter.next());
try testing.expectEqual(@as(?u64, 4), iter.next());
try testing.expectEqual(@as(?u64, null), iter.next());
try testing.expectEqual(@as(?u64, null), iter.next());
}

test "FilterMapContextIter closure" {
const slice: []const u32 = &.{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };
const slice_iter = sliceIter(u32, slice);

const Closure = struct {
divider: u32,
pub fn func(self: @This(), x: u32) ?u64 {
return if (x % self.divider == 0)
x / self.divider
else
null;
}
};

var iter = filterMapContext(slice_iter, Closure{ .divider = 3 }, Closure.func);

try testing.expectEqual(u64, Item(@TypeOf(iter)));
try testing.expectEqual(@as(?u64, 1), iter.next());
try testing.expectEqual(@as(?u64, 2), iter.next());
try testing.expectEqual(@as(?u64, 3), iter.next());
try testing.expectEqual(@as(?u64, null), iter.next());
try testing.expectEqual(@as(?u64, null), iter.next());
}

test "FilterMapIter error" {
const test_iter = TestErrorIter.init(3);

const func = struct {
pub fn func(x: usize) ?u64 {
@@ -113,6 +257,23 @@ test "MapIter error" {
try testing.expectError(error.TestErrorIterError, iter.next());
}

test "FilterMapIter error union" {
const test_iter = TestErrorIter.init(3);

const doubleEven = struct {
pub fn doubleEven(x: usize) error{Overflow}!?u64 {
return if (x % 2 == 0) try std.math.mul(u64, 2, x) else null;
}
}.doubleEven;

var iter = filterMap(test_iter, doubleEven);

try testing.expectEqual(u64, Item(@TypeOf(iter)));
try testing.expectEqual(@as(?u64, 0), try iter.next());
try testing.expectEqual(@as(?u64, 4), try iter.next());
try testing.expectError(error.TestErrorIterError, iter.next());
}

const TestErrorIter = struct {
const Self = @This();

103 changes: 103 additions & 0 deletions src/find.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
const it = @import("main.zig");
const Item = it.Item;
const IterError = it.IterError;

/// Returns the return type of the `find`/`findContext` function.
pub fn Find(comptime Iter: type, comptime PredicateES: ?type) type {
return if (IterError(Iter)) |ES|
(if (PredicateES) |PES| (ES || PES)!?Item(Iter) else ES!?Item(Iter))
else
(if (PredicateES) |PES| PES!?Item(Iter) else ?Item(Iter));
}

/// Searches for an element in an iterator that satisfies the given predicate.
///
/// Find is short-circuiting, i.e. it will stop processing as soon as the predicate returns true.
///
/// Note that `find(iter, f)` is equivalent to `filter(iter, f).next()`.
///
/// You can still use the iterator after calling this function.
pub fn find(
iter: anytype,
comptime predicate: anytype,
) Find(
@typeInfo(@TypeOf(iter)).Pointer.child,
switch (@typeInfo(@TypeOf(predicate))) {
.Fn => |Fn| switch (@typeInfo(Fn.return_type orelse @compileError("predicate must be a function that returns a `bool` or `!bool`"))) {
.Bool => null,
.ErrorUnion => |EU| EU.error_set,
else => @compileError("predicate must be a function that returns a `bool` or `!bool`"),
},
else => @compileError("predicate must be a function that returns a `bool` or `!bool`"),
},
) {
const validatedPredicate = it.validatePredicateFn(@typeInfo(@TypeOf(iter)).Pointer.child, predicate);
const iter_has_error = comptime IterError(@typeInfo(@TypeOf(iter)).Pointer.child) != null;
const predicate_has_error = comptime @typeInfo(@TypeOf(validatedPredicate)) == .ErrorUnion;
while (if (iter_has_error) try iter.next() else iter.next()) |item| {
const predicate_result: bool = if (predicate_has_error)
try validatedPredicate(&item)
else
validatedPredicate(&item);
if (predicate_result) return item;
}
return null;
}

/// Searches for an element in an iterator that satisfies the given predicate with context.
///
/// Find is short-circuiting, i.e. it will stop processing as soon as the predicate returns true.
///
/// Note that `findContext(iter, c, f)` is equivalent to `filterContext(iter, c, f).next()`.
///
/// You can still use the iterator after calling this function.
pub fn findContext(
iter: anytype,
context: anytype,
comptime predicate: anytype,
) Find(
@typeInfo(@TypeOf(iter)).Pointer.child,
switch (@typeInfo(@TypeOf(predicate))) {
.Fn => |Fn| switch (@typeInfo(Fn.return_type orelse @compileError("predicate must be a function that returns a `bool` or `!bool`"))) {
.Bool => null,
.ErrorUnion => |EU| EU.error_set,
else => @compileError("predicate must be a function that returns a `bool` or `!bool`"),
},
else => @compileError("predicate must be a function that returns a `bool` or `!bool`"),
},
) {
const validatedPredicate = it.validatePredicateContextFn(
@typeInfo(@TypeOf(iter)).Pointer.child,
@TypeOf(context),
predicate,
);
const iter_has_error = comptime IterError(@typeInfo(@TypeOf(iter)).Pointer.child) != null;
const predicate_has_error = comptime @typeInfo(@TypeOf(validatedPredicate)) == .ErrorUnion;
while (if (iter_has_error) try iter.next() else iter.next()) |item| {
const predicate_result: bool = if (predicate_has_error)
try validatedPredicate(context, &item)
else
validatedPredicate(context, &item);
if (predicate_result) return item;
}
return null;
}

const testing = @import("std").testing;

test "find 'o'" {
const slice: []const u8 = "Hello, world!";
var iter = it.sliceIter(u8, slice);

const predicate = struct {
fn predicate(item: *const u8) bool {
return item.* == 'o';
}
}.predicate;
try testing.expectEqual(@as(?u8, 'o'), find(&iter, predicate));
try testing.expectEqual(@as(usize, 5), iter.index);
try testing.expectEqual(@as(?u8, ','), iter.next());
try testing.expectEqual(@as(?u8, 'o'), find(&iter, predicate));
try testing.expectEqual(@as(usize, 9), iter.index);
try testing.expectEqual(@as(?u8, null), find(&iter, predicate));
}
33 changes: 15 additions & 18 deletions src/flatten_iter.zig
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ pub fn FlattenIter(comptime BaseIters: type) type {
base_iters: BaseIters,
current_iter: ?Item(BaseIters),

const Next = if (IterError(BaseIters)) |ESB|
pub const Next = if (IterError(BaseIters)) |ESB|
if (IterError(Item(BaseIters))) |ESI|
(ESB || ESI)!?Item(Item(BaseIters))
else
@@ -31,22 +31,19 @@ pub fn FlattenIter(comptime BaseIters: type) type {
const base_has_error = comptime IterError(BaseIters) != null;
const item_has_error = comptime IterError(Item(BaseIters)) != null;

if (self.current_iter) |*iter| {
const maybe_item = if (item_has_error)
try iter.next()
else
iter.next();
if (maybe_item) |item|
return item;
const iter = &(self.current_iter orelse return null);
const maybe_item = if (item_has_error)
try iter.next()
else
iter.next();
if (maybe_item) |item|
return item;

self.current_iter = if (base_has_error)
try self.base_iters.next()
else
self.base_iters.next();
return self.next();
} else {
return null;
}
self.current_iter = if (base_has_error)
try self.base_iters.next()
else
self.base_iters.next();
return @call(.always_tail, Self.next, .{self});
}
};
}
@@ -68,11 +65,11 @@ pub fn flatten(iter: anytype) FlattenIter(@TypeOf(iter)) {
test "Flatten" {
const func = struct {
fn func(x: u32) RangeIter(u32) {
return itertools.range(u32, 0, x);
return itertools.range(@as(u32, 0), x);
}
}.func;

var iters = itertools.map(itertools.range(u32, 0, 5), func);
const iters = itertools.map(itertools.range(@as(u32, 0), 5), func);
var iter = flatten(iters);

try testing.expectEqual(u32, Item(@TypeOf(iter)));
119 changes: 119 additions & 0 deletions src/fold.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
const std = @import("std");
const testing = std.testing;

const itertools = @import("main.zig");
const Item = itertools.Item;
const IterError = itertools.IterError;
const sliceIter = itertools.sliceIter;

/// Returns the return type to be used in `fold`
pub fn Fold(comptime Iter: type, comptime T: type) type {
return if (IterError(Iter)) |ES| ES!T else T;
}

/// Applies a binary operator between all items in iter with an initial element.
pub fn fold(
iter: anytype,
init: anytype,
comptime func: fn (@TypeOf(init), Item(@TypeOf(iter))) @TypeOf(init),
) Fold(@TypeOf(iter), @TypeOf(init)) {
const has_error = comptime IterError(@TypeOf(iter)) != null;
var mut_iter = iter;
var res = init;
while (if (has_error) try mut_iter.next() else mut_iter.next()) |item| {
res = func(res, item);
}
return res;
}

/// Applies a binary operator between all items in iter with an initial element and a context.
///
/// The context is passed as the first argument to the function. Context is useful for
/// when you want to pass in a function that behaves like a closure.
pub fn foldContext(
iter: anytype,
context: anytype,
init: anytype,
comptime func: fn (@TypeOf(context), @TypeOf(init), Item(@TypeOf(iter))) @TypeOf(init),
) Fold(@TypeOf(iter), @TypeOf(init)) {
const has_error = comptime IterError(@TypeOf(iter)) != null;
var mut_iter = iter;
var res = init;
while (if (has_error) try mut_iter.next() else mut_iter.next()) |item| {
res = func(context, res, item);
}
return res;
}

test "fold" {
const slice: []const u32 = &.{ 1, 2, 3, 4 };
const iter = sliceIter(u32, slice);

const add = struct {
fn add(x: u64, y: u32) u64 {
return x + y;
}
}.add;

try testing.expectEqual(@as(u64, 10), fold(iter, @as(u64, 0), add));
}

test "fold empty" {
const slice: []const u32 = &.{};
const iter = sliceIter(u32, slice);

const add = struct {
fn add(x: u64, y: u32) u64 {
return x + y;
}
}.add;

try testing.expectEqual(@as(u64, 0), fold(iter, @as(u64, 0), add));
}

test "fold context" {
const slice: []const u32 = &.{ 1, 2, 3, 4 };
const iter = sliceIter(u32, slice);

const add = struct {
fn add(context: u64, x: u64, y: u32) u64 {
return context + x + y;
}
}.add;

try testing.expectEqual(@as(u64, 30), foldContext(
iter,
@as(u64, 5),
@as(u64, 0),
add,
));
}

test "fold error" {
const iter = TestErrorIter.init(5);

const add = struct {
fn add(x: u64, y: usize) u64 {
return x + y;
}
}.add;

try testing.expectError(error.TestErrorIterError, fold(iter, @as(u64, 0), add));
}

const TestErrorIter = struct {
const Self = @This();

counter: usize = 0,
until_err: usize,

pub fn init(until_err: usize) Self {
return .{ .until_err = until_err };
}

pub fn next(self: *Self) !?usize {
if (self.counter >= self.until_err) return error.TestErrorIterError;
self.counter += 1;
return self.counter - 1;
}
};
50 changes: 50 additions & 0 deletions src/for_each.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const itertools = @import("main.zig");
const Item = itertools.Item;
const IterError = itertools.IterError;
const sliceIter = itertools.sliceIter;

pub fn ForEach(comptime Iter: type) type {
return if (IterError(Iter)) |ES| ES!void else void;
}

pub fn forEach(
iter: anytype,
comptime callback: fn (Item(@TypeOf(iter))) void,
) ForEach(@TypeOf(iter)) {
const has_error = comptime IterError(@TypeOf(iter)) != null;
var mut_iter = iter;
while (if (has_error) try mut_iter.next() else mut_iter.next()) |item| {
callback(item);
}
}

pub fn forEachContext(
iter: anytype,
context: anytype,
comptime callback: fn (@TypeOf(context), Item(@TypeOf(iter))) void,
) ForEach(@TypeOf(iter)) {
const has_error = comptime IterError(@TypeOf(iter)) != null;
var mut_iter = iter;
while (if (has_error) try mut_iter.next() else mut_iter.next()) |item| {
callback(context, item);
}
}

const testing = @import("std").testing;

test "forEach" {
const iter = sliceIter(u8, &.{ 1, 2, 3, 4, 5 });
var sum: u8 = 0;

forEachContext(
iter,
&sum,
struct {
fn callback(total: *u8, item: u8) void {
total.* += item;
}
}.callback,
);

try testing.expect(sum == 15);
}
225 changes: 225 additions & 0 deletions src/iter_methods.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
const it = @import("main.zig");
const Item = it.Item;
const IterError = it.IterError;

pub fn iterMethods(iter: anytype) IterMethods(@TypeOf(iter)) {
return .{ .iter = iter };
}

pub fn IterMethods(comptime Iter: type) type {
return struct {
const Self = @This();
iter: Iter,

pub const Next = if (IterError(Iter)) |ES| ES!?Item(Iter) else ?Item(Iter);

pub fn next(self: *Self) Next {
return self.iter.next();
}

pub fn nth(self: *Self, n: usize) it.Nth(Iter) {
return it.nth(&self.iter, n);
}

// pub fn peekable

pub fn map(self: Self, comptime func: anytype) IterMethods(it.MapIter(
Iter,
it.validateMapFn(Item(Iter), func),
)) {
return .{ .iter = it.map(self.iter, func) };
}

pub fn mapContext(self: Self, context: anytype, comptime func: anytype) IterMethods(it.MapContextIter(
Iter,
it.validateMapContextFn(Item(Iter), @TypeOf(context), func),
)) {
return .{ .iter = it.mapContext(self.iter, context, func) };
}

pub fn filter(self: Self, comptime predicate: anytype) IterMethods(it.FilterIter(
Iter,
it.validatePredicateFn(Iter, predicate),
)) {
return .{ .iter = it.filter(self.iter, predicate) };
}

pub fn filterContext(self: Self, context: anytype, comptime predicate: anytype) IterMethods(it.FilterContextIter(
Iter,
@TypeOf(context),
it.validatePredicateContextFn(Iter, @TypeOf(context), predicate),
)) {
return .{ .iter = it.filterContext(self.iter, context, predicate) };
}

pub fn filterMap(self: Self, comptime func: anytype) IterMethods(it.FilterMapIter(
Iter,
it.validateFilterMapFn(Item(Iter), func),
)) {
return .{ .iter = it.filterMap(self.iter, func) };
}

pub fn filterMapContext(self: Self, context: anytype, comptime func: anytype) IterMethods(it.FilterMapContextIter(
Iter,
@TypeOf(context),
it.validateFilterMapContextFn(Item(Iter), @TypeOf(context), func),
)) {
return .{ .iter = it.filterMapContext(self.iter, context, func) };
}

pub inline fn find(self: *Self, comptime predicate: anytype) it.Find(
Iter,
switch (@typeInfo(@TypeOf(predicate))) {
.Fn => |Fn| switch (@typeInfo(Fn.return_type orelse @compileError("Predicate must return a `bool` or `!bool`"))) {
.Bool => bool,
.ErrorUnion => |EU| switch (@typeInfo(EU.payload)) {
.Bool => bool,
else => @compileError("Only `bool` or `!bool` allowed as predicate return type, found '" ++ @typeName(EU.payload) ++ "'"),
},
else => |T| @compileError("Only `bool` or `!bool` allowed as predicate return type, found '" ++ @typeName(T) ++ "'"),
},
else => |T| @compileError("Only `bool` or `!bool` allowed as predicate return type, found '" ++ @typeName(T) ++ "'"),
},
) {
return it.find(&self.iter, predicate);
}

pub inline fn findContext(self: *Self, context: anytype, comptime predicate: anytype) it.Find(
Iter,
@TypeOf(context),
switch (@typeInfo(@TypeOf(predicate))) {
.Fn => |Fn| switch (@typeInfo(Fn.return_type orelse @compileError("Predicate must return a `bool` or `!bool`"))) {
.Bool => bool,
.ErrorUnion => |EU| switch (@typeInfo(EU.payload)) {
.Bool => bool,
else => @compileError("Only `bool` or `!bool` allowed as predicate return type, found '" ++ @typeName(EU.payload) ++ "'"),
},
else => |T| @compileError("Only `bool` or `!bool` allowed as predicate return type, found '" ++ @typeName(T) ++ "'"),
},
else => |T| @compileError("Only `bool` or `!bool` allowed as predicate return type, found '" ++ @typeName(T) ++ "'"),
},
) {
return it.findContext(&self.iter, context, predicate);
}

pub fn chain(self: Self, other: anytype) IterMethods(it.ChainIter(Iter, @TypeOf(other))) {
return .{ .iter = it.chain(self.iter, other) };
}

pub fn zip(self: Self, other: anytype) IterMethods(it.ZipIter(struct { Iter, @TypeOf(other) })) {
return .{ .iter = it.zip(.{ self.iter, other }) };
}

pub fn skip(self: Self, to_skip: usize) IterMethods(it.SkipIter(Iter)) {
return .{ .iter = it.skip(self.iter, to_skip) };
}

pub fn skipWhile(
self: Self,
comptime predicate: fn (*const Item(Iter)) bool,
) IterMethods(it.SkipWhileIter(Iter, predicate)) {
return .{ .iter = it.skipWhile(self.iter, predicate) };
}

pub fn skipWhileContext(
self: Self,
context: anytype,
comptime predicate: fn (@TypeOf(context), *const Item(Iter)) bool,
) IterMethods(it.SkipWhileContextIter(Iter, @TypeOf(context), predicate)) {
return .{ .iter = it.skipWhileContext(self.iter, context, predicate) };
}

pub fn take(self: Self, to_take: usize) IterMethods(it.TakeIter(Iter)) {
return .{ .iter = it.take(self.iter, to_take) };
}

pub fn takeWhile(
self: Self,
comptime predicate: fn (Item(Iter)) bool,
) IterMethods(it.TakeWhileIter(Iter, predicate)) {
return .{ .iter = it.takeWhile(self.iter, predicate) };
}

pub fn takeWhileContext(
self: Self,
context: anytype,
comptime predicate: fn (@TypeOf(context), Item(Iter)) bool,
) IterMethods(it.TakeWhileContextIter(Iter, @TypeOf(context), predicate)) {
return .{ .iter = it.takeWhileContext(self.iter, context, predicate) };
}

pub inline fn fold(
self: Self,
init: anytype,
comptime func: fn (@TypeOf(init), Item(Iter)) @TypeOf(init),
) it.Fold(Iter, @TypeOf(init)) {
return it.fold(self.iter, init, func);
}

pub inline fn foldContext(
self: Self,
context: anytype,
init: anytype,
comptime func: fn (@TypeOf(context), @TypeOf(init), Item(Iter)) @TypeOf(init),
) it.Fold(Iter, @TypeOf(context), @TypeOf(init)) {
return it.foldContext(self.iter, context, init, func);
}

pub inline fn reduce(
self: Self,
comptime func: fn (Item(Iter), Item(Iter)) Item(Iter),
) it.Reduce(Iter) {
return it.reduce(self.iter, func);
}

pub inline fn reduceContext(
self: Self,
context: anytype,
comptime func: fn (@TypeOf(context), Item(Iter), Item(Iter)) Item(Iter),
) it.Reduce(Iter) {
return it.reduceContext(self.iter, context, func);
}

pub inline fn count(self: Self) usize {
return it.count(self.iter);
}

pub inline fn sum(self: Self, comptime Dest: ?type) it.Sum(Iter, Dest) {
return it.sum(Dest, self.iter);
}

pub inline fn product(self: Self, comptime Dest: ?type) it.Product(Iter, Dest) {
return it.product(Dest, self.iter);
}

pub inline fn forEach(
self: Self,
comptime func: fn (Item(Iter)) void,
) void {
return it.forEach(self.iter, func);
}

pub inline fn forEachContext(
self: Self,
context: anytype,
comptime func: fn (@TypeOf(context), Item(Iter)) void,
) void {
return it.forEachContext(self.iter, context, func);
}
};
}

const testing = @import("std").testing;

test "IterMethods" {
var iter = iterMethods(it.sliceIter(u8, &.{ 1, 2, 3 }))
.map(struct {
fn double(item: u8) u9 {
return item * 2;
}
}.double);
try testing.expectEqual(@as(?u9, 2), iter.next());
try testing.expectEqual(@as(?u9, 4), iter.next());
try testing.expectEqual(@as(?u9, 6), iter.next());
try testing.expectEqual(@as(?u9, null), iter.next());
}
54 changes: 54 additions & 0 deletions src/lines_iter.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const std = @import("std");
const SplitIterator = std.mem.SplitIterator;

pub const LinesIter = struct {
split_iter: SplitIterator(u8, .scalar),

pub fn next(self: *LinesIter) ?[]const u8 {
const start = self.split_iter.index orelse return null;
const line_non_inclusive = self.split_iter.next().?;
const end = self.split_iter.index orelse start + line_non_inclusive.len;
const line = self.split_iter.buffer[start..end];
const line_stripped = blk: {
const line_without_lf = strip_suffix(line, "\n") orelse break :blk line;
const line_without_crlf = strip_suffix(line_without_lf, "\r") orelse break :blk line_without_lf;
break :blk line_without_crlf;
};

return line_stripped;
}

fn strip_suffix(line: []const u8, suffix: []const u8) ?[]const u8 {
if (line.len < suffix.len) return null;
if (!std.mem.eql(u8, line[line.len - suffix.len ..], suffix)) return null;
return line[0 .. line.len - suffix.len];
}
};

pub fn lines(input: []const u8) LinesIter {
return .{ .split_iter = std.mem.splitScalar(u8, input, '\n') };
}

const testing = std.testing;

test "basic usage" {
const text = "foo\r\nbar\n\nbaz\r";
var text_lines = lines(text);

try testing.expectEqualStrings("foo", text_lines.next().?);
try testing.expectEqualStrings("bar", text_lines.next().?);
try testing.expectEqualStrings("", text_lines.next().?);
try testing.expectEqualStrings("baz\r", text_lines.next().?);
try testing.expect(text_lines.next() == null);
}

test "the final line doesn't require any ending" {
const text = "foo\r\nbar\n\nbaz";
var text_lines = lines(text);

try testing.expectEqualStrings("foo", text_lines.next().?);
try testing.expectEqualStrings("bar", text_lines.next().?);
try testing.expectEqualStrings("", text_lines.next().?);
try testing.expectEqualStrings("baz", text_lines.next().?);
try testing.expect(text_lines.next() == null);
}
87 changes: 80 additions & 7 deletions src/main.zig
Original file line number Diff line number Diff line change
@@ -21,25 +21,42 @@ pub const empty = empty_iter.empty;
pub const EmptyIter = empty_iter.EmptyIter;
const map_iter = @import("map_iter.zig");
pub const map = map_iter.map;
pub const validateMapFn = map_iter.validateMapFn;
pub const mapContext = map_iter.mapContext;
pub const validateMapContextFn = map_iter.validateMapContextFn;
pub const MapIter = map_iter.MapIter;
pub const MapDestType = map_iter.MapDestType;
pub const MapContextIter = map_iter.MapContextIter;
const filter_iter = @import("filter_iter.zig");
pub const filter = filter_iter.filter;
pub const validatePredicateFn = filter_iter.validatePredicateFn;
pub const filterContext = filter_iter.filterContext;
pub const validatePredicateContextFn = filter_iter.validatePredicateContextFn;
pub const FilterIter = filter_iter.FilterIter;
pub const FilterContextIter = filter_iter.FilterContextIter;
const to_slice = @import("to_slice.zig");
pub const ToSlice = to_slice.ToSlice;
pub const toSlice = to_slice.toSlice;
pub const ToSliceAlloc = to_slice.ToSliceAlloc;
pub const toSliceAlloc = to_slice.toSliceAlloc;
const fold_namespace = @import("fold.zig");
pub const fold = fold_namespace.fold;
pub const foldContext = fold_namespace.foldContext;
pub const Fold = fold_namespace.Fold;
const reduce_namespace = @import("reduce.zig");
pub const reduce = reduce_namespace.reduce;
pub const reduceContext = reduce_namespace.reduceContext;
pub const Reduce = reduce_namespace.Reduce;
pub const reduce1 = reduce_namespace.reduce1;
pub const Reduce1 = reduce_namespace.Reduce1;
const filter_map_iter = @import("filter_map_iter.zig");
pub const filterMap = filter_map_iter.filterMap;
pub const validateFilterMapFn = filter_map_iter.validateFilterMapFn;
pub const filterMapContext = filter_map_iter.filterMapContext;
pub const validateFilterMapContextFn = filter_map_iter.validateFilterMapContextFn;
pub const FilterMapIter = filter_map_iter.FilterMapIter;
pub const FilterMapDestType = filter_map_iter.FilterMapDestType;
pub const FilterMapContextIter = filter_map_iter.FilterMapContextIter;
const find_namespace = @import("find.zig");
pub const find = find_namespace.find;
pub const findContext = find_namespace.findContext;
pub const Find = find_namespace.Find;
const flatten_iter = @import("flatten_iter.zig");
pub const flatten = flatten_iter.flatten;
pub const FlattenIter = flatten_iter.FlattenIter;
@@ -57,7 +74,9 @@ pub const skip = skip_iter.skip;
pub const SkipIter = skip_iter.SkipIter;
const skip_while_iter = @import("skip_while_iter.zig");
pub const skipWhile = skip_while_iter.skipWhile;
pub const skipWhileContext = skip_while_iter.skipWhileContext;
pub const SkipWhileIter = skip_while_iter.SkipWhileIter;
pub const SkipWhileContextIter = skip_while_iter.SkipWhileContextIter;
const successors_iter = @import("successors_iter.zig");
pub const successors = successors_iter.successors;
pub const SuccessorsIter = successors_iter.SuccessorsIter;
@@ -66,15 +85,59 @@ pub const take = take_iter.take;
pub const TakeIter = take_iter.TakeIter;
const take_while_iter = @import("take_while_iter.zig");
pub const takeWhile = take_while_iter.takeWhile;
pub const takeWhileContext = take_while_iter.takeWhileContext;
pub const TakeWhileIter = take_while_iter.TakeWhileIter;
pub const TakeWhileContextIter = take_while_iter.TakeWhileContextIter;
const zip_iter = @import("zip_iter.zig");
pub const zip = zip_iter.zip;
pub const ZipIter = zip_iter.ZipIter;
const sum_namespace = @import("sum.zig");
pub const sum = sum_namespace.sum;
pub const Sum = sum_namespace.Sum;
const product_namespace = @import("product.zig");
pub const product = product_namespace.product;
pub const Product = product_namespace.Product;
const for_each = @import("for_each.zig");
pub const forEach = for_each.forEach;
pub const forEachContext = for_each.forEachContext;
const nth_namespace = @import("nth.zig");
pub const nth = nth_namespace.nth;
pub const Nth = nth_namespace.Nth;
const lines_iter = @import("lines_iter.zig");
pub const lines = lines_iter.lines;
pub const LinesIter = lines_iter.LinesIter;
const iter_methods = @import("iter_methods.zig");
pub const iterMethods = iter_methods.iterMethods;
pub const IterMethods = iter_methods.IterMethods;

test {
testing.refAllDeclsRecursive(@This());
}

pub fn count(iter: anytype) callconv(if (iterHasLen(@TypeOf(iter))) .Inline else .Unspecified) usize {
if (comptime iterHasLen(@TypeOf(iter))) {
return iter.len();
}
var result: usize = 0;
var mut_iter = iter;
while (mut_iter.next()) |_| {
result += 1;
}
return result;
}

fn iterHasLen(comptime Iter: type) bool {
if (@hasDecl(Iter, "len")) {
switch (@typeInfo(@TypeOf(Iter.len))) {
.Fn => |Fn| if (Fn.params.len == 1 and (Fn.params[0].type == Iter or Fn.params[0].type == *Iter or Fn.params[0].type == *const Iter) and Fn.return_type == usize) {
return true;
},
else => {},
}
}
return false;
}

/// Returns the type of item the iterator holds
///
/// # example
@@ -117,7 +180,7 @@ pub fn Item(comptime Iter: type) type {
}

test "Item" {
var iter = std.mem.tokenize(u8, "hi there world", " ");
const iter = std.mem.tokenize(u8, "hi there world", " ");
try testing.expectEqual([]const u8, Item(@TypeOf(iter)));
}

@@ -172,7 +235,7 @@ pub fn IterError(comptime Iter: type) ?type {
}

test "IterError" {
var iter = std.mem.tokenize(u8, "hi there world", " ");
const iter = std.mem.tokenize(u8, "hi there world", " ");
try testing.expectEqual(@as(?type, null), IterError(@TypeOf(iter)));

const dir = testing.tmpIterableDir(.{}).iterable_dir;
@@ -188,7 +251,7 @@ test "IterError" {

test "tokenize" {
const string = "hi there world";
var tokens = std.mem.tokenizeAny(u8, string, " ");
const tokens = std.mem.tokenizeAny(u8, string, " ");

const length = struct {
fn length(x: []const u8) usize {
@@ -202,3 +265,13 @@ test "tokenize" {

try testing.expectEqualSlices(usize, &.{ 2, 5, 5 }, lengths);
}

test "count" {
const iter = std.mem.tokenize(u8, "hi there world", " ");
try testing.expectEqual(@as(usize, 3), count(iter));
}

test "count optimal" {
const iter = sliceIter(u8, "hi there world");
try testing.expectEqual(@as(usize, 14), count(iter));
}
172 changes: 144 additions & 28 deletions src/map_iter.zig
Original file line number Diff line number Diff line change
@@ -9,62 +9,127 @@ const sliceIter = itertools.sliceIter;
/// Iter type for mapping another iterator with a function
///
/// See `map` for more info.
pub fn MapIter(comptime BaseIter: type, comptime Dest: type) type {
pub fn MapIter(comptime BaseIter: type, comptime func: anytype) type {
const Fn = @typeInfo(@TypeOf(func)).Fn;
return struct {
const Self = @This();

base_iter: BaseIter,
func: *const fn (Item(BaseIter)) Dest,

pub const Next = if (IterError(BaseIter)) |ES| ES!?Dest else ?Dest;
pub const Dest: type = switch (@typeInfo(Fn.return_type.?)) {
.ErrorUnion => |EU| EU.payload,
else => Fn.return_type.?,
};
pub const DestES: ?type = switch (@typeInfo(Fn.return_type.?)) {
.ErrorUnion => |EU| EU.error_set,
else => null,
};
pub const Next: type = if (IterError(BaseIter)) |ES|
(if (DestES) |DES| (ES || DES)!?Dest else ES!?Dest)
else
(if (DestES) |DES| DES!?Dest else ?Dest);

pub fn next(self: *Self) Next {
const has_error = comptime IterError(BaseIter) != null;
const maybe_item = if (has_error)
const maybe_item = if (IterError(BaseIter) != null)
try self.base_iter.next()
else
self.base_iter.next();

return if (maybe_item) |item|
self.func(item)
(if (DestES != null) try func(item) else func(item))
else
null;
}
};
}

/// Returns the destination type for a given base iterator and function type
pub fn MapDestType(comptime BaseIter: type, comptime Func: type) type {
const Source = Item(BaseIter);

const func = switch (@typeInfo(Func)) {
.Fn => |func| func,
else => @compileError("map func must be a function"),
};
/// Iter type for mapping another iterator with a function and context
///
/// See `mapContext` for more info.
pub fn MapContextIter(
comptime BaseIter: type,
comptime func: anytype,
) type {
const Fn = @typeInfo(@TypeOf(func)).Fn;
const Context = Fn.params[0].type.?;
return struct {
const Self = @This();

if (func.params.len != 1)
@compileError("map func must be a unary function");
base_iter: BaseIter,
context: Context,

pub const Dest: type = switch (@typeInfo(Fn.return_type.?)) {
.ErrorUnion => |EU| EU.payload,
else => Fn.return_type.?,
};
pub const DestES: ?type = switch (@typeInfo(Fn.return_type.?)) {
.ErrorUnion => |EU| EU.error_set,
else => null,
};
pub const Next: type = if (IterError(BaseIter)) |ES|
(if (DestES) |DES| (ES || DES)!?Dest else ES!?Dest)
else
(if (DestES) |DES| DES!?Dest else ?Dest);

if (func.params[0].type.? != Source)
@compileError("map func's argument must be iter's item type");
pub fn next(self: *Self) Next {
const maybe_item = if (IterError(BaseIter) != null)
try self.base_iter.next()
else
self.base_iter.next();

return func.return_type.?;
return if (maybe_item) |item|
(if (DestES != null) try func(self.context, item) else func(self.context, item))
else
null;
}
};
}

/// Returns a new iterator which maps items in iter using func as a function.
///
/// iter must be an iterator, meaning it has to be a type containing a next method which returns
/// an optional. func must be a unary function for which it's first argument type is the iterator's
/// item type.
pub fn map(
pub fn map(iter: anytype, comptime func: anytype) MapIter(
@TypeOf(iter),
validateMapFn(Item(@TypeOf(iter)), func),
) {
return .{ .base_iter = iter };
}

pub fn validateMapFn(
comptime Source: type,
comptime func: anytype,
) fn (Source) @typeInfo(@TypeOf(func)).Fn.return_type.? {
return func;
}

/// Returns a new iterator which maps items in iter using func as a function and context as the
/// first argument to func.
///
/// Context is useful for when you want to pass in a function that behaves like a closure.
pub fn mapContext(
iter: anytype,
func: anytype,
) MapIter(@TypeOf(iter), MapDestType(@TypeOf(iter), @TypeOf(func))) {
return .{ .base_iter = iter, .func = func };
context: anytype,
comptime func: anytype,
) MapContextIter(
@TypeOf(iter),
validateMapContextFn(Item(@TypeOf(iter)), @TypeOf(context), func),
) {
return .{ .base_iter = iter, .context = context };
}

pub fn validateMapContextFn(
comptime Source: type,
comptime Context: type,
comptime func: anytype,
) fn (Context, Source) @typeInfo(@TypeOf(func)).Fn.return_type.? {
return func;
}

test "MapIter" {
const slice: []const u32 = &.{ 1, 2, 3, 4 };
var slice_iter = sliceIter(u32, slice);
const slice_iter = sliceIter(u32, slice);

const functions = struct {
pub fn double(x: u32) u64 {
@@ -87,12 +152,63 @@ test "MapIter" {
try testing.expectEqual(@as(?u65, null), iter.next());
}

test "MapIter error" {
var test_iter = TestErrorIter.init(3);
test "MapIter value closure" {
const slice: []const u32 = &.{ 1, 2, 3, 4 };
const slice_iter = sliceIter(u32, slice);

const bias: u32 = 1;
const Closure = struct {
enclosed: u32,
pub fn apply(self: @This(), x: u32) u65 {
return x * 2 + self.enclosed;
}
};

var iter = mapContext(slice_iter, Closure{ .enclosed = bias }, Closure.apply);

try testing.expectEqual(u65, Item(@TypeOf(iter)));
try testing.expectEqual(@as(?u65, 3), iter.next());
try testing.expectEqual(@as(?u65, 5), iter.next());
try testing.expectEqual(@as(?u65, 7), iter.next());
try testing.expectEqual(@as(?u65, 9), iter.next());
try testing.expectEqual(@as(?u65, null), iter.next());
try testing.expectEqual(@as(?u65, null), iter.next());
}

test "MapIter reference closure" {
const slice: []const u32 = &.{ 1, 2, 3, 4 };
const slice_iter = sliceIter(u32, slice);

var acc: u32 = 0;
const Closure = struct {
enclosed: *u32,
pub fn apply(self: *@This(), x: u32) u65 {
if (x % 2 == 0) {
self.enclosed.* += x;
}
return x * 2 + 1;
}
};

var closure = Closure{ .enclosed = &acc };
var iter = mapContext(slice_iter, &closure, Closure.apply);

try testing.expectEqual(u65, Item(@TypeOf(iter)));
try testing.expectEqual(@as(?u65, 3), iter.next());
try testing.expectEqual(@as(?u65, 5), iter.next());
try testing.expectEqual(@as(?u65, 7), iter.next());
try testing.expectEqual(@as(?u65, 9), iter.next());
try testing.expectEqual(@as(?u65, null), iter.next());
try testing.expectEqual(@as(?u65, null), iter.next());
try testing.expectEqual(@as(u32, 6), acc);
}

test "MapIter error union" {
const test_iter = TestErrorIter.init(3);

const double = struct {
pub fn double(x: usize) u64 {
return 2 * x;
pub fn double(x: usize) error{Overflow}!u64 {
return std.math.mul(u64, 2, x);
}
}.double;

87 changes: 87 additions & 0 deletions src/nth.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
const std = @import("std");

const itertools = @import("main.zig");
const Item = itertools.Item;
const IterError = itertools.IterError;

pub fn Nth(comptime IterPtr: type) type {
const Iter = switch (@typeInfo(IterPtr)) {
.Pointer => |Pointer| if (Pointer.size == .One)
Pointer.child
else
@compileError("Expected mutable single-item pointer to iterator"),
else => @compileError("Expected mutable single-item pointer to iterator"),
};
return if (IterError(Iter)) |ES| ES!?Item(Iter) else ?Item(Iter);
}

pub fn nth(iter: anytype, n: usize) callconv(if (iterHasNth(@TypeOf(iter))) .Inline else .Unspecified) Nth(@TypeOf(iter)) {
if (comptime iterHasNth(@TypeOf(iter))) {
return iter.nth(n);
}
const has_error = comptime IterError(@typeInfo(@TypeOf(iter)).Pointer.child) != null;
const item = (if (has_error) try iter.next() else iter.next()) orelse return null;
return if (n == 0) item else @call(.always_tail, nth, .{ iter, n - 1 });
}

fn iterHasNth(comptime IterPtr: type) bool {
const Iter = switch (@typeInfo(IterPtr)) {
.Pointer => |Pointer| if (Pointer.size == .One)
Pointer.child
else
@compileError("Expected mutable single-item pointer to iterator"),
else => @compileError("Expected mutable single-item pointer to iterator"),
};
if (@hasDecl(Iter, "nth")) {
switch (@typeInfo(@TypeOf(Iter.nth))) {
.Fn => |Fn| if (Fn.params.len == 2 and Fn.params[0].type == *Iter and Fn.params[1].type == usize and Fn.return_type == Nth(*Iter)) {
return true;
},
else => {},
}
}
return false;
}

const testing = @import("std").testing;

test "nth" {
var iter = itertools.range(@as(u8, 0), 10);
try testing.expectEqual(@as(?u8, 5), nth(&iter, 5));
try testing.expectEqual(@as(?u8, 6), nth(&iter, 0));
try testing.expectEqual(@as(?u8, null), nth(&iter, 3));
}

test "nth far" {
var iter = itertools.range(@as(u8, 0), 10);
try testing.expectEqual(@as(?u8, null), nth(&iter, 15));
}

test "nth error" {
var iter = TestErrorIter.init(5);
try testing.expectError(error.TestErrorIterError, nth(&iter, 5));
}

test "nth optimal" {
var iter = itertools.sliceIter(u8, &.{ 1, 2, 3, 4 });
try testing.expectEqual(@as(?u8, 2), nth(&iter, 1));
try testing.expectEqual(@as(?u8, 3), nth(&iter, 0));
try testing.expectEqual(@as(?u8, null), nth(&iter, 1));
}

const TestErrorIter = struct {
const Self = @This();

counter: usize = 0,
until_err: usize,

pub fn init(until_err: usize) Self {
return .{ .until_err = until_err };
}

pub fn next(self: *Self) !?usize {
if (self.counter >= self.until_err) return error.TestErrorIterError;
self.counter += 1;
return self.counter - 1;
}
};
2 changes: 1 addition & 1 deletion src/peekable_iter.zig
Original file line number Diff line number Diff line change
@@ -55,7 +55,7 @@ pub fn peekable(iter: anytype) PeekableIter(@TypeOf(iter)) {
}

test "Peekable" {
var range = itertools.range(u32, 0, 5);
const range = itertools.range(@as(u32, 0), 5);
var iter = peekable(range);
try std.testing.expectEqual(u32, Item(@TypeOf(iter)));
try testing.expectEqual(@as(?u32, 0), iter.next());
99 changes: 99 additions & 0 deletions src/product.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
const std = @import("std");
const itertools = @import("main.zig");
const Item = itertools.Item;
const IterError = itertools.IterError;
const fold = itertools.fold;
const sliceIter = itertools.sliceIter;

/// Returns the return type to be used in `product`
pub fn Product(comptime Iter: type, comptime Dest: ?type) type {
return if (IterError(Iter)) |ES| ES!(Dest orelse Item(Iter)) else (Dest orelse Item(Iter));
}

/// This function is used to fold all the items in an iterator into a single value by __multiplying__ them
/// together. The type of the value is determined by the type of the iterator or `Dest`, if provided. If the iterator
/// returns an error, then the error is returned from the function.
///
/// Can be used to mul integers, floats, and vectors.
pub fn product(comptime Dest: ?type, iter: anytype) Product(@TypeOf(iter), Dest) {
const T = Item(@TypeOf(iter));
const U = Dest orelse T;
const mul = struct {
fn mul(a: U, b: T) U {
return @as(U, a) * @as(U, b);
}
}.mul;

const has_error = comptime IterError(@TypeOf(iter)) != null;

const init = switch (@typeInfo(U)) {
.Int, .Float => @as(U, 1),
.Vector => @as(U, @splat(1)),
.ErrorUnion => |ErrorUnion| switch (@typeInfo(ErrorUnion.payload)) {
.Int, .Float => @as(ErrorUnion.payload, 1),
.Vector => @as(ErrorUnion.payload, @splat(1)),
else => @panic("[product] unsupported type: " ++ @typeName(ErrorUnion.payload)),
},
else => @panic("[product] unsupported type: " ++ @typeName(U)),
};
return (if (has_error) try fold(iter, init, mul) else fold(iter, init, mul));
}

const testing = @import("std").testing;

test "mul ints" {
const slice: []const u32 = &.{ 1, 2, 3, 4, 5 };
try testing.expectEqual(@as(u32, 120), product(null, sliceIter(u32, slice)));
}

test "mul u32 as u33" {
const slice: []const u32 = &.{ std.math.maxInt(u32), 2 };
try testing.expectEqual(@as(u33, std.math.maxInt(u33) - 1), product(u33, sliceIter(u32, slice)));
}

test "mul floats" {
const slice: []const f32 = &.{ 1, 2, 3, 4, 5 };
try testing.expectEqual(@as(f32, 120), product(null, sliceIter(f32, slice)));
}

test "mul vectors" {
const slice: []const @Vector(2, u32) = &.{
@Vector(2, u32){ 1, 2 },
@Vector(2, u32){ 3, 4 },
@Vector(2, u32){ 5, 6 },
};
try testing.expectEqual(@Vector(2, u32){ 15, 48 }, product(null, sliceIter(@Vector(2, u32), slice)));
}

test "mul empty" {
const slice: []const u32 = &.{};
try testing.expectEqual(@as(u32, 1), product(null, sliceIter(u32, slice)));

const slice2: []const f32 = &.{};
try testing.expectEqual(@as(f32, 1), product(null, sliceIter(f32, slice2)));

const slice3: []const @Vector(2, u32) = &.{};
try testing.expectEqual(@Vector(2, u32){ 1, 1 }, product(null, sliceIter(@Vector(2, u32), slice3)));
}

test "mul error" {
const iter = TestErrorIter.init(5);
try testing.expectError(error.TestErrorIterError, product(null, iter));
}

const TestErrorIter = struct {
const Self = @This();

counter: usize = 0,
until_err: usize,

pub fn init(until_err: usize) Self {
return .{ .until_err = until_err };
}

pub fn next(self: *Self) error{TestErrorIterError}!?usize {
if (self.counter >= self.until_err) return error.TestErrorIterError;
self.counter += 1;
return self.counter - 1;
}
};
28 changes: 21 additions & 7 deletions src/range_iter.zig
Original file line number Diff line number Diff line change
@@ -25,8 +25,10 @@ pub fn RangeIter(comptime T: type) type {
}

pub fn next(self: *Self) ?T {
if (self.step > 0 and self.current >= self.end or
self.step < 0 and self.current <= self.end)
const is_vector = comptime @typeInfo(T) == .Vector;
const zero = comptime @as(T, if (is_vector) @splat(0) else 0);
if (self.step > zero and self.current >= self.end or
self.step < zero and self.current <= self.end)
return null;
const result = self.current;
self.current += self.step;
@@ -35,13 +37,25 @@ pub fn RangeIter(comptime T: type) type {
};
}

/// Creates a `RangeIter`. See its documentation for more info.
pub fn range(comptime T: type, start: T, end: T) RangeIter(T) {
return .{ .current = start, .end = end, .step = 1 };
/// Creates a `RangeIter`, a (half-open) range iterator bounded inclusively below and exclusively above [start, end).
pub fn range(start: anytype, end: @TypeOf(start)) RangeIter(@TypeOf(start)) {
const T = @TypeOf(start);
return .{
.current = start,
.end = end,
.step = switch (@typeInfo(T)) {
.Int, .Float => @as(T, 1),
.Vector => |Vector| switch (@typeInfo(Vector.child)) {
.Int, .Float => @as(T, @splat(1)),
else => @compileError("RangeIter Item type must be a number or vector of numbers"),
},
else => @compileError("RangeIter Item type must be a number or vector of numbers"),
},
};
}

test "RangeIter" {
var iter = range(u32, 0, 5);
var iter = range(@as(u32, 0), 5);
try testing.expectEqual(u32, Item(@TypeOf(iter)));
try testing.expectEqual(@as(?u32, 0), iter.next());
try testing.expectEqual(@as(?u32, 1), iter.next());
@@ -53,7 +67,7 @@ test "RangeIter" {
}

test "RangeIter reverse" {
var iter = range(i32, 5, 0).stepBy(-1);
var iter = range(@as(i32, 5), 0).stepBy(-1);
try testing.expectEqual(i32, Item(@TypeOf(iter)));
try testing.expectEqual(@as(?i32, 5), iter.next());
try testing.expectEqual(@as(?i32, 4), iter.next());
122 changes: 61 additions & 61 deletions src/reduce.zig
Original file line number Diff line number Diff line change
@@ -1,112 +1,112 @@
const std = @import("std");
const testing = std.testing;
const Child = std.meta.Child;

const itertools = @import("main.zig");
const Item = itertools.Item;
const IterError = itertools.IterError;
const sliceIter = itertools.sliceIter;

/// Returns the return type to be used in `reduce`
pub fn Reduce(comptime Iter: type, comptime T: type) type {
return if (IterError(Iter)) |ES|
ES!T
else
T;
pub fn Reduce(comptime Iter: type) type {
return if (IterError(Iter)) |ES| ES!?Item(Iter) else ?Item(Iter);
}

/// Applies a binary operator between all items in iter with an initial element.
///
/// Also know as fold in functional languages.
/// Applies a binary operator between all items in iter with no initial element.
pub fn reduce(
iter: anytype,
comptime T: type,
func: *const fn (T, Item(Child(@TypeOf(iter)))) T,
init: T,
) Reduce(Child(@TypeOf(iter)), T) {
const has_error = comptime IterError(Child(@TypeOf(iter))) != null;
var res = init;
while (if (has_error) try iter.next() else iter.next()) |item| {
res = func(res, item);
}
return res;
comptime func: fn (
Item(@TypeOf(iter)),
Item(@TypeOf(iter)),
) Item(@TypeOf(iter)),
) Reduce(@TypeOf(iter)) {
const has_error = comptime IterError(@TypeOf(iter)) != null;
var mut_iter = iter;
const maybe_init = if (has_error) try mut_iter.next() else mut_iter.next();
return if (has_error) try itertools.fold(
mut_iter,
maybe_init orelse return null,
func,
) else itertools.fold(
mut_iter,
maybe_init orelse return null,
func,
);
}

/// Applies a binary operator between all items in iter with an initial element and a context.
///
/// The context is passed as the first argument to the function. Context is useful for
/// when you want to pass in a function that behaves like a closure.
pub fn reduceContext(
iter: anytype,
context: anytype,
comptime func: fn (
@TypeOf(context),
Item(@TypeOf(iter)),
Item(@TypeOf(iter)),
) Item(@TypeOf(iter)),
) Reduce(@TypeOf(iter)) {
const has_error = comptime IterError(@TypeOf(iter)) != null;
var mut_iter = iter;
const maybe_init = if (has_error) try mut_iter.next() else mut_iter.next();
return itertools.foldContext(
mut_iter,
context,
maybe_init orelse return null,
func,
);
}

test "reduce" {
const slice: []const u32 = &.{ 1, 2, 3, 4 };
var iter = sliceIter(u32, slice);
const iter = sliceIter(u32, slice);

const add = struct {
fn add(x: u64, y: u32) u64 {
fn add(x: u32, y: u32) u32 {
return x + y;
}
}.add;

try testing.expectEqual(@as(u64, 10), reduce(&iter, u64, add, 0));
try testing.expectEqual(@as(?u32, 10), reduce(iter, add));
}

test "reduce error" {
var iter = TestErrorIter.init(5);
test "reduce context" {
const slice: []const u32 = &.{ 1, 2, 3, 4 };
const iter = sliceIter(u32, slice);

const add = struct {
fn add(x: u64, y: usize) u64 {
return x + y;
fn add(context: u32, x: u32, y: u32) u32 {
return x + y + context;
}
}.add;

try testing.expectError(error.TestErrorIterError, reduce(&iter, u64, add, 0));
const context: u32 = 5;
try testing.expectEqual(@as(?u32, 25), reduceContext(iter, context, add));
}

/// Returns the return type to be used in `reduce1`
pub fn Reduce1(comptime Iter: type) type {
return if (IterError(Iter)) |ES|
(error{EmptyIterator} || ES)!Item(Iter)
else
error{EmptyIterator}!Item(Iter);
}

/// Applies a binary operator between all items in iter with no initial element.
///
/// If the iterator is empty `error.EmptyIterator` is returned.
///
/// Also know as fold1 in functional languages.
pub fn reduce1(
iter: anytype,
func: *const fn (
Item(Child(@TypeOf(iter))),
Item(Child(@TypeOf(iter))),
) Item(Child(@TypeOf(iter))),
) Reduce1(Child(@TypeOf(iter))) {
const has_error = comptime IterError(Child(@TypeOf(iter))) != null;
const maybe_init = if (has_error) try iter.next() else iter.next();
const init = maybe_init orelse return error.EmptyIterator;
return reduce(iter, Item(Child(@TypeOf(iter))), func, init);
}

test "reduce1" {
const slice: []const u32 = &.{ 1, 2, 3, 4 };
var iter = sliceIter(u32, slice);
test "reduce empty" {
const slice: []const u32 = &.{};
const iter = sliceIter(u32, slice);

const add = struct {
fn add(x: u32, y: u32) u32 {
return x + y;
}
}.add;

try testing.expectEqual(@as(u32, 10), try reduce1(&iter, add));
try testing.expectError(error.EmptyIterator, reduce1(&iter, add));
try testing.expect(reduce(iter, add) == null);
}

test "reduce1 error" {
var iter = TestErrorIter.init(5);
test "reduce error" {
const iter = TestErrorIter.init(5);

const add = struct {
fn add(x: usize, y: usize) usize {
return x + y;
}
}.add;

try testing.expectError(error.TestErrorIterError, reduce1(&iter, add));
try testing.expectError(error.TestErrorIterError, reduce(iter, add));
}

const TestErrorIter = struct {
2 changes: 1 addition & 1 deletion src/skip_iter.zig
Original file line number Diff line number Diff line change
@@ -49,7 +49,7 @@ pub fn skip(iter: anytype, to_skip: usize) SkipIter(@TypeOf(iter)) {
}

test "skip" {
var base_iter = range(u32, 5, 10);
const base_iter = range(@as(u32, 5), 10);
var iter = skip(base_iter, 2);
try testing.expectEqual(Item(@TypeOf(base_iter)), Item(@TypeOf(iter)));
try testing.expectEqual(@as(?u32, 7), iter.next());
127 changes: 105 additions & 22 deletions src/skip_while_iter.zig
Original file line number Diff line number Diff line change
@@ -5,36 +5,72 @@ const itertools = @import("main.zig");
const IterError = itertools.IterError;
const Item = itertools.Item;
const range = itertools.range;
const findContext = itertools.findContext;

/// An iterator that rejects elements while predicate returns true.
///
/// See `skipWhile` for more info.
pub fn SkipWhileIter(comptime BaseIter: type) type {
pub fn SkipWhileIter(comptime BaseIter: type, comptime predicate: fn (*const Item(BaseIter)) bool) type {
return struct {
const Self = @This();

base_iter: BaseIter,
predicate: ?*const fn (Item(BaseIter)) bool,
flag: bool = false,

pub const Next = if (IterError(BaseIter)) |ES| ES!?Item(BaseIter) else ?Item(BaseIter);

pub fn next(self: *Self) Next {
const has_error = IterError(BaseIter) != null;
if (self.predicate) |predicate| {
self.predicate = null;
while (true) {
const maybe_item = if (has_error)
try self.base_iter.next()
else
self.base_iter.next();
const item = maybe_item orelse return null;
if (!predicate(item)) return item;
const Check = struct {
flag: *bool,
fn call(self_Check: @This(), item: *const Item(BaseIter)) bool {
if (self_Check.flag.* or !predicate(item)) {
self_Check.flag.* = true;
return true;
} else {
return false;
}
}
}
return if (has_error)
try self.base_iter.next()
else
self.base_iter.next();
};
return findContext(&self.base_iter, Check{ .flag = &self.flag }, Check.call);
}
};
}

/// An iterator that rejects elements while predicate returns true, given the context.
///
/// See `skipWhileContext` for more info.
pub fn SkipWhileContextIter(
comptime BaseIter: type,
comptime Context: type,
comptime predicate: fn (Context, *const Item(BaseIter)) bool,
) type {
return struct {
const Self = @This();

base_iter: BaseIter,
context: Context,
flag: bool = false,

pub const Next = if (IterError(BaseIter)) |ES| ES!?Item(BaseIter) else ?Item(BaseIter);

pub fn next(self: *Self) Next {
const Check = struct {
context: Context,
flag: *bool,
fn call(self_Check: @This(), item: *const Item(BaseIter)) bool {
if (self_Check.flag.* or !predicate(self_Check.context, item)) {
self_Check.flag.* = true;
return true;
} else {
return false;
}
}
};
return findContext(
&self.base_iter,
Check{ .context = self.context, .flag = &self.flag },
Check.call,
);
}
};
}
@@ -47,15 +83,34 @@ pub fn SkipWhileIter(comptime BaseIter: type) type {
//
// After false is returned, `skipWhile()`’s job is over, and the rest of the
/// elements are yielded.
pub fn skipWhile(iter: anytype, predicate: *const fn (Item(@TypeOf(iter))) bool) SkipWhileIter(@TypeOf(iter)) {
return .{ .base_iter = iter, .predicate = predicate };
pub fn skipWhile(
iter: anytype,
comptime predicate: fn (*const Item(@TypeOf(iter))) bool,
) SkipWhileIter(@TypeOf(iter), predicate) {
return .{ .base_iter = iter };
}

/// Creates an iterator that skips elements based on a predicate, given the context.
///
/// `skipWhileContext()` takes a function as an argument. It will call this function
/// on each element of the iterator, and ignore elements until it returns
/// false.
///
/// After false is returned, `skipWhileContext()`’s job is over, and the rest of the
/// elements are yielded.
pub fn skipWhileContext(
iter: anytype,
context: anytype,
comptime predicate: fn (@TypeOf(context), *const Item(@TypeOf(iter))) bool,
) SkipWhileContextIter(@TypeOf(iter), @TypeOf(context), predicate) {
return .{ .base_iter = iter, .context = context };
}

test "skipWhile" {
var base_iter = range(u32, 0, 10);
const base_iter = range(@as(u32, 0), 10);
const predicate = struct {
fn predicate(x: u32) bool {
return x < 5;
fn predicate(x: *const u32) bool {
return x.* < 5;
}
}.predicate;
var iter = skipWhile(base_iter, predicate);
@@ -67,3 +122,31 @@ test "skipWhile" {
try testing.expectEqual(@as(?u32, 9), iter.next());
try testing.expectEqual(@as(?u32, null), iter.next());
}

test "skipWhile error" {
const base_iter = TestErrorIter{ .until_err = 3 };
const predicate = struct {
fn predicate(x: *const usize) bool {
return x.* < 5;
}
}.predicate;
var iter = skipWhile(base_iter, predicate);
try testing.expectError(error.TestErrorIterError, iter.next());
}

const TestErrorIter = struct {
const Self = @This();

counter: usize = 0,
until_err: usize,

pub fn init(until_err: usize) Self {
return .{ .until_err = until_err };
}

pub fn next(self: *Self) !?usize {
if (self.counter >= self.until_err) return error.TestErrorIterError;
self.counter += 1;
return self.counter - 1;
}
};
15 changes: 14 additions & 1 deletion src/slice_iter.zig
Original file line number Diff line number Diff line change
@@ -18,11 +18,24 @@ pub fn SliceIter(comptime T: type) type {
self.index += 1;
return self.slice[self.index - 1];
}

pub fn nth(self: *Self, n: usize) ?T {
if (n >= self.len()) {
self.index = self.slice.len;
return null;
}
defer self.index += n + 1;
return self.slice[self.index + n];
}

pub fn len(self: *const Self) usize {
return if (self.index < self.slice.len) self.slice.len - self.index else 0;
}
};
}

/// Returns an iterator iterating over the values in the slice.
pub fn sliceIter(comptime T: anytype, slice: []const T) SliceIter(T) {
pub fn sliceIter(comptime T: anytype, slice: anytype) SliceIter(T) {
return .{ .slice = slice, .index = 0 };
}

80 changes: 65 additions & 15 deletions src/successors_iter.zig
Original file line number Diff line number Diff line change
@@ -9,41 +9,70 @@ const range = itertools.range;
/// An new iterator where each successive item is computed based on the preceding one.
///
/// See `successors` for more info.
pub fn SuccessorsIter(comptime T: type) type {
pub fn SuccessorsIter(comptime T: type, comptime succ: fn (*const T) ?T) type {
return struct {
const Self = @This();

current: ?T,
func: *const fn (T) ?T,
next_item: ?T,

pub fn next(self: *Self) ?T {
const current = self.current orelse return null;
self.current = self.func(current);
return current;
const item: T = self.next_item orelse return null;
self.next_item = succ(&item);
return item;
}
};
}

pub fn SuccessorsContextIter(comptime T: type, comptime Context: type, comptime succ: fn (Context, *const T) ?T) type {
return struct {
const Self = @This();

next_item: ?T,
context: Context,

pub fn next(self: *Self) ?T {
const item: T = self.next_item orelse return null;
self.next_item = succ(self.context, &item);
return item;
}
};
}

/// Creates a new iterator where each successive item is computed based on the
/// preceding one.
///
/// The iterator starts with the given first item (if any) and calls the given
/// The iterator starts with the given first item, if any, and calls the given
/// function to compute each item’s successor.
pub fn successors(
init: anytype,
func: *const fn (@TypeOf(init)) ?@TypeOf(init),
) SuccessorsIter(@TypeOf(init)) {
return .{ .current = init, .func = func };
comptime T: type,
first: ?T,
comptime succ: fn (*const T) ?T,
) SuccessorsIter(T, succ) {
return .{ .next_item = first };
}

/// Creates a new iterator where each successive item is computed based on the
/// preceding one, given the context.
///
/// The iterator starts with the given first item, if any, and calls the given
/// function to compute each item’s successor.
pub fn successorsContext(
comptime T: type,
first: ?T,
context: anytype,
comptime func: fn (@TypeOf(context), *const T) ?T,
) SuccessorsContextIter(T, @TypeOf(context), func) {
return .{ .next_item = first, .context = context };
}

test "successors" {
const func = struct {
fn func(x: u32) ?u32 {
if (x >= 5) return null;
return x + 1;
fn func(x: *const u32) ?u32 {
if (x.* >= 5) return null;
return x.* + 1;
}
}.func;
var iter = successors(@as(u32, 0), func);
var iter = successors(u32, 0, func);
try testing.expectEqual(u32, Item(@TypeOf(iter)));
try testing.expectEqual(@as(?u32, 0), iter.next());
try testing.expectEqual(@as(?u32, 1), iter.next());
@@ -55,3 +84,24 @@ test "successors" {
try testing.expectEqual(@as(?u32, null), iter.next());
try testing.expectEqual(@as(?u32, null), iter.next());
}

test "successorsContext fibbonacci" {
const func = struct {
fn fib(context: *u32, x: *const u32) ?u32 {
defer context.* += x.*;
return context.*;
}
}.fib;
var context: u32 = 1;
var iter = successorsContext(u32, 0, &context, func);
try testing.expectEqual(u32, Item(@TypeOf(iter)));
try testing.expectEqual(@as(?u32, 0), iter.next());
try testing.expectEqual(@as(?u32, 1), iter.next());
try testing.expectEqual(@as(?u32, 1), iter.next());
try testing.expectEqual(@as(?u32, 2), iter.next());
try testing.expectEqual(@as(?u32, 3), iter.next());
try testing.expectEqual(@as(?u32, 5), iter.next());
try testing.expectEqual(@as(?u32, 8), iter.next());
try testing.expectEqual(@as(?u32, 13), iter.next());
try testing.expectEqual(@as(?u32, 21), iter.next());
}
103 changes: 103 additions & 0 deletions src/sum.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
const std = @import("std");
const itertools = @import("main.zig");
const Item = itertools.Item;
const IterError = itertools.IterError;
const fold = itertools.fold;
const sliceIter = itertools.sliceIter;

/// Returns the return type to be used in `sum`
pub fn Sum(comptime Iter: type, comptime Dest: ?type) type {
return if (IterError(Iter)) |ES| ES!(Dest orelse Item(Iter)) else (Dest orelse Item(Iter));
}

/// This function is used to fold all the items in an iterator into a single value by __adding__ them
/// together. The type of the value is determined by the type of the iterator or `Dest`, if provided. If the iterator
/// returns an error, then the error is returned from the function.
///
/// Can be used to sum integers, floats, and vectors.
pub fn sum(comptime Dest: ?type, iter: anytype) Sum(@TypeOf(iter), Dest) {
const T = Item(@TypeOf(iter));
const U = Dest orelse T;
const add = struct {
fn add(a: U, b: T) U {
return @as(U, a) + @as(U, b);
}
}.add;

const has_error = comptime IterError(@TypeOf(iter)) != null;

const init = switch (@typeInfo(U)) {
.Int, .Float => @as(U, 0),
.Vector => @as(U, @splat(0)),
.ErrorUnion => |ErrorUnion| switch (@typeInfo(ErrorUnion.payload)) {
.Int, .Float => @as(ErrorUnion.payload, 0),
.Vector => @as(ErrorUnion.payload, @splat(0)),
else => @compileError("[sum] unsupported type: " ++ @typeName(ErrorUnion.payload)),
},
else => @compileError("[sum] unsupported type: " ++ @typeName(U)),
};

return if (has_error)
try fold(iter, init, add)
else
fold(iter, init, add);
}

const testing = @import("std").testing;

test "sum ints" {
const slice: []const u32 = &.{ 1, 2, 3, 4, 5 };
try testing.expectEqual(@as(u32, 15), sum(null, sliceIter(u32, slice)));
}

test "sum u32 as u33" {
const slice: []const u32 = &(.{std.math.maxInt(u32)} ** 2);
try testing.expectEqual(@as(u33, std.math.maxInt(u33) - 1), sum(u33, sliceIter(u32, slice)));
}

test "sum floats" {
const slice: []const f32 = &.{ 1, 2, 3, 4, 5 };
try testing.expectEqual(@as(f32, 15), sum(null, sliceIter(f32, slice)));
}

test "sum vectors" {
const slice: []const @Vector(2, u32) = &.{
@Vector(2, u32){ 1, 2 },
@Vector(2, u32){ 3, 4 },
@Vector(2, u32){ 5, 6 },
};
try testing.expectEqual(@Vector(2, u32){ 9, 12 }, sum(null, sliceIter(@Vector(2, u32), slice)));
}

test "sum empty" {
const slice: []const u32 = &.{};
try testing.expectEqual(@as(u32, 0), sum(null, sliceIter(u32, slice)));

const slice2: []const f32 = &.{};
try testing.expectEqual(@as(f32, 0), sum(null, sliceIter(f32, slice2)));

const slice3: []const @Vector(2, u32) = &.{};
try testing.expectEqual(@Vector(2, u32){ 0, 0 }, sum(null, sliceIter(@Vector(2, u32), slice3)));
}

test "sum error" {
const iter = TestErrorIter.init(5);
try testing.expectError(error.TestErrorIterError, sum(null, iter));
}

const TestErrorIter = struct {
const Self = @This();

counter: usize = 0,
until_err: usize,

pub fn init(until_err: usize) Self {
return .{ .until_err = until_err };
}

pub fn next(self: *Self) !?usize {
if (self.counter >= self.until_err) return error.TestErrorIterError;
self.counter += 1;
return self.counter - 1;
}
};
4 changes: 2 additions & 2 deletions src/take_iter.zig
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ pub fn take(iter: anytype, to_take: usize) TakeIter(@TypeOf(iter)) {
}

test "take" {
var base_iter = range(u32, 0, 10);
const base_iter = range(@as(u32, 0), 10);
var iter = take(base_iter, 5);
try testing.expectEqual(Item(@TypeOf(base_iter)), Item(@TypeOf(iter)));
try testing.expectEqual(@as(?u32, 0), iter.next());
@@ -55,7 +55,7 @@ test "take" {
}

test "take small iter" {
var base_iter = range(u32, 0, 2);
const base_iter = range(@as(u32, 0), 2);
var iter = take(base_iter, 5);
try testing.expectEqual(Item(@TypeOf(base_iter)), Item(@TypeOf(iter)));
try testing.expectEqual(@as(?u32, 0), iter.next());
89 changes: 80 additions & 9 deletions src/take_while_iter.zig
Original file line number Diff line number Diff line change
@@ -9,17 +9,20 @@ const range = itertools.range;
/// An iterator that only accepts elements while predicate returns true.
///
/// See `takeWhile` for more info.
pub fn TakeWhileIter(comptime BaseIter: type) type {
pub fn TakeWhileIter(
comptime BaseIter: type,
comptime predicate: fn (Item(BaseIter)) bool,
) type {
return struct {
const Self = @This();

base_iter: BaseIter,
predicate: ?*const fn (Item(BaseIter)) bool,
flag: bool = false,

pub const Next = if (IterError(BaseIter)) |ES| ES!?Item(BaseIter) else ?Item(BaseIter);

pub fn next(self: *Self) Next {
const predicate = self.predicate orelse return null;
if (self.flag) return null;

const has_error = IterError(BaseIter) != null;
const maybe_item = if (has_error)
@@ -28,25 +31,76 @@ pub fn TakeWhileIter(comptime BaseIter: type) type {
self.base_iter.next();
const item = maybe_item orelse return null;
if (predicate(item)) return item;
self.predicate = null;
self.flag = true;
return null;
}
};
}

/// An iterator that only accepts elements while predicate returns true, given the context.
///
/// See `takeWhileContext` for more info.
pub fn TakeWhileContextIter(
comptime BaseIter: type,
comptime Context: type,
comptime predicate: fn (Context, Item(BaseIter)) bool,
) type {
return struct {
const Self = @This();

base_iter: BaseIter,
context: Context,
flag: bool = false,

pub const Next = if (IterError(BaseIter)) |ES| ES!?Item(BaseIter) else ?Item(BaseIter);

pub fn next(self: *Self) Next {
if (self.flag) return null;

const has_error = IterError(BaseIter) != null;
const maybe_item = if (has_error)
try self.base_iter.next()
else
self.base_iter.next();
const item = maybe_item orelse return null;
if (predicate(self.context, item)) return item;
self.flag = true;
return null;
}
};
}

/// Creates an iterator that yields elements based on a predicate.
///
/// `take_while()` takes a function as an argument. It will call this function
/// `takeWhile()` takes a function as an argument. It will call this function
/// on each element of the iterator, and yield elements while it returns true.
///
/// After false is returned, `takeWhile()`'s job is over, and the rest of the
/// elements are ignored.
pub fn takeWhile(
iter: anytype,
comptime predicate: fn (Item(@TypeOf(iter))) bool,
) TakeWhileIter(@TypeOf(iter), predicate) {
return .{ .base_iter = iter };
}

/// Creates an iterator that yields elements based on a predicate, given the context.
///
/// `takeWhileContext()` takes a function as an argument. It will call this function
/// on each element of the iterator, and yield elements while it returns true.
///
/// After false is returned, `take_while()`s job is over, and the rest of the
/// After false is returned, `takeWhileContext()`'s job is over, and the rest of the
/// elements are ignored.
pub fn takeWhile(iter: anytype, predicate: *const fn (Item(@TypeOf(iter))) bool) TakeWhileIter(@TypeOf(iter)) {
return .{ .base_iter = iter, .predicate = predicate };
pub fn takeWhileContext(
iter: anytype,
context: anytype,
comptime predicate: fn (@TypeOf(context), Item(@TypeOf(iter))) bool,
) TakeWhileContextIter(@TypeOf(iter), @TypeOf(context), predicate) {
return .{ .base_iter = iter, .context = context };
}

test "takeWhile" {
var base_iter = range(u32, 0, 10);
const base_iter = range(@as(u32, 0), 10);
const predicate = struct {
fn predicate(x: u32) bool {
return x < 5;
@@ -61,3 +115,20 @@ test "takeWhile" {
try testing.expectEqual(@as(?u32, 4), iter.next());
try testing.expectEqual(@as(?u32, null), iter.next());
}

test "takeWhileContext" {
const base_iter = range(@as(u32, 0), 10);
const predicate = struct {
fn predicate(context: u32, x: u32) bool {
return x < context;
}
}.predicate;
var iter = takeWhileContext(base_iter, @as(u32, 5), predicate);
try testing.expectEqual(Item(@TypeOf(base_iter)), Item(@TypeOf(iter)));
try testing.expectEqual(@as(?u32, 0), iter.next());
try testing.expectEqual(@as(?u32, 1), iter.next());
try testing.expectEqual(@as(?u32, 2), iter.next());
try testing.expectEqual(@as(?u32, 3), iter.next());
try testing.expectEqual(@as(?u32, 4), iter.next());
try testing.expectEqual(@as(?u32, null), iter.next());
}
202 changes: 161 additions & 41 deletions src/zip_iter.zig
Original file line number Diff line number Diff line change
@@ -1,82 +1,202 @@
const std = @import("std");
const testing = std.testing;
const Type = std.builtin.Type;
const Struct = std.builtin.Type.Struct;
const StructField = std.builtin.Type.StructField;
const ErrorSet = std.builtin.Type.ErrorSet;
const Error = std.builtin.Type.Error;

const itertools = @import("main.zig");
const Item = itertools.Item;
const SliceIter = itertools.SliceIter;
const IterError = itertools.IterError;
const sliceIter = itertools.sliceIter;
const range = itertools.range;
const iterMethods = itertools.iterMethods;

/// An iterator that iterates two other iterators simultaneously.
///
/// See `zip` for more info.
pub fn ZipIter(comptime A: type, comptime B: type) type {
pub fn ZipIter(comptime Iters: type) type {
return struct {
const Self = @This();

a: A,
b: B,
iters: Iters,

pub const Item = struct { itertools.Item(A), itertools.Item(B) };

pub const Next = if (IterError(A)) |ESA|
if (IterError(B)) |ESB|
(ESA || ESB)!?Self.Item
else
ESA!?Self.Item
else if (IterError(B)) |ESB|
ESB!?Self.Item
else
?Self.Item;
pub const Item: type = ZipIterItem(Iters);
pub const ErrorSet: ?type = ZipIterErrorSet(Iters);
pub const Next = if (Self.ErrorSet) |ES| ES!?Self.Item else ?Self.Item;

pub fn next(self: *Self) Next {
const a_has_error = comptime IterError(A) != null;
const b_has_error = comptime IterError(B) != null;
const maybe_a = if (a_has_error)
try self.a.next()
else
self.a.next();
const a = maybe_a orelse return null;
const maybe_b = if (b_has_error)
try self.b.next()
else
self.b.next();
const b = maybe_b orelse return null;
return .{ a, b };
var item: Self.Item = undefined;
inline for (@typeInfo(Iters).Struct.fields) |iter_field| {
if (iter_field.is_comptime) {
continue;
}

const has_error = comptime IterError(iter_field.type) != null;
const maybe_item = if (has_error)
try @field(self.iters, iter_field.name).next()
else
@field(self.iters, iter_field.name).next();
@field(item, iter_field.name) = maybe_item orelse return null;
}
return item;
}
};
}

pub fn ZipIterItem(comptime Iters: type) type {
const iters_type_info = @typeInfo(Iters);
const total_fields = blk: {
var total_fields = 0;
for (iters_type_info.Struct.fields) |field| {
if (field.is_comptime) {
continue;
}
total_fields += 1;
}
break :blk total_fields;
};
if (iters_type_info != .Struct) {
@compileError("expected tuple or struct, found '" ++ @typeName(Iters) ++ "'");
}
return @Type(.{
.Struct = Struct{
.layout = .Auto,
.fields = &blk: {
var fields: [total_fields]StructField = undefined;
var i = 0; // manual counter because of filtering out of comptime fields
for (iters_type_info.Struct.fields) |field| {
if (field.is_comptime) {
continue;
}
fields[i] = std.builtin.Type.StructField{
.name = field.name,
.type = Item(field.type),
.default_value = null,
.is_comptime = false,
.alignment = 0,
};
i += 1;
}
break :blk fields;
},
.decls = &.{},
.is_tuple = iters_type_info.Struct.is_tuple,
},
});
}

pub fn ZipIterErrorSet(comptime Iters: type) ?type {
const iters_type_info = @typeInfo(Iters);
if (iters_type_info != .Struct) {
@compileError("expected tuple or struct, found '" ++ @typeName(Iters) ++ "'");
}

var ES: ?type = null;
for (iters_type_info.Struct.fields) |field| {
if (field.is_comptime) {
continue;
}
ES = ES orelse error{} || (IterError(field.type) orelse continue);
}
return ES;
}

/// ‘Zips up’ two iterators into a single iterator of pairs.
/// ‘Zips up’ several iterators into a single iterator of tuples/structs.
///
/// `zip()` returns a new iterator that will iterate over two other iterators,
/// returning a tuple where the first element comes from the first iterator,
/// and the second element comes from the second iterator.
/// `zip()` returns a new iterator that will iterate over several other iterators,
/// returning a tuple/struct of items where each field corresponts
/// to the field in the tuple/struct of iteratos.
///
/// In other words, it zips two iterators together, into a single one.
/// In other words, it zips several iterators together, into a single one.
///
/// If either iterator returns null, next from the zipped iterator will return
/// null. If the zipped iterator has no more elements to return then each
/// further attempt to advance it will first try to advance the first iterator
/// at most one time and if it still yielded an item try to advance the second
/// iterator at most one time.
pub fn zip(iter1: anytype, iter2: anytype) ZipIter(@TypeOf(iter1), @TypeOf(iter2)) {
return .{ .a = iter1, .b = iter2 };
/// iterator at most one time and so on.
pub fn zip(iters: anytype) ZipIter(@TypeOf(iters)) {
return .{ .iters = iters };
}

test "zip" {
var iter1 = sliceIter(u32, &.{ 1, 2, 3 });
var iter2 = range(u64, 5, 8);
var iter = zip(iter1, iter2);
test "zip tuple" {
const iter1 = sliceIter(u32, &.{ 1, 2, 3 });
const iter2 = range(@as(u64, 5), 8);
var iter = zip(.{ iter1, iter2 });

try testing.expectEqual(@TypeOf(iter).Item, Item(@TypeOf(iter)));

const v1 = iter.next().?;
try testing.expectEqual(@as(u32, 1), v1.@"0");
try testing.expectEqual(@as(u64, 5), v1.@"1");
try testing.expectEqual(@as(u32, 1), v1[0]);
try testing.expectEqual(@as(u64, 5), v1[1]);

const v2 = iter.next().?;
try testing.expectEqual(@as(u32, 2), v2.@"0");
try testing.expectEqual(@as(u64, 6), v2.@"1");

const v3 = iter.next().?;
try testing.expectEqual(@as(u32, 3), v3.@"0");
try testing.expectEqual(@as(u64, 7), v3.@"1");
try testing.expectEqual(@as(?Item(@TypeOf(iter)), null), iter.next());
}

test "zip struct" {
const iter1 = sliceIter(u32, &.{ 1, 2, 3 });
const iter2 = range(@as(u64, 5), 8);
var iter = zip(.{ .first = iter1, .second = iter2 });

try testing.expectEqual(@TypeOf(iter).Item, Item(@TypeOf(iter)));

const v1 = iter.next().?;
try testing.expectEqual(@as(u32, 1), v1.first);
try testing.expectEqual(@as(u64, 5), v1.second);

const v2 = iter.next().?;
try testing.expectEqual(@as(u32, 2), v2.first);
try testing.expectEqual(@as(u64, 6), v2.second);

const v3 = iter.next().?;
try testing.expectEqual(@as(u32, 3), v3.first);
try testing.expectEqual(@as(u64, 7), v3.second);
try testing.expectEqual(@as(?Item(@TypeOf(iter)), null), iter.next());
}

test "zip error first" {
const iter1 = TestErrorIter.init(3);
const iter2 = range(@as(u64, 5), 8);
var iter = zip(.{ iter1, iter2 });

try testing.expectEqual(@TypeOf(iter).Item, Item(@TypeOf(iter)));

const v1 = (try iter.next()).?;
try testing.expectEqual(@as(usize, 0), v1.@"0");
try testing.expectEqual(@as(u64, 5), v1.@"1");

const v2 = (try iter.next()).?;
try testing.expectEqual(@as(usize, 1), v2.@"0");
try testing.expectEqual(@as(u64, 6), v2.@"1");

const v3 = (try iter.next()).?;
try testing.expectEqual(@as(usize, 2), v3.@"0");
try testing.expectEqual(@as(u64, 7), v3.@"1");
try testing.expectError(error.TestErrorIterError, iter.next());
}

const TestErrorIter = struct {
const Self = @This();

counter: usize = 0,
until_err: usize,

pub fn init(until_err: usize) Self {
return .{ .until_err = until_err };
}

pub fn next(self: *Self) !?usize {
if (self.counter >= self.until_err) return error.TestErrorIterError;
self.counter += 1;
return self.counter - 1;
}
};