iterkit

package
v0.285.0 Latest Latest
Warning

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

Go to latest
Published: Mar 8, 2025 License: Apache-2.0 Imports: 12 Imported by: 2

Documentation

Overview

package iterators provide iterator implementations.

Summary

An Iterator's goal is to decouple the origin of the data from the consumer who uses that data. Most commonly, iterators hide whether the data comes from a specific database, standard input, or elsewhere. This approach helps to design data consumers that are not dependent on the concrete implementation of the data source, while still allowing for the composition and various actions on the received data stream. An Iterator represents an iterable list of element, which length is not known until it is fully iterated, thus can range from zero to infinity. As a rule of thumb, if the consumer is not the final destination of the data stream, it should use the pipeline pattern to avoid bottlenecks with local resources such as memory.

Resources

https://en.wikipedia.org/wiki/Iterator_pattern https://en.wikipedia.org/wiki/Pipeline_(software)

Index

Examples

Constants

View Source
const Break errorkit.Error = `iterators:break`

Variables

This section is empty.

Functions

func Batch

func Batch[T any](i iter.Seq[T], size int) iter.Seq[[]T]

func BatchWithWaitLimit

func BatchWithWaitLimit[T any](i iter.Seq[T], size int, waitLimit time.Duration) iter.Seq[[]T]
Example
var slow iter.Seq[int] = func(yield func(int) bool) {
	for {
		if 5 < rnd.IntBetween(0, 10) { // random slowness
			time.Sleep(time.Second / 3)
		}

		if !yield(rnd.Int()) {
			return
		}
	}
}

batched := iterkit.BatchWithWaitLimit(slow, 7, time.Second)

for vs := range batched {
	fmt.Printf("%#v\n", vs)
}

func Chan

func Chan[T any](ch <-chan T) iter.Seq[T]

Chan creates an iterator out from a channel

Example
ch := make(chan int)

i := iterkit.Chan(ch)

go func() {
	defer close(ch)
	ch <- 42
}()

for v := range i {
	fmt.Println(v) // 42 once
}

func CharRange

func CharRange(begin, end rune) iter.Seq[rune]

