goiteratorhelper

package module
v0.0.19 Latest Latest
Warning

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

Go to latest
Published: May 23, 2025 License: MIT Imports: 0 Imported by: 0

README

go-iterator-helper

GoDoc

Helpers / converters / sources for iterators.

NOTE: things that are already iterator

Listed below are already iterators. This module will not define iterator sources for these kind. (in case older version of this modules has defined those, it should already be removed.)

// https://pkg.go.dev/go/token#FileSet.Iterate
func (s *FileSet) Iterate(f func(*File) bool)
// https://pkg.go.dev/log/slog#Record.Attrs
func (r Record) Attrs(f func(Attr) bool)
// https://pkg.go.dev/sync#Map.Range
func (m *Map) Range(f func(key, value any) bool)

hiter

Helpers for iterator.

This package avoids re-implementing those which defined in standard or quasi-standard libraries. Namely slices, maps, x/exp/xiter.

For example, Zip, Reduce are not defined since they will be implemented in xiter when #61898 accepted and merged.

Some ideas are stolen from https://jsr.io/@std/collections/doc, like Permutation and SumOf.

Each package named *iter corresponds to same name of std library. Packages nested under other package are flattened, e.g. encodingiter defines helpers for encoding/json, encoding/xml, encoding/csv and so on.

x/exp/xiter

Those listed in #61898.

This package is vendored so that you can use it anywhere without copy-and-pasting everywhere. It is already frozen; no change will be made even when xiter proposal got some modification.

Future deprecations

All functions will be noted as deprecated when std, golang.org/x/exp/xiter or similar quasi-std packages define equivalents.

It is just simply noted as deprecated: functions will remain same regardless of deprecation. You definitely should use std where possible, but you can keep using hiter.

Deprecated functions

After Go 1.24

Nothing.

https://tip.golang.org/doc/go1.24

Go 1.24 adds

  • Line, SplitSeq, SplitAfterSeq, FieldSeq and FieldFuncSeq to (strings|bytes).
    • stringsiter package defines similar functions but not exactly same. Those remain valid and maintained.
  • iterator sources to go/types

Documentation

Overview

This package only contains some example which did not fit to other packages. See other packages' godoc for usage.

Example (Async_chunk)
package main

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/ngicks/go-iterator-helper/hiter"
	"github.com/ngicks/go-iterator-helper/hiter/async"
)

func main() {
	var (
		wg sync.WaitGroup
		in = make(chan int)
	)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	wg.Add(1)
	go func() {
		defer wg.Done()
		ticker := time.NewTicker(500 * time.Nanosecond)
		defer ticker.Stop()
		_, _ = hiter.ChanSend(ctx, in, hiter.Tap(func(int) { <-ticker.C }, hiter.Range(0, 20)))
		close(in)
	}()

	first := true
	var count int
	for c := range async.Chunk(time.Microsecond, 5, hiter.Chan(ctx, in)) {
		count++
		for _, i := range c {
			if !first {
				fmt.Print(", ")
			}
			first = false
			fmt.Printf("%d", i)
		}
	}
	fmt.Println()
	wg.Wait()
	fmt.Printf("count > 0 = %t\n", count > 0)
}
Output:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
count > 0 = true
Example (Async_worker_channel)

Example async worker channel demonstrates usage of [hiter.Chan], [hiter.ChanSend]. It sends values from seq to worker running on separates goroutines. Workers work on values and then send results back to the main goroutine.

package main

import (
	"context"
	"fmt"
	"maps"
	"slices"
	"sync"

	"github.com/ngicks/go-iterator-helper/hiter"
	"github.com/ngicks/go-iterator-helper/hiter/iterable"
	"github.com/ngicks/go-iterator-helper/x/exp/xiter"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	works := []string{"foo", "bar", "baz"}

	in := make(chan string, 5)
	out := make(chan hiter.KeyValue[string, error])

	var wg sync.WaitGroup
	wg.Add(3)
	for range 3 {
		go func() {
			defer wg.Done()
			_, _ = hiter.ChanSend(
				ctx,
				out,
				xiter.Map(
					func(s string) hiter.KeyValue[string, error] {
						return hiter.KeyValue[string, error]{
							K: "✨" + s + "✨" + s + "✨",
							V: nil,
						}
					},
					hiter.Chan(ctx, in),
				),
			)
		}()
	}

	var wg2 sync.WaitGroup
	wg2.Add(1)
	go func() {
		defer wg2.Done()
		wg.Wait()
		close(out)
	}()

	_, _ = hiter.ChanSend(ctx, in, slices.Values(works))
	close(in)

	results := maps.Collect(hiter.FromKeyValue(hiter.Chan(ctx, out)))

	for result, err := range iterable.MapSorted[string, error](results).Iter2() {
		fmt.Printf("result = %s, err = %v\n", result, err)
	}

	wg2.Wait()

}
Output:

