goiteratorhelper

package module
v0.0.23 Latest Latest
Warning

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

Go to latest
Published: Sep 21, 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)

Typical Usages

This section provides examples of common iterator operations. Each example has a dedicated example test.

1. Basic Operations
Creating Iterators

See Example_creatingIterators for creating iterators from slices, ranges, repeated values, and single values.

// From slices, Range, Repeat, Once
// From slice (std): 1 - 3
slice_iter := slices.Values([]int{1, 2, 3})
// Range: 10 - 14 (inclusive-exclusive range)
// To control inclusiveness, use RangeInclusive
range_iter := hiter.Range(10, 15)
// Repeat: [hi hi hi]
repeat_iter := hiter.Repeat("hi", 3)
// Once: [42]
once_iter := hiter.Once(42)
Combining Iterators

See Example_concat for combining multiple iterators.

first := slices.Values([]int{1, 2, 3})
second := slices.Values([]int{10, 11})
third := slices.Values([]int{20, 21, 22})

combined := hiter.Concat(first, second, third)
// Output: [1 2 3 10 11 20 21 22]
Transforming Data

See Example_mapAndFilter for transforming iterators with Map and Filter.

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

doubled := hiter.Map(func(n int) int { return n * 2 }, numbers)
// 2, 4, 6, 8, 10, 12
evens := hiter.Filter(func(n int) bool { return n%2 == 0 }, numbers)
// 2, 4, 6
Aggregation

See Example_reduce for basic aggregation operations.

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

sum := hiter.Reduce(func(acc, val int) int { return acc + val }, 0, numbers)
// Sum: 15
// For this simple case use instead
sum := hiter.Sum(numbers)
// Sum: 15

product := hiter.Reduce(func(acc, val int) int { return acc * val }, 1, numbers)
// Product: 120
Group and Reduce

See Example_reduceGroup for grouping by key and aggregating values.

grouped := hiter.ReduceGroup(
    func(acc, val int) int { return acc + val },
    0,
    pairs,
)
// Result: map[fruit:55 vegetable:45]
String Operations

See Example_stringJoin for joining iterator values into strings.

words := slices.Values([]string{"go", "iterator", "helper"})
collected := stringsiter.Collect(words)
// Result: "goiteratorhelper"
joined := stringsiter.Join("-", words)
// Result: "go-iterator-helper"
2. Error Handling
Error-Aware Iteration

See Example_tryForEach for processing until errors occur.

err := hiter.TryForEach(func(item string) { /* process */ }, data)
JSON Stream Error Handling

See Example_errboxJSON for handling errors in JSON streams.

decoder := json.NewDecoder(reader)
jsonBox := errbox.New(encodingiter.Decode[Person](decoder))
for person := range jsonBox.IntoIter() {
    // Process valid records
}
if err := jsonBox.Err(); err != nil {
    // Handle stream errors
}
3. Advanced Patterns
Moving Averages with Windows

See Example_windowMovingAverage for sliding window calculations.

windows := hiter.Window(data, 3)
averages := hiter.Map(func(w []int) float64 {
    return float64(hiter.Sum(slices.Values(w))) / float64(len(w))
}, windows)
Flattening Nested Structures

See Example_flatten for flattening nested arrays.

flattened := hiter.Flatten(slices.Values(nested))
// [[a b] [c] [d e f]] → [a b c d e f]
Resumable Iteration

See Example_resumable for pausable and resumable iteration.

resumable := iterable.NewResumable(source)
batch1 := hiter.Limit(3, resumable.IntoIter())  // [1 2 3]
batch2 := hiter.Limit(3, resumable.IntoIter())  // [4 5 6]
remaining := resumable.IntoIter()               // [7 8 9]

hiter

Helpers for iterator.