CharRange returns an iterator that will range between the specified `begin“ and the `end` rune.

Example
for char := range iterkit.CharRange('A', 'Z') {
	// prints characters between A and Z
	// A, B, C, D... Z
	fmt.Println(string(char))
}

func Collect

func Collect[T any](i iter.Seq[T]) []T
Example
var itr iter.Seq[int]

ints := iterkit.Collect(itr)
_ = ints

func Collect2

func Collect2[K, V, KV any](i iter.Seq2[K, V], m func(K, V) KV) []KV
Example
var itr iter.Seq2[string, int]

type T struct {
	S string
	I int
}

ints := iterkit.Collect2(itr, func(s string, i int) T {
	return T{S: s, I: i}
})
_ = ints

func CollectErr

func CollectErr[T any](i iter.Seq[T], e ErrFunc) ([]T, error)

func CollectErrIter

func CollectErrIter[T any](i iter.Seq2[T, error]) ([]T, error)
Example
var itr iter.Seq2[int, error] = func(yield func(int, error) bool) {
	for i := 0; i < 42; i++ {
		if !yield(i, nil) {
			return
		}
	}
}

vs, err := iterkit.CollectErrIter(itr)
_, _ = vs, err

func CollectPull

func CollectPull[T any](next func() (T, bool), stops ...func()) []T
Example
var itr iter.Seq[int] = iterkit.IntRange(1, 10)
vs := iterkit.CollectPull(iter.Pull(itr))
_ = vs

func CollectPullIter

func CollectPullIter[T any](itr PullIter[T]) ([]T, error)

func Count

func Count[T any](i iter.Seq[T]) int

Count will iterate over and count the total iterations number

Good when all you want is count all the elements in an iterator but don't want to do anything else.

Example
i := iterkit.Slice[int]([]int{1, 2, 3})
total := iterkit.Count[int](i)
_ = total // 3

func Count2

func Count2[K, V any](i iter.Seq2[K, V]) int
Example
itr := maps.All(map[string]int{
	"foo": 2,
	"bar": 4,
	"baz": 8,
})
iterkit.Count2(itr) // 3

func Empty

func Empty[T any]() iter.Seq[T]

Empty iterator is used to represent nil result with Null object pattern

Example
_ = iterkit.Empty[any]()

func Empty2

func Empty2[T1, T2 any]() iter.Seq2[T1, T2]

Empty2 iterator is used to represent nil result with Null object pattern

func Filter

func Filter[T any](i iter.Seq[T], filter func(T) bool) iter.Seq[T]
Example
var i iter.Seq[int]
i = iterkit.Slice([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
i = iterkit.Filter[int](i, func(n int) bool { return n > 2 })

for v := range i {
	fmt.Println(v)
}

func First

func First[T any](i iter.Seq[T]) (T, bool)

First decode the first next value of the iterator and close the iterator

func First2

func First2[K, V any](i iter.Seq2[K, V]) (K, V, bool)

First2 decode the first next value of the iterator and close the iterator

Example
var itr iter.Seq2[string, int] = func(yield func(string, int) bool) {
	for i := 0; i < 42; i++ {
		if !yield(strconv.Itoa(i), i) {
			return
		}
	}
}

k, v, ok := iterkit.First2(itr)
_, _, _ = k, v, ok

func ForEach

func ForEach[T any](i iter.Seq[T], fn func(T) error, errFuncs ...ErrFunc) (rErr error)

func FromPull

func FromPull[T any](next func() (T, bool), stops ...func()) iter.Seq[T]

func FromPull2

func FromPull2[K, V any](next func() (K, V, bool), stops ...func()) iter.Seq2[K, V]
func Head[T any](i iter.Seq[T], n int) iter.Seq[T]

Head takes the first n element, similarly how the coreutils "head" app works.

Example
inf42 := func(yield func(int) bool) {
	for /* infinite */ {
		if !yield(42) {
			return
		}
	}
}

i := iterkit.Head[int](inf42, 3)

vs := iterkit.Collect(i)
_ = vs // []{42, 42, 42}, nil

func IntRange

func IntRange(begin, end int) iter.Seq[int]

IntRange returns an iterator that will range between the specified `begin“ and the `end` int.

Example
for n := range iterkit.IntRange(1, 9) {
	// prints characters between 1 and 9
	// 1, 2, 3, 4, 5, 6, 7, 8, 9
	fmt.Println(n)
}

func Last

func Last[T any](i iter.Seq[T]) (T, bool)

func Limit

func Limit[V any](i iter.Seq[V], n int) iter.Seq[V]

func Map

func Map[To any, From any](i iter.Seq[From], transform func(From) To) iter.Seq[To]

Map allows you to do additional transformation on the values. This is useful in cases, where you have to alter the input value, or change the type all together. Like when you read lines from an input stream, and then you map the line content to a certain data structure, in order to not expose what steps needed in order to deserialize the input stream, thus protect the business rules from this information.

Example
rawNumbers := iterkit.Slice([]string{"1", "2", "42"})
numbers := iterkit.Map[int](rawNumbers, func(v string) int {
	return len(v)
})
_ = numbers

func Merge

func Merge[T any](is ...iter.Seq[T]) iter.Seq[T]

func Merge2

func Merge2[K, V any](is ...iter.Seq2[K, V]) iter.Seq2[K, V]

func Offset

func Offset[V any](i iter.Seq[V], offset int) iter.Seq[V]

func Once

func Once[T any](i iter.Seq[T]) iter.Seq[T]

func Once2

func Once2[K, V any](i iter.Seq2[K, V]) iter.Seq2[K, V]

func Reduce

func Reduce[R, T any](i iter.Seq[T], initial R, fn func(R, T) R) R
Example
raw := iterkit.Slice([]int{1, 2, 42})

_ = iterkit.Reduce[[]int](raw, nil, func(vs []int, v int) []int {
	return append(vs, v)
})

func ReduceErr

func ReduceErr[R, T any](i iter.Seq[T], initial R, fn func(R, T) (R, error)) (result R, rErr error)
Example
raw := iterkit.Slice([]string{"1", "2", "42"})

_, _ = iterkit.ReduceErr[[]int](raw, nil, func(vs []int, raw string) ([]int, error) {

	v, err := strconv.Atoi(raw)
	if err != nil {
		return nil, err
	}
	return append(vs, v), nil

})

func Reverse

func Reverse[T any](i iter.Seq[T]) iter.Seq[T]

Reverse will reverse the iteration direction.

WARNING

It does not work with infinite iterators, as it requires to collect all values before it can reverse the elements.

Example
itr := iterkit.IntRange(1, 3) // []int{1, 2, 3}
itr = iterkit.Reverse(itr)    // []int{3, 2, 1}
for range itr {
}

func SingleValue

func SingleValue[T any](v T) iter.Seq[T]

SingleValue creates an iterator that can return one single element and will ensure that Next can only be called once.

func Slice

func Slice[T any](slice []T) iter.Seq[T]

func Take

func Take[T any](next func() (T, bool), n int) []T

Take will take the next N value from a pull iterator.

func TakeAll

func TakeAll[T any](next func() (T, bool)) []T

TakeAll will take all the remaining values from a pull iterator.

Example
i := iterkit.Slice([]int{1, 2, 3, 4, 5})
next, stop := iter.Pull(i)
defer stop()
vs := iterkit.TakeAll(next)
_ = vs // []int{1, 2, 3, 4, 5}

func ToChan

func ToChan[T any](itr iter.Seq[T]) (_ <-chan T, cancel func())

func WithConcurrentAccess

func WithConcurrentAccess[T any](i iter.Seq[T]) (iter.Seq[T], func())

WithConcurrentAccess allows you to convert any iterator into one that is safe to use from concurrent access. The caveat with this is that this protection only allows 1 Decode call for each Next call.

Types

type ErrFunc

type ErrFunc = errorkit.ErrFunc

ErrFunc is the check function that can tell if currently an iterator that is related to the error function has an issue or not.

func BufioScanner

func BufioScanner[T string | []byte](s *bufio.Scanner, closer io.Closer, errFuncs ...ErrFunc) (iter.Seq[T], ErrFunc)

func FromErrIter

func FromErrIter[T any](i ErrIter[T]) (iter.Seq[T], ErrFunc)

FromErrIter will split an iter.Seq2[T, error] iterator into a iter.Seq[T] iterator plus an error retrival func.

Example
var sourceErrIter iter.Seq2[int, error]

i, errFunc := iterkit.FromErrIter(sourceErrIter)
for v := range i {
	fmt.Println(v)
}
if err := errFunc(); err != nil {
	fmt.Println(err.Error())
}

func MapErr

func MapErr[To any, From any](i iter.Seq[From], transform func(From) (To, error), errs ...ErrFunc) (iter.Seq[To], ErrFunc)
Example
rawNumbers := iterkit.Slice([]string{"1", "2", "42"})
numbers, finish := iterkit.MapErr[int](rawNumbers, strconv.Atoi)
_ = finish
_ = numbers

func Paginate

func Paginate[T any](
	ctx context.Context,
	more func(ctx context.Context, offset int) (values []T, hasNext bool, _ error),
	errFuncs ...ErrFunc,
) (iter.Seq[T], ErrFunc)

Paginate will create an iter.Seq[T] which can be used like any other iterator, Under the hood the "more" function will be used to dynamically retrieve more values when the previously called values are already used up.

If the more function has a hard-coded true for the "has next page" return value, then the pagination will interpret an empty result as "no more pages left".

