iterx

package
v0.1.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 14, 2026 License: MIT Imports: 2 Imported by: 0

Documentation

Overview

Package iterx provides lazy, context-aware sequence transformations for Go 1.23 iter.Seq values.

The iterx package is useful when you want to build allocation-light data pipelines over slices, files, database rows, or generated streams without materializing every intermediate result. Each helper returns a new lazy sequence or consumes one directly, so work only happens when the caller ranges over the final sequence.

Example:

ctx := context.Background()
numbers := slices.Values([]int{1, 2, 3, 4, 5})

for v := range iterx.Filter(ctx, numbers, func(n int) bool {
	return n%2 == 0
}) {
	fmt.Println(v)
}

The iterx package functions are lazy and stop as soon as the context is cancelled or the consumer stops reading. Reverse buffers the full sequence in memory before yielding results, while helpers like Filter, Map, and Take stream values one at a time.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Chunk

func Chunk[T any](ctx context.Context, seq iter.Seq[T], n int) iter.Seq[[]T]

Chunk splits a sequence into slices of size n.

Example

ExampleChunk demonstrates how to process data in batches, such as for bulk database inserts or writing to chunked APIs.

package main

import (
	"context"
	"fmt"
	"slices"

	"github.com/MostafaMagdSalama/vortex/iterx"
)

func main() {
	ctx := context.Background()
	logs := slices.Values([]string{"log1", "log2", "log3", "log4", "log5"})

	for batch := range iterx.Chunk(ctx, logs, 2) {
		fmt.Printf("Batch size: %d, items: %v\n", len(batch), batch)
	}
}
Output:
Batch size: 2, items: [log1 log2]
Batch size: 2, items: [log3 log4]
Batch size: 1, items: [log5]

func Contains

func Contains[T comparable](ctx context.Context, seq iter.Seq[T], target T) bool

Contains returns true if the sequence contains the target value.

Example

ExampleContains demonstrates checking if an item exists efficiently, as the iterator stops processing as soon as it finds a match.

package main

import (
	"context"
	"fmt"
	"slices"

	"github.com/MostafaMagdSalama/vortex/iterx"
)

func main() {
	ctx := context.Background()
	users := slices.Values([]string{"alice", "bob", "charlie", "admin"})

	hasAdmin := iterx.Contains(ctx, users, "admin")
	fmt.Println("Has admin:", hasAdmin)
}
Output:
Has admin: true

func Distinct

func Distinct[T comparable](ctx context.Context, seq iter.Seq[T]) iter.Seq[T]

Distinct filters out duplicate values keeping only the first occurrence.

Example

ExampleDistinct demonstrates removing duplicates from an incoming stream, such as a sequence of IP addresses.

package main

import (
	"context"
	"fmt"
	"slices"

	"github.com/MostafaMagdSalama/vortex/iterx"
)

func main() {
	ctx := context.Background()
	ipAddresses := slices.Values([]string{"192.168.1.1", "10.0.0.1", "192.168.1.1", "10.0.0.2"})

	for ip := range iterx.Distinct(ctx, ipAddresses) {
		fmt.Println(ip)
	}
}
Output:
192.168.1.1
10.0.0.1
10.0.0.2

func Drain

func Drain[T any](ctx context.Context, seq iter.Seq[T], fn func(T) error) error

Drain consumes a sequence and calls fn for each item. Stops immediately if ctx is cancelled or fn returns an error. Use Drain when your terminal operation can fail — writing to CSV, DB, files. Use ForEach when your terminal operation cannot fail — logging, printing.

example:

err := iterx.Drain(ctx, users, func(u User) error {
    return csvWriter.Write([]string{u.Name, u.Email})
})
Example

ExampleDrain demonstrates exhausting a sequence when the terminal operation can fail, like writing to an io.Writer.

package main

import (
	"context"
	"fmt"
	"slices"
	"strings"

	"github.com/MostafaMagdSalama/vortex/iterx"
)

func main() {
	ctx := context.Background()
	lines := slices.Values([]string{"header", "data 1", "data 2"})

	// Simulate writing to something that could fail
	var out strings.Builder

	err := iterx.Drain(ctx, lines, func(line string) error {
		_, err := out.WriteString(line + "\n")
		return err // stops early if err != nil
	})

	fmt.Printf("Error: %v, Output:\n%s", err, out.String())
}
Output:
Error: <nil>, Output:
header
data 1
data 2