This package avoids re-implementing those which defined in standard or quasi-standard libraries. Namely slices, maps, x/exp/xiter (the proposal was withdrawn, at this time I'm not seeing what will be implemented in the furure hypothesis 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 (e.g. reflectiter defines iterator souces/adapters for std package refect). Packages nested under other package are flattened, e.g. encodingiter defines helpers for encoding/json, encoding/xml, encoding/csv and so on.

Deprecated: x/exp/xiter

Deprecated: you should no longer use this package since the proposal is withdrawn. hiter re-defines equivalents so you can use these in there. The proposal was wound down because the author saw Go iterator was too young. Once Go iterator gets matured in the community, proposal might again be proposed. At that time signatures of functions would be changed if the community finds better conventions.

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
After Go 1.25

Seems nothing

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

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"
)

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,
				hiter.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 [hiter.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 (Concat)

Example_concat demonstrates combining multiple iterators

package main

import (
	"fmt"
	"slices"

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

func main() {
	first := slices.Values([]int{1, 2, 3})
	second := slices.Values([]int{10, 11})
	third := slices.Values([]int{20, 21, 22})

	combined := hiter.Concat(first, second, third)
	fmt.Println("Combined:", slices.Collect(combined))

}
Output:

Combined: [1 2 3 10 11 20 21 22]
Example (CreatingIterators)

Example_creatingIterators demonstrates creating iterators from various sources

package main

import (
	"fmt"
	"slices"

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

func main() {
	// From slices
	slice_iter := slices.Values([]int{1, 2, 3})
	fmt.Println("From slice:", slices.Collect(slice_iter))

	// Using Range
	range_iter := hiter.Range(10, 15)
	fmt.Println("Range:", slices.Collect(range_iter))

	// Using Repeat
	repeat_iter := hiter.Repeat("hi", 3)
	fmt.Println("Repeat:", slices.Collect(repeat_iter))

	// Using Once
	once_iter := hiter.Once(42)
	fmt.Println("Once:", slices.Collect(once_iter))

}
Output:

From slice: [1 2 3]
Range: [10 11 12 13 14]
Repeat: [hi hi hi]
Once: [42]
Example (Dec_enc_round_trip)
package main

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

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

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,
		hiter.Map(
			func(m map[string]string) map[string]string {
				return maps.Collect(
					hiter.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 (ErrboxJSON)

Example_errboxJSON demonstrates error handling in JSON streams

package main

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

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

func main() {
	// JSON stream with an invalid entry
	jsonData := `{"id":1,"name":"Alice"}
{"id":2,"name":"Bob"}
{"invalid json syntax
{"id":3,"name":"Charlie"}`

	type Person struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	}

	decoder := json.NewDecoder(bytes.NewReader([]byte(jsonData)))
	jsonBox := errbox.New(encodingiter.Decode[Person](decoder))

	var validRecords []Person
	for person := range jsonBox.IntoIter() {
		validRecords = append(validRecords, person)
		fmt.Printf("Decoded: %+v\n", person)
	}

	// Check for errors after processing
	if err := jsonBox.Err(); err != nil {
		fmt.Printf("Stream error after %d valid records: %v\n", len(validRecords), err)
	}

}
Output:

Decoded: {ID:1 Name:Alice}
Decoded: {ID:2 Name:Bob}
Stream error after 2 valid records: invalid character '\n' in string literal
Example (ErrboxXML)

Example_errboxXML demonstrates error handling in XML streams

package main

import (
	"bytes"
	"encoding/xml"
	"fmt"

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

func main() {
	// XML stream with mixed valid and invalid entries
	xmlData := `<item><id>1</id><name>First</name></item>
<item><id>2</id><name>Second</name></item>
<item><id>3<name>Third</name></item>
<item><id>4</id><name>Fourth</name></item>`

	type Item struct {
		ID   int    `xml:"id"`
		Name string `xml:"name"`
	}

	decoder := xml.NewDecoder(bytes.NewReader([]byte(xmlData)))
	xmlBox := errbox.New(encodingiter.Decode[Item](decoder))

	validCount := 0
	for item := range xmlBox.IntoIter() {
		validCount++
		fmt.Printf("Valid item: %+v\n", item)
	}

	if err := xmlBox.Err(); err != nil {
		fmt.Printf("Parsing stopped after %d valid items: error in XML\n", validCount)
	}

}
Output:

Valid item: {ID:1 Name:First}
Valid item: {ID:2 Name:Second}
Parsing stopped after 2 valid items: error in XML
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"
)

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

	erroneous := hiter.Pairs(
		hiter.Range(0, 6),
		hiter.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 (Flatten)

Example_flatten demonstrates flattening nested structures

package main

import (
	"fmt"
	"slices"

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

func main() {
	// Nested arrays representing grouped data
	grouped := [][]string{
		{"apple", "banana"},
		{"carrot"},
		{"dog", "elephant", "fox"},
		{"guitar"},
	}

	// Flatten the nested structure
	flattened := hiter.Flatten(slices.Values(grouped))

	fmt.Println("Grouped:", grouped)
	fmt.Println("Flattened:", slices.Collect(flattened))

}
Output:

Grouped: [[apple banana] [carrot] [dog elephant fox] [guitar]]
Flattened: [apple banana carrot dog elephant fox guitar]
Example (FlattenF)

Example_flattenF demonstrates FlattenF for pair flattening

package main

import (
	"fmt"
	"slices"

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

func main() {
	// Categories with items
	categories := [][]string{{"fruits", "vegetables"}, {"proteins"}}
	counts := []int{10, 5}

	// FlattenF expands the first element (categories) while keeping the second
	expanded := hiter.FlattenF(hiter.Pairs(slices.Values(categories), slices.Values(counts)))

	fmt.Println("Category-Count pairs:")
	for category, count := range expanded {
		fmt.Printf("  %s: %d items\n", category, count)
	}

}
Output:

Category-Count pairs:
  fruits: 10 items
  vegetables: 10 items
  proteins: 5 items
Example (FlattenL)

Example_flattenL demonstrates FlattenL for second element flattening

package main

import (
	"fmt"
	"slices"

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

func main() {
	// Products with multiple prices (different stores)
	products := []string{"laptop", "phone"}
	prices := [][]float64{{999.99, 1099.99, 899.99}, {599.99, 649.99}}

	// FlattenL expands the second element (prices) while keeping the first
	expanded := hiter.FlattenL(hiter.Pairs(slices.Values(products), slices.Values(prices)))

	fmt.Println("Product-Price combinations:")
	for product, price := range expanded {
		fmt.Printf("  %s: $%.2f\n", product, price)
	}

}
Output:

Product-Price combinations:
  laptop: $999.99
  laptop: $1099.99
  laptop: $899.99
  phone: $599.99
  phone: $649.99
Example (MapAndFilter)

Example_mapAndFilter demonstrates transforming iterators

package main

import (
	"fmt"
	"slices"

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

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

	// Map to double values
	doubled := hiter.Map(func(n int) int { return n * 2 }, numbers)
	fmt.Println("Doubled:", slices.Collect(doubled))

	// Filter even numbers
	numbers2 := slices.Values([]int{1, 2, 3, 4, 5, 6})
	evens := hiter.Filter(func(n int) bool { return n%2 == 0 }, numbers2)
	fmt.Println("Evens:", slices.Collect(evens))

	// Chain Map and Filter
	numbers3 := slices.Values([]int{1, 2, 3, 4, 5, 6})
	doubled_evens := hiter.Filter(
		func(n int) bool { return n%4 == 0 },
		hiter.Map(func(n int) int { return n * 2 }, numbers3),
	)
	fmt.Println("Doubled evens:", slices.Collect(doubled_evens))

}
Output:

Doubled: [2 4 6 8 10 12]
Evens: [2 4 6]
Doubled evens: [4 8 12]
Example (Peek_and_continue)
package main

import (
	"fmt"

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

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 hiter.Limit(3, src) {
		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 hiter.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 (Reduce)

Example_reduce demonstrates aggregation with Reduce

package main

import (
	"fmt"
	"slices"

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

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

	// Sum using Reduce
	sum := hiter.Reduce(func(acc, val int) int { return acc + val }, 0, numbers)
	fmt.Println("Sum:", sum)

	// Product using Reduce
	numbers2 := slices.Values([]int{1, 2, 3, 4, 5})
	product := hiter.Reduce(func(acc, val int) int { return acc * val }, 1, numbers2)
	fmt.Println("Product:", product)

}
Output:

Sum: 15
Product: 120
Example (ReduceGroup)

Example_reduceGroup demonstrates grouping and aggregation

package main

import (
	"fmt"
	"slices"

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

func main() {
	// Group by key and sum values
	pairs := hiter.Pairs(
		slices.Values([]string{"fruit", "vegetable", "fruit", "fruit", "vegetable"}),
		slices.Values([]int{10, 20, 30, 15, 25}),
	)

	grouped := hiter.ReduceGroup(
		func(acc, val int) int { return acc + val },
		0,
		pairs,
	)
	fmt.Println("Grouped sums:", grouped)

}
Output:

Grouped sums: map[fruit:55 vegetable:45]
Example (Resumable)

Example_resumable demonstrates pausable and resumable iteration

package main

import (
	"fmt"
	"slices"

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

func main() {
	// Create a resumable iterator from a range
	source := hiter.Range(1, 10)
	resumable := iterable.NewResumable(source)

	// Process in batches
	fmt.Println("Processing in batches:")

	// Batch 1: Take first 3 elements
	batch1 := hiter.Limit(3, resumable.IntoIter())
	fmt.Println("Batch 1:", slices.Collect(batch1))

	// Batch 2: Take next 3 elements
	batch2 := hiter.Limit(3, resumable.IntoIter())
	fmt.Println("Batch 2:", slices.Collect(batch2))

	// Batch 3: Get remaining elements
	remaining := resumable.IntoIter()
	fmt.Println("Remaining:", slices.Collect(remaining))

}
Output:

Processing in batches:
Batch 1: [1 2 3]
Batch 2: [4 5 6]
Remaining: [7 8 9]
Example (ResumablePeek)

Example_resumablePeek demonstrates peeking with resumable iterators

package main

import (
	"fmt"

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

func main() {
	// Data stream to process
	data := hiter.Range(100, 106)
	resumable := iterable.NewResumable(data)

	// Peek at first element without consuming
	first, _ := hiter.First(resumable.IntoIter())
	fmt.Printf("Peeked first: %d\n", first)

	// Process all elements (including the peeked one)
	fmt.Print("All elements: ")
	for val := range resumable.IntoIter() {
		fmt.Printf("%d ", val)
	}
	fmt.Println()

}
Output:

Peeked first: 100
All elements: 101 102 103 104 105
Example (StringJoin)

Example_stringJoin demonstrates joining strings with an iterator

package main

import (
	"fmt"
	"slices"

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

func main() {
	words := slices.Values([]string{"go", "iterator", "helper"})
	collected := stringsiter.Collect(words)
	fmt.Println("Collected:", collected)
	joined := stringsiter.Join("-", words)
	fmt.Println("Joined:", joined)

	// Join with different separator
	parts := slices.Values([]string{"2024", "12", "25"})
	date := stringsiter.Join("/", parts)
	fmt.Println("Date:", date)

}
Output:

Collected: goiteratorhelper
Joined: go-iterator-helper
Date: 2024/12/25
Example (Teeing)
package main

import (
	"fmt"
	"sync"

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

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 hiter.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
Example (TryFind)

Example_tryFind demonstrates error-aware searching

package main

import (
	"errors"
	"fmt"
	"slices"

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

func main() {
	// Create an iterator with potential errors
	data := hiter.Pairs(
		slices.Values([]int{1, 2, 3, 4, 5}),
		hiter.Concat(
			hiter.Repeat(error(nil), 2),
			hiter.Once(errors.New("processing error")),
			hiter.Repeat(error(nil), 2),
		),
	)

	// TryFind stops on first error
	val, idx, err := hiter.TryFind(func(n int) bool { return n > 2 }, data)
	if err != nil {
		fmt.Printf("Error encountered at search: %v\n", err)
		fmt.Printf("Last value: %d, index: %d\n", val, idx)
	} else {
		fmt.Printf("Found: value=%d at index=%d\n", val, idx)
	}

}
Output:

Error encountered at search: processing error
Last value: 0, index: -1
Example (TryForEach)

Example_tryForEach demonstrates error-aware iteration

package main

import (
	"errors"
	"fmt"
	"slices"

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

func main() {
	// Create an iterator with errors at specific positions
	data := hiter.Pairs(
		slices.Values([]string{"apple", "banana", "cherry", "date"}),
		hiter.Concat(
			hiter.Repeat(error(nil), 2),
			hiter.Once(errors.New("bad fruit")),
			hiter.Once(error(nil)),
		),
	)

	count := 0
	err := hiter.TryForEach(func(fruit string) {
		count++
		fmt.Printf("Processing: %s\n", fruit)
	}, data)

	if err != nil {
		fmt.Printf("Stopped after %d items due to: %v\n", count, err)
	} else {
		fmt.Printf("Processed all %d items successfully\n", count)
	}

}
Output:

Processing: apple
Processing: banana
Stopped after 2 items due to: bad fruit
Example (WindowMovingAverage)

Example_windowMovingAverage demonstrates sliding window for moving averages

package main

import (
	"fmt"
	"slices"

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

func main() {
	// Data for moving average calculation
	data := []int{10, 20, 30, 40, 50, 60, 70, 80}
	windowSize := 3

	// Create sliding windows
	windows := hiter.Window(data, windowSize)

	// Calculate average for each window
	averages := hiter.Map(func(window []int) float64 {
		sum := hiter.Sum(slices.Values(window))
		return float64(sum) / float64(len(window))
	}, windows)

	fmt.Println("Data:", data)
	fmt.Printf("Moving averages (window=%d): %.1f\n", windowSize, slices.Collect(averages))

}
Output:

Data: [10 20 30 40 50 60 70 80]
Moving averages (window=3): [20.0 30.0 40.0 50.0 60.0 70.0]
Example (WindowSeq)

Example_windowSeq demonstrates WindowSeq with iterator-based windows

package main

import (
	"fmt"
	"iter"
	"slices"

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

func main() {
	// Temperature readings
	temperatures := []float64{22.5, 23.0, 22.8, 23.5, 24.0, 23.8, 23.2}

	// Create iterator-based sliding windows
	windowSeqs := hiter.WindowSeq(3, slices.Values(temperatures))

	// Find max temperature in each window
	maxTemps := hiter.Map(func(window iter.Seq[float64]) float64 {
		max := 0.0
		for temp := range window {
			if temp > max {
				max = temp
			}
		}
		return max
	}, windowSeqs)

	fmt.Printf("Max temperatures in sliding windows: %.1f\n", slices.Collect(maxTemps))

}
Output:

Max temperatures in sliding windows: [23.0 23.5 24.0 24.0 24.0]

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