Example
ctx := context.Background()
fetchMoreFoo := func(ctx context.Context, offset int) ([]Foo, bool, error) {
	const limit = 10
	query := url.Values{}
	query.Set("limit", strconv.Itoa(limit))
	query.Set("offset", strconv.Itoa(offset))
	resp, err := http.Get("https://api.mydomain.com/v1/foos?" + query.Encode())
	if err != nil {
		return nil, false, err
	}

	var values []FooDTO
	defer resp.Body.Close()
	dec := json.NewDecoder(resp.Body)
	dec.DisallowUnknownFields()
	if err := dec.Decode(&values); err != nil {
		return nil, false, err
	}

	vs, err := dtokit.Map[[]Foo](ctx, values)
	if err != nil {
		return nil, false, err
	}
	probablyHasNextPage := len(vs) == limit
	return vs, probablyHasNextPage, nil
}

foos, release := iterkit.Paginate(ctx, fetchMoreFoo)
_ = foos // foos can be called like any iterator,
// and under the hood, the fetchMoreFoo function will be used dynamically,
// to retrieve more values when the previously called values are already used up.
_ = release

type ErrIter

type ErrIter[T any] = iter.Seq2[T, error]

ErrIter is an iterator that can tell if a currently returned value has an issue or not.

func Error

func Error[T any](err error) ErrIter[T]

Error returns an Interface that only can do is returning an Err and never have next element

func ErrorF

func ErrorF[T any](format string, a ...any) ErrIter[T]

ErrorF behaves exactly like fmt.ErrorF but returns the error wrapped as iterator

func FromPullIter

func FromPullIter[T any](itr PullIter[T]) ErrIter[T]

func OnErrIterValue

func OnErrIterValue[To any, From any](itr ErrIter[From], pipeline func(itr iter.Seq[From]) iter.Seq[To]) ErrIter[To]

OnErrIterValue will apply a iterator pipeline on a given ErrIter

Example
var (
	input  iter.Seq2[int, error]
	output iter.Seq2[string, error]
)

output = iterkit.OnErrIterValue(input, func(itr iter.Seq[int]) iter.Seq[string] {
	// we receive an iterator without the error second value
	// we do our iterator manipulation like it doesn't have an error
	// then we return it back
	itr = iterkit.Map(itr, func(v int) int { return v * 3 })
	itr = iterkit.Filter(itr, func(i int) bool { return i%2 == 0 })
	return iterkit.Map(itr, strconv.Itoa)
})

// the returned iter have the pipeline applied,
// but the elements still contain the potential error value in case something went wrong.
_ = output

func ToErrIter

func ToErrIter[T any](i iter.Seq[T], errFuncs ...ErrFunc) ErrIter[T]

ToErrIter will turn a iter.Seq[T] into an iter.Seq2[T, error] iterator, and use the error function to yield potential issues with the iteration.

Example
seq1Iter := iterkit.Slice([]int{1, 2, 3})
errIter := iterkit.ToErrIter(seq1Iter)
for v, err := range errIter {
	if err != nil {
		// will be always nil for the []int slice
	}
	_ = v // 1, 2, 3...
}

type KV

type KV[K, V any] struct {
	K K
	V V
}

func CollectKV

func CollectKV[K, V any](i iter.Seq2[K, V]) []KV[K, V]
Example
var itr iter.Seq2[string, int]

ints := iterkit.CollectKV(itr)
_ = ints

type PullIter

type PullIter[V any] interface {
	// Next will ensure that Value returns the next item when executed.
	// If the next value is not retrievable, Next should return false and ensure Err() will return the error cause.
	Next() bool
	// Value returns the current value in the iterator.
	// The action should be repeatable without side effects.
	Value() V
	// Closer is required to make it able to cancel iterators where resources are being used behind the scene
	// for all other cases where the underling io is handled on a higher level, it should simply return nil
	io.Closer
	// Err return the error cause.
	Err() error
}

PullIter define a separate object that encapsulates accessing and traversing an aggregate object. Clients use an iterator to access and traverse an aggregate without knowing its representation (data structures). Interface design inspirited by https://golang.org/pkg/encoding/json/#Decoder https://en.wikipedia.org/wiki/Iterator_pattern

func ToPullIter

func ToPullIter[T any](itr ErrIter[T]) PullIter[T]

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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