func Filter

func Filter[T any](ctx context.Context, seq iter.Seq[T], fn func(T) bool) iter.Seq[T]

Filter returns a new sequence containing only elements where fn returns true.

Example

ExampleFilter demonstrates removing unwanted items from a stream.

package main

import (
	"context"
	"fmt"
	"slices"

	"github.com/MostafaMagdSalama/vortex/iterx"
)

func main() {
	ctx := context.Background()
	numbers := slices.Values([]int{1, 2, 3, 4, 5, 6})

	evens := iterx.Filter(ctx, numbers, func(n int) bool {
		return n%2 == 0
	})

	for v := range evens {
		fmt.Println(v)
	}
}
Output:
2
4
6

func FlatMap

func FlatMap[T, U any](ctx context.Context, seq iter.Seq[T], fn func(T) iter.Seq[U]) iter.Seq[U]

FlatMap transforms each element into a sequence, then flattens all sequences.

Example

ExampleFlatMap demonstrates expanding a single item into multiple items. In this case: expanding a user into a stream of their roles.

package main

import (
	"context"
	"fmt"
	"iter"
	"slices"

	"github.com/MostafaMagdSalama/vortex/iterx"
)

type DemoUser struct {
	Name  string
	Roles []string
}

func main() {
	ctx := context.Background()
	users := slices.Values([]DemoUser{
		{Name: "Alice", Roles: []string{"admin", "editor"}},
		{Name: "Bob", Roles: []string{"viewer"}},
	})

	roles := iterx.FlatMap(ctx, users, func(u DemoUser) iter.Seq[string] {
		return slices.Values(u.Roles)
	})

	for role := range roles {
		fmt.Println(role)
	}
}
Output:
admin
editor
viewer

func Flatten

func Flatten[T any](ctx context.Context, seq iter.Seq[[]T]) iter.Seq[T]

Flatten converts a sequence of slices into a flat sequence of elements.

Example

ExampleFlatten demonstrates unrolling an iterator of slices into a flat iterator.

package main

import (
	"context"
	"fmt"
	"slices"

	"github.com/MostafaMagdSalama/vortex/iterx"
)

func main() {
	ctx := context.Background()
	batches := slices.Values([][]int{
		{1, 2},
		{3},
		{4, 5, 6},
	})

	for v := range iterx.Flatten(ctx, batches) {
		fmt.Println(v)
	}
}
Output:
1
2
3
4
5
6

func ForEach

func ForEach[T any](ctx context.Context, seq iter.Seq[T], fn func(T))

ForEach calls fn for every element in the sequence.

Example

ExampleForEach demonstrates running a non-failing side effect on every item.

package main

import (
	"context"
	"fmt"
	"slices"

	"github.com/MostafaMagdSalama/vortex/iterx"
)

func main() {
	ctx := context.Background()
	messages := slices.Values([]string{"hello", "world"})

	iterx.ForEach(ctx, messages, func(msg string) {
		fmt.Println("Processed:", msg)
	})
}
Output:
Processed: hello
Processed: world

func Map

func Map[T, U any](ctx context.Context, seq iter.Seq[T], fn func(T) U) iter.Seq[U]

Map transforms each element using fn.

Example

ExampleMap demonstrates transforming items from one type to another.

package main

import (
	"context"
	"fmt"
	"slices"
	"strings"

	"github.com/MostafaMagdSalama/vortex/iterx"
)

type DemoUser struct {
	Name  string
	Roles []string
}

func main() {
	ctx := context.Background()
	users := slices.Values([]DemoUser{
		{Name: "Alice"},
		{Name: "Bob"},
	})

	names := iterx.Map(ctx, users, func(u DemoUser) string {
		return strings.ToUpper(u.Name) // U is string
	})

	for name := range names {
		fmt.Println(name)
	}
}
Output:
ALICE
BOB

func Reverse

func Reverse[T any](ctx context.Context, seq iter.Seq[T]) iter.Seq[T]

Reverse collects the sequence into memory and yields it in reverse order.