result = ✨bar✨bar✨, err = <nil>
result = ✨baz✨baz✨, err = <nil>
result = ✨foo✨foo✨, err = <nil>
Example (Async_worker_map)

Example async worker map demonstrates usage of async.Map. At the surface it is similar to [xiter.Map2]. Actually it calls mapper in separate goroutine. If you don't care about order of element, just send values to workers through a channel and send back through another channel.

package main

import (
	"context"
	"fmt"
	"slices"

	"github.com/ngicks/go-iterator-helper/hiter/async"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	works := []string{"foo", "bar", "baz"}

	// The order is kept.
	for result, err := range async.Map(
		ctx,
		/*queueLimit*/ 10,
		/*workerLimit*/ 5,
		/*mapper*/ func(ctx context.Context, s string) (string, error) {
			return "✨" + s + "✨" + s + "✨", nil
		},
		slices.Values(works),
	) {
		fmt.Printf("result = %s, err = %v\n", result, err)
	}
}
Output:

result = ✨foo✨foo✨, err = <nil>
result = ✨bar✨bar✨, err = <nil>
result = ✨baz✨baz✨, err = <nil>
Example (Async_worker_map_graceful_cancellation)
package main

import (
	"context"
	"fmt"
	"slices"

	"github.com/ngicks/go-iterator-helper/hiter/async"
	"github.com/ngicks/go-iterator-helper/hiter/mapper"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	works := []string{"foo", "bar", "baz"}

	workerCtx, cancelWorker := context.WithCancel(context.Background())
	defer cancelWorker()

	for result, err := range async.Map(
		ctx,
		/*queueLimit*/ 1,
		/*workerLimit*/ 1,
		/*mapper*/ func(ctx context.Context, s string) (string, error) {
			combined, cancel := context.WithCancel(ctx)
			defer cancel()
			go func() {
				select {
				case <-ctx.Done():
				case <-combined.Done():
				case <-workerCtx.Done():
				}
				cancel()
			}()
			if combined.Err() != nil {
				return "", combined.Err()
			}
			return "✨" + s + "✨" + s + "✨", nil
		},
		mapper.Cancellable(1, workerCtx, slices.Values(works)),
	) {
		fmt.Printf("result = %s, err = %v\n", result, err)
		cancelWorker()
	}
}
Output:

result = ✨foo✨foo✨, err = <nil>
result = ✨bar✨bar✨, err = <nil>
Example (Dec_enc_round_trip)
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"maps"
	"os"

	"github.com/ngicks/go-iterator-helper/hiter/encodingiter"
	"github.com/ngicks/go-iterator-helper/hiter/errbox"
	"github.com/ngicks/go-iterator-helper/x/exp/xiter"
)

func main() {
	src := []byte(`
	{"foo":"foo"}
	{"bar":"bar"}
	{"baz":"baz"}
	`)

	rawDec := json.NewDecoder(bytes.NewReader(src))
	dec := errbox.New(encodingiter.Decode[map[string]string](rawDec))

	enc := json.NewEncoder(os.Stdout)

	err := encodingiter.Encode(
		enc,
		xiter.Map(
			func(m map[string]string) map[string]string {
				return maps.Collect(
					xiter.Map2(
						func(k, v string) (string, string) { return k + k, v + v },
						maps.All(m),
					),
				)
			},
			dec.IntoIter(),
		),
	)

	fmt.Printf("dec error = %v\n", dec.Err())
	fmt.Printf("enc error = %v\n", err)
}
Output:

{"foofoo":"foofoo"}
{"barbar":"barbar"}
{"bazbaz":"bazbaz"}
dec error = <nil>
enc error = <nil>
Example (Error_handle)

Example error handle demonstrates various way to handle error.

package main

import (
	"errors"
	"fmt"
	"slices"

	"github.com/ngicks/go-iterator-helper/hiter"
	"github.com/ngicks/go-iterator-helper/hiter/errbox"
	"github.com/ngicks/go-iterator-helper/hiter/mapper"
	"github.com/ngicks/go-iterator-helper/x/exp/xiter"
)

func main() {
	var (
		errSample  = errors.New("sample")
		errSample2 = errors.New("sample2")
	)

	erroneous := hiter.Pairs(
		hiter.Range(0, 6),
		xiter.Concat(
			hiter.Repeat(error(nil), 2),
			hiter.Repeat(errSample2, 2),
			hiter.Once(errSample),
			hiter.Once(error(nil)),
		),
	)

	fmt.Println("TryFind:")
	v, idx, err := hiter.TryFind(func(i int) bool { return i > 0 }, erroneous)
	fmt.Printf("v = %d, idx = %d, err = %v\n", v, idx, err)
	v, idx, err = hiter.TryFind(func(i int) bool { return i > 5 }, erroneous)
	fmt.Printf("v = %d, idx = %d, err = %v\n", v, idx, err)
	fmt.Println()

	fmt.Println("TryForEach:")
	err = hiter.TryForEach(func(i int) { fmt.Printf("i = %d\n", i) }, erroneous)
	fmt.Printf("err = %v\n", err)
	fmt.Println()

	fmt.Println("TryReduce:")
	collected, err := hiter.TryReduce(func(c []int, i int) []int { return append(c, i) }, nil, erroneous)
	fmt.Printf("collected = %#v, err = %v\n", collected, err)
	fmt.Println()

	fmt.Println("HandleErr:")
	var handled error
	collected = slices.Collect(
		mapper.HandleErr(
			func(i int, err error) bool {
				handled = err
				return errors.Is(err, errSample2)
			},
			erroneous,
		),
	)
	fmt.Printf("collected = %#v, err = %v\n", collected, handled)
	fmt.Println()

	fmt.Println("*errbox.Box:")
	box := errbox.New(erroneous)
	collected = slices.Collect(box.IntoIter())
	fmt.Printf("collected = %#v, err = %v\n", collected, box.Err())
	fmt.Println()
}
Output:

TryFind:
v = 1, idx = 1, err = <nil>
v = 0, idx = -1, err = sample2

TryForEach:
i = 0
i = 1
err = sample2

TryReduce:
collected = []int{0, 1}, err = sample2

HandleErr:
collected = []int{0, 1}, err = sample

*errbox.Box:
collected = []int{0, 1}, err = sample2
Example (Peek_and_continue)
package main

import (
	"fmt"

	"github.com/ngicks/go-iterator-helper/hiter"
	"github.com/ngicks/go-iterator-helper/hiter/iterable"
	"github.com/ngicks/go-iterator-helper/x/exp/xiter"
)

func main() {
	// iterator that yields 0 to 9 sequentially.
	src := hiter.Range(0, 10)

	fmt.Println("It replays data if break-ed and resumed.")

	count := 3
	first := true
	for v := range src {
		count--
		if count < 0 {
			break
		}
		if !first {
			fmt.Print(", ")
		}
		first = false
		fmt.Printf("%d", v)
	}
	fmt.Println()
	fmt.Println("break and resume")
	first = true
	for v := range xiter.Limit(src, 3) {
		if !first {
			fmt.Print(", ")
		}
		first = false
		fmt.Printf("%d", v)
	}
	fmt.Print("\n\n")

	fmt.Println("converting it to be resumable.")
	resumable := iterable.NewResumable(src)

	v0, _ := hiter.First(resumable.IntoIter())
	fmt.Printf("first:  %d\n", v0)
	v1, _ := hiter.First(resumable.IntoIter())
	fmt.Printf("second: %d\n", v1)

	fmt.Println()
	fmt.Println("reconnect them to whole iterator.")
	first = true
	for v := range xiter.Concat(hiter.Once(v0), hiter.Once(v1), resumable.IntoIter()) {
		if !first {
			fmt.Print(", ")
		}
		first = false
		fmt.Printf("%d", v)
	}
	fmt.Println()

	fmt.Println("\nYou can achieve above also with iterable.Peekable")
	peekable := iterable.NewPeekable(src)
	fmt.Printf("%#v\n", peekable.Peek(5))
	first = true
	for v := range peekable.IntoIter() {
		if !first {
			fmt.Print(", ")
		}
		first = false
		fmt.Printf("%d", v)
	}
	fmt.Println()

}
Output:

It replays data if break-ed and resumed.
0, 1, 2
break and resume
0, 1, 2

converting it to be resumable.
first:  0
second: 1

reconnect them to whole iterator.
0, 1, 2, 3, 4, 5, 6, 7, 8, 9

You can achieve above also with iterable.Peekable
[]int{0, 1, 2, 3, 4}
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
Example (Teeing)
package main

import (
	"fmt"
	"sync"

	"github.com/ngicks/go-iterator-helper/hiter"
	"github.com/ngicks/go-iterator-helper/hiter/tee"
	"github.com/ngicks/go-iterator-helper/x/exp/xiter"
)

func main() {
	src := hiter.Range(0, 5)
	seqPiped, seq := tee.TeeSeqPipe(0, src)

	var found bool

	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		found = hiter.Contains(3, seqPiped.IntoIter())
		// Don't forget to discard all elements from seq!
		// Without this, tee could not proceed.
		hiter.Discard(seqPiped.IntoIter())
	}()

	for i := range xiter.Map(func(i int) int { return i * i }, seq.IntoIter()) {
		fmt.Printf("i = %02d\n", i)
	}
	wg.Wait()
	fmt.Printf("\nfound=%t\n", found)
}
Output:

i = 00
i = 01
i = 04
i = 09
i = 16

found=true

Directories

Path Synopsis
hiter defines iterator sources from various inputs, adapters, collectors.
hiter defines iterator sources from various inputs, adapters, collectors.
async
The package async defines asynchronous adapters.
The package async defines asynchronous adapters.
bufioiter
bufioiter defines iterator source/collector that corresponds to std library `bufio`.
bufioiter defines iterator source/collector that corresponds to std library `bufio`.
containeriter
containeriter defines iterator source/collector that corresponds to std library `container/*`.
containeriter defines iterator source/collector that corresponds to std library `container/*`.
cryptoiter
cryptoiter defines iterator source/collector that corresponds to std `crypto/*`.
cryptoiter defines iterator source/collector that corresponds to std `crypto/*`.
databaseiter
databaseiter defines iterator source/collector that corresponds to std library `database/*`.
databaseiter defines iterator source/collector that corresponds to std library `database/*`.
encodingiter
encodingiter defines iterator source/collector that corresponds to std library `encoding` and all its descendants (`encoding/*`)
encodingiter defines iterator source/collector that corresponds to std library `encoding` and all its descendants (`encoding/*`)
errbox
errbox boxes iter.Seq[V, error] and converts to iter.Seq[V].
errbox boxes iter.Seq[V, error] and converts to iter.Seq[V].
ioiter
ioiter defines iterator source/collector that corresponds to std library `io/*`.
ioiter defines iterator source/collector that corresponds to std library `io/*`.
iterable
Wrapper for iterable objects; heap, list, ring, slice, map, channel, etc.
Wrapper for iterable objects; heap, list, ring, slice, map, channel, etc.
iterreader
iterreader defines functions that converts an iterator to io.Reader.
iterreader defines functions that converts an iterator to io.Reader.
mapper
package mapper is collection of small mapping helpers.
package mapper is collection of small mapping helpers.
mathiter
mathiter defines iterator source/collector that corresponds to std `math/*`.
mathiter defines iterator source/collector that corresponds to std `math/*`.
reflectiter
reflectiter defines iterator source/collector that corresponds to std library `reflect`.
reflectiter defines iterator source/collector that corresponds to std library `reflect`.
stringsiter
stringsiter defines iterator source/collector that corresponds to std library `strings`.
stringsiter defines iterator source/collector that corresponds to std library `strings`.
tee
internal
x
exp/xiter
Code copied from
Code copied from

Jump to

Keyboard shortcuts

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