Example

ExampleReverse demonstrates yielding the sequence in reverse order.

package main

import (
	"context"
	"fmt"
	"slices"

	"github.com/MostafaMagdSalama/vortex/iterx"
)

func main() {
	ctx := context.Background()
	steps := slices.Values([]string{"step 1", "step 2", "step 3"})

	for step := range iterx.Reverse(ctx, steps) {
		fmt.Println(step)
	}
}
Output:
step 3
step 2
step 1

func Take

func Take[T any](ctx context.Context, seq iter.Seq[T], n int) iter.Seq[T]

Take returns the first n elements.

Example

ExampleTake demonstrates grabbing just the first N elements. Particularly useful with endless streams or large files.

package main

import (
	"context"
	"fmt"
	"slices"

	"github.com/MostafaMagdSalama/vortex/iterx"
)

func main() {
	ctx := context.Background()
	items := slices.Values([]int{10, 20, 30, 40, 50, 60})

	top3 := iterx.Take(ctx, items, 3)

	for v := range top3 {
		fmt.Println(v)
	}
}
Output:
10
20
30

func TakeWhile

func TakeWhile[T any](ctx context.Context, seq iter.Seq[T], fn func(T) bool) iter.Seq[T]

TakeWhile yields values from the sequence as long as fn returns true. Iteration stops as soon as fn evaluates to false for the first time.

Example

ExampleTakeWhile demonstrates reading a stream until a condition stops being met. For instance: reading lines from a log until a marker is reached.

package main

import (
	"context"
	"fmt"
	"slices"

	"github.com/MostafaMagdSalama/vortex/iterx"
)

func main() {
	ctx := context.Background()
	logs := slices.Values([]string{"ok", "ok", "ok", "error", "ok"})

	for v := range iterx.TakeWhile(ctx, logs, func(s string) bool {
		return s != "error"
	}) {
		fmt.Println(v)
	}
}
Output:
ok
ok
ok

func Validate

func Validate[T any](ctx context.Context, seq iter.Seq[T], fn func(T) (bool, string), onError func(ValidationError[T])) iter.Seq[T]

Validate conditionally streams elements by evaluating fn(item). If fn yields {false, reason}, the onError callback is triggered with a ValidationError, and the element is discarded from the resulting sequence.

Example

ExampleValidate demonstrates checking structures and extracting validation metadata on failing entries without stopping the processing pipeline.

package main

import (
	"context"
	"fmt"
	"slices"
	"strings"

	"github.com/MostafaMagdSalama/vortex/iterx"
)

func main() {
	ctx := context.Background()
	emails := slices.Values([]string{"test@example.com", "invalid-email", "hello@world.com"})

	valid := iterx.Validate(ctx, emails,
		func(email string) (bool, string) {
			if !strings.Contains(email, "@") {
				return false, "missing @"
			}
			return true, ""
		},
		func(err iterx.ValidationError[string]) {
			fmt.Printf("Validation error: %s - %s\n", err.Item, err.Reason)
		},
	)

	for email := range valid {
		fmt.Println("Processed valid email:", email)
	}
}
Output:
Processed valid email: test@example.com
Validation error: invalid-email - missing @
Processed valid email: hello@world.com

func Zip

func Zip[A, B any](ctx context.Context, a iter.Seq[A], b iter.Seq[B]) iter.Seq[[2]any]

Zip combines two sequences into pairs, yielding [2]any{a, b} for each corresponding element. It stops as soon as the shortest sequence runs out.

Example

ExampleZip demonstrates combining two synchronised streams into pairs.

package main

import (
	"context"
	"fmt"
	"slices"

	"github.com/MostafaMagdSalama/vortex/iterx"
)

func main() {
	ctx := context.Background()
	keys := slices.Values([]string{"A", "B", "C"})
	values := slices.Values([]int{100, 200}) // Notice: shorter sequence!

	for pair := range iterx.Zip(ctx, keys, values) {
		fmt.Printf("%v: %v\n", pair[0], pair[1])
	}
}
Output:
A: 100
B: 200

Types

type ValidationError

type ValidationError[T any] struct {
	Item   T
	Reason string
}

ValidationError represents an item that failed validation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL