gcf

package module
v0.0.9 Latest Latest
Warning

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

Go to latest
Published: Sep 8, 2022 License: MIT Imports: 3 Imported by: 0

README

gcf

gcf (Go Colletion Framework) is a library that provides various collection operations using Generics.
By operating on the collection using a common interface, you can easily composite the operations.

Go Reference codecov Go Report Card

Motivation

I wanted a functions that allows Go to easily composite and use processes instead of using basic syntax such as for and if.

Until now, it was difficult to provide processing to multiple types with the same interface, but since the support for Generics in Go 1.18 made this implementation easier, gcf was actually built as a library.

Example

Take as an example the process of extracting only odd numbers from the elements of a slice and returning those numbers by 3 times.

func Odd3(s []int) []int {
    var r []int
    for _, v := range s {
        if v%2 == 0 {
            continue
        }
        r := append(v*3)
    }
    return r
}

When using gcf, implement as follows.

// var s []int

itb := gcf.FromSlice(s)
itb = gcf.Filter(itb, func(v int) bool {
    return v%2 > 0
})
itb = gcf.Map(itb, func(v int) int {
    return v * 3
})

// Get the processing result as a slice
r := gcf.ToSlice(itb)

This example is meant to show how to use it briefly.
Replacing inline processing with gcf will significantly reduce the performance, so it is not recommended to rewrite processing that can be easily implemented and managed inline using gcf.

Environment

  • Go 1.18 or 1.19

Since gcf uses Generics feature, version 1.18 or higher is required.
We also have a container usage environment for vscode that you can use if you do not want to install new Go version in your local environment.
(See below .devcontainer)

Installation

Install by using go get on the directory under the control of the Go module.

go get -d github.com/meian/gcf

Design

Implements by Iterator

gcf is designed to composite processing with the Iterator pattern.
Some processes may allocate memory internally, but most processes avoid unnecessary memory allocations in the middle of the process.

Iterable + Iterator

Each function returns Iterable[T], which only has the ability to generate Iterator[T] by Iterator().
Iterator[T] moves the element position next by MoveNext(), and gets current element by Current().
Functions is composited by Iterable[T], and the state is keep only in the Iterator[T], which makes it easy to reuse the generated composition.

MoveNext + Current

In Iterator implementation, you may see set of functions that uses HasNext() to check for the next element and Next() to move to the next element and return the element.
In gcf, we implemented that MoveNext() moves to the next element and returns move result, and Current() returns the current element.
This is because we took advantage to get current value multiple times without changing, rather than providing next check without changing.

Top-level functions

In libraries of collection operations in other languages, the processing is often defined by methods so that we can use method chain, but we implemented by top-level functions.
This is because generics in Go cannot define type parameters at the method level, so some functions cannot be provided by methods, and if only some functions are provided by methods, the processing cannot be maintained consistency.
If it is implemented to define type parameters at the method level as a result of future version upgrades of Go, we will consider providing method chain functions.

Thread Safety

The exported functions that generate Iterables are thread-safe and can be accessed from multiple go routines at the same time.
The Iterator() in each Iterable is also thread-safe and can be called from multiple go routines at the same time.
In MoveNext() and Current() in each Iterator, thread-safe is not guaranteed , so when sharing between go routines, separate the processing with mutex etc. as needs.
The process of retrieving elements from Iterable, such as ToSlice(), is thread-safe.

It is not currently implemented, but it is undecided how the thread-safe will change when functions for channel is implemented.

Performance

The performance of gcf has the following characteristics.

  • It takes a processing time proportional to the number of elements in the collection and the number of processes to be combined.
  • Overwhelmingly slower than in-line processing (about 70 times)
  • About 4 times slower than function call without allocation
  • Overwhelmingly faster than channel processing (about 60 times)

Due to the characteristics of the library, it is processed repeatedly, so it is not recommended to use it for processing that requires severe processing speed.

Please refer to the Benchmark README for details.

Under the consideration

  • channel function implements
    • Create Iterable from channel
    • Get the result of Iterable on channel
    • It's suspended cause I don't understand good design.
  • Zip
    • Combine multiple Iterable elements into one Iterable.
    • We plan to refer to other implementations for how to handle when the number of elements of each Iterable is different.

Configure README Display Language

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ToSlice

func ToSlice[T any](itb Iterable[T]) []T

ToSlice makes slice of elements listed in Iterable.

itb := gcf.FromSlice([]int{1, 2, 3})
s := gcf.ToSlice(itb)

Types

type Iterable

type Iterable[T any] interface {
	// Iterator create Iterator[T] instance.
	Iterator() Iterator[T]
}

Iterable provide Iterator creation.

func Concat

func Concat[T any](itb1 Iterable[T], itb2 Iterable[T]) Iterable[T]

Concat makes Iterable elements concatenated of itb1 and itb2.

itb1 := gcf.FromSlice([]int{1, 2, 3})
itb2 := gcf.FromSlice([]int{4, 5, 6})
itbc := gcf.Concat(itb1, itb2)
Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	itb1 := gcf.FromSlice([]int{1, 2, 3})
	itb2 := gcf.FromSlice([]int{5, 6, 7})
	itb := gcf.Concat(itb1, itb2)
	fmt.Println(gcf.ToSlice(itb))
	// [ 1 2 3 5 6 7 ]
}
Output:

func Distinct

func Distinct[T comparable](itb Iterable[T]) Iterable[T]

Distinct makes Iterable contains unique elements. Inner elements is restrict by comparable constraint.

itb := gcf.FromSlice([]int{1, 2, 3, 3, 4, 2, 5})
itb = gcf.Distinct(itb)

Currently, result order is determined, but on spec, is undefined.

Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	itb := gcf.FromSlice([]int{1, 4, 2, 3, 2, 3, 1, 2})
	itb = gcf.Distinct(itb)
	for it := itb.Iterator(); it.MoveNext(); {
		fmt.Println(it.Current())
	}
}
Output:

1
2
3
4

func Filter

func Filter[T any](itb Iterable[T], filterFunc func(v T) bool) Iterable[T]

Filter makes Iterable with elements which filterFunc is true.

itb := gcf.FromSlice([]int{1, 2, 3})
itb = gcf.Filter(itb, func(v int) bool { return v%2 > 0 })

If filterFunc is nil, returns original Iteratable.

Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	s := []string{"dog", "cat", "mouse", "rabbit"}
	itb := gcf.FromSlice(s)
	itb = gcf.Filter(itb, func(v string) bool {
		return len(v) > 3
	})
	for it := itb.Iterator(); it.MoveNext(); {
		fmt.Println(it.Current())
	}
}
Output:

mouse
rabbit

func FlatMap

func FlatMap[T any, R any](itb Iterable[T], mapFunc func(v T) []R) Iterable[R]

FlatMap makes Iterable in elements in slice converted by mapFunc.

itbs := gcf.Func([]string{"a", "ab", "abc"})
itbi := gcf.Map(itbs, func(v string) int[] {
    var r := make([]int, 0)
    for _, c := range []rune(v) {
        r = append(r, int(c))
    }
})

If mapFunc is nil, return empty Iterable.

Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	s := []string{"dog", "cat", "mouse", "rabbit"}
	itb := gcf.FromSlice(s)
	itbs := gcf.FlatMap(itb, func(v string) []string {
		r := make([]string, 0, len(v))
		for _, c := range v {
			r = append(r, fmt.Sprintf("%c", c))
		}
		return r
	})
	fmt.Println(gcf.ToSlice(itbs))
}
Output:

[d o g c a t m o u s e r a b b i t]

func FromSlice

func FromSlice[T any](s []T) Iterable[T]

FromSlice makes Iterable from slice.

s := []int{1, 2, 3}
itb := gcf.FromSlice(s)

By change elements in base slice afrer this called, change is affected to Iterator. If you want no affects by change, you can use FromSliceImmutable.

func FromSliceImmutable

func FromSliceImmutable[T any](s []T) Iterable[T]

FromSliceImmutable makes Iterable from slice with immutable.

s := []int{1, 2, 3}
itb := gcf.FromSliceImmutable(s)

Input slice is duplicated to make immutable, so have some performance bottleneck.

func Map

func Map[T any, R any](itb Iterable[T], mapFunc func(v T) R) Iterable[R]

Map makes Iterable in elements convert by mapFunc.

itbs := gcf.Func([]string{"a", "ab", "abc"})
itbi := gcf.Map(itbs, func(v string) int { return len(v) })

If mapFunc is nil, return Iterable in zero value elements.

Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	s := []string{"dog", "cat", "mouse", "rabbit"}
	itb := gcf.FromSlice(s)
	itbs := gcf.Map(itb, func(v string) string {
		return fmt.Sprintf("%s:%d", v, len(v))
	})
	for it := itbs.Iterator(); it.MoveNext(); {
		fmt.Println(it.Current())
	}
}
Output:

dog:3
cat:3
mouse:5
rabbit:6

func Range

func Range[T constraints.Integer](start, end, step T) (Iterable[T], error)

Range makes Iterable with increasing or decreasing elements according to step.

itb := gcf.Range(1, 10, 3)

If step is positive, elements is enumerated from start to end with increment by step. If step is negative, elements is enumerated from start to end with decrement by step. If step is zero, returns error. If start equals to end, makes Iterable with one element. If direction of step is opposite to start and end, returns empty Iterable.

Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	itb, _ := gcf.Range(2, 10, 2)
	fmt.Println(gcf.ToSlice(itb))
	itb, _ = gcf.Range(10, 1, -2)
	fmt.Println(gcf.ToSlice(itb))
}
Output:

[2 4 6 8 10]
[10 8 6 4 2]

func Repeat

func Repeat[T any](value T, count int) Iterable[T]

Repeat makes Iterable that repeat value a count times.

itb = gcf.Repeat(1, 3)

If count is 0, returns empty Iterable. If count is negative, raises panic.

Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	itb := gcf.Repeat(1, 3)
	fmt.Println(gcf.ToSlice(itb))
}
Output:

[1 1 1]

func RepeatIterable

func RepeatIterable[T any](itb Iterable[T], count int) Iterable[T]

RepeatIterable makes Iterable that repeat elements in itb a count times.

s := []int{1, 2, 3}
itb := gcf.FromSlice(s)
itb = gcf.RepeatIterable(itb, 3)

If count is 0, returns empty Iterable. If count is negative, raises panic.

Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	s := []int{1, 2, 3}
	itb := gcf.FromSlice(s)
	itb = gcf.RepeatIterable(itb, 3)
	fmt.Println(gcf.ToSlice(itb))
}
Output:

[1 2 3 1 2 3 1 2 3]

func Reverse

func Reverse[T any](itb Iterable[T]) Iterable[T]

Reverse makes Iterable with reverse order elements.

itb := gcf.FromSlice([]int{1, 2, 3})
itb = gcf.Reverse(itb)
Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	itb := gcf.FromSlice([]int{1, 3, 2, 4})
	itb = gcf.Reverse(itb)
	fmt.Println(gcf.ToSlice(itb))
}
Output:

[4 2 3 1]

func Skip

func Skip[T any](itb Iterable[T], count int) Iterable[T]

Skip makes Iterable with elements excepting counted elements from ahead.

itb := gcf.FromSlice([]{1, 2, 3})
itb = gcf.Skip(itb, 2)

If count is 0, returns original Iterable. If count is negative, raises panic.

Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	itb := gcf.FromSlice([]int{1, 2, 3, 4, 5})
	itb = gcf.Skip(itb, 2)
	fmt.Println(gcf.ToSlice(itb))
}
Output:

[3 4 5]

func SkipLast added in v0.0.6

func SkipLast[T any](itb Iterable[T], count int) Iterable[T]

SkipLast makes Iterable with elements excepting counted elements from end.

itb := gcf.FromSlice([]{1, 2, 3})
itb = gcf.SkipLast(itb, 2)

If count is 0, returns original Iterable. If count is negative, raises panic.

Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	itb := gcf.FromSlice([]int{1, 2, 3, 4, 5})
	itb = gcf.SkipLast(itb, 3)
	fmt.Println(gcf.ToSlice(itb))
}
Output:

[1 2]

func SkipLastWhile added in v0.0.6

func SkipLastWhile[T any](itb Iterable[T], whileFunc func(v T) bool) Iterable[T]

SkipLastWhile makes Iterable with elements excepting elements that whileFunc is true from end.

itb := gcf.FromSlice([]{1, 2, 3})
itb = gcf.SkipLastWhile(itb, func(v int) bool { return v <= 2 })

If whileFunc is nil, returns original Iterable.

Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	itb := gcf.FromSlice([]int{1, 3, 5, 7, 2, 4, 6, 8, 9})
	itb = gcf.SkipLastWhile(itb, func(v int) bool { return v > 3 })
	s := gcf.ToSlice(itb)
	fmt.Println(s)
}
Output:

[1 3 5 7 2]

func SkipWhile

func SkipWhile[T any](itb Iterable[T], whileFunc func(v T) bool) Iterable[T]

SkipWhile makes Iterable with elements excepting elements that whileFunc is true from ahead.

itb := gcf.FromSlice([]{1, 2, 3})
itb = gcf.SkipWhile(itb, func(v int) bool { return v <= 2 })

If whileFunc is nil, returns original Iterable.

Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	itb := gcf.FromSlice([]int{1, 3, 5, 7, 2, 4, 6, 8, 9})
	itb = gcf.SkipWhile(itb, func(v int) bool { return v%2 > 0 })
	s := gcf.ToSlice(itb)
	fmt.Println(s)
}
Output:

[2 4 6 8 9]

func SortAsc

func SortAsc[T constraints.Ordered](itb Iterable[T]) Iterable[T]

SortAsc makes Iterable with sorted by ascending elements.

itb := gcf.FromSlice([]int{1, 3, 2})
itb = gcf.SortAsc(itb)
Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	itb := gcf.FromSlice([]int{3, 6, 7, 1, 5, 6, 2, 4, 5})
	itb = gcf.SortAsc(itb)
	fmt.Println(gcf.ToSlice(itb))
}
Output:

[1 2 3 4 5 5 6 6 7]

func SortBy

func SortBy[T any](itb Iterable[T], less func(x, y T) bool) Iterable[T]

SortBy makes iterable with elements sorted by provided less function.

type data struct { id int }
itb := gcf.FromSlice([]data{{1}, {3}, {2}})
itb = gcf.SortBy(itb, func(x, y data) bool { return x.id < y.id })

The less function takes x element and y element, and returns true if x is less than y.

Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	type data struct{ v int }
	itbi := gcf.FromSlice([]int{3, 6, 7, 1, 5, 6, 2, 4, 5})
	itb := gcf.Map(itbi, func(v int) data { return data{v} })
	itb = gcf.SortBy(itb, func(x, y data) bool { return x.v < y.v })
	fmt.Println(gcf.ToSlice(itb))
}
Output:

[{1} {2} {3} {4} {5} {5} {6} {6} {7}]

func SortDesc

func SortDesc[T constraints.Ordered](itb Iterable[T]) Iterable[T]

SortDesc makes Iterable with sorted by descending elements.

itb := gcf.FromSlice([]int{1, 3, 2})
itb = gcf.SortDesc(itb)
Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	itb := gcf.FromSlice([]int{3, 6, 7, 1, 5, 6, 2, 4, 5})
	itb = gcf.SortDesc(itb)
	fmt.Println(gcf.ToSlice(itb))
}
Output:

[7 6 6 5 5 4 3 2 1]

func Take

func Take[T any](itb Iterable[T], count int) Iterable[T]

Take makes Iterable with count elements from ahead.

itb := gcf.FromSlice([]{1, 2, 3})
itb = gcf.Take(itb, 2)

If count is 0, returns empty Iterable. If count is negative, raises panic.

Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	itb := gcf.FromSlice([]int{1, 2, 3, 4, 5})
	itb = gcf.Take(itb, 3)
	fmt.Println(gcf.ToSlice(itb))
}
Output:

[1 2 3]

func TakeLast added in v0.0.6

func TakeLast[T any](itb Iterable[T], count int) Iterable[T]

TakeLast makes Iterable with count elements from end.

itb := gcf.FromSlice([]{1, 2, 3})
itb = gcf.TakeLast(itb, 2)

If count is 0, returns empty Iterable. If count is negative, raises panic.

Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	itb := gcf.FromSlice([]int{1, 2, 3, 4, 5})
	itb = gcf.TakeLast(itb, 3)
	fmt.Println(gcf.ToSlice(itb))
}
Output:

[3 4 5]

func TakeLastWhile added in v0.0.6

func TakeLastWhile[T any](itb Iterable[T], whileFunc func(v T) bool) Iterable[T]

TakeLastWhile makes Iterable with elements in which whileFunc is true from end.

itb := gcf.FromSlice([]{1, 2, 3})
itb = gcf.TakeLastWhile(itb, func(v int) bool { return v >= 2 })

If whileFunc is nil, returns empty Iterable.

Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	itb := gcf.FromSlice([]int{1, 3, 5, 7, 2, 4, 6, 8, 9})
	itb = gcf.TakeLastWhile(itb, func(v int) bool { return v > 3 })
	s := gcf.ToSlice(itb)
	fmt.Println(s)
}
Output:

[4 6 8 9]

func TakeWhile

func TakeWhile[T any](itb Iterable[T], whileFunc func(v T) bool) Iterable[T]

TakeWhile makes Iterable with elements in which whileFunc is true from ahead.

itb := gcf.FromSlice([]{1, 2, 3})
itb = gcf.TakeWhile(itb, func(v int) bool { return v <= 2 })

If whileFunc is nil, returns empty Iterable.

Example
package main

import (
	"fmt"

	"github.com/meian/gcf"
)

func main() {
	itb := gcf.FromSlice([]int{1, 3, 5, 7, 2, 4, 6, 8, 9})
	itb = gcf.TakeWhile(itb, func(v int) bool { return v%2 > 0 })
	s := gcf.ToSlice(itb)
	fmt.Println(s)
}
Output:

[1 3 5 7]

type Iterator

type Iterator[T any] interface {
	// MoveNext proceed element position to next element.
	//
	//   var itb Iterator[int]
	//   it := itb.Iterator()
	//   for it.MoveNext() {
	//   	v := it.Current()
	//   	// some processes for iteration values
	//   }
	//
	// Return true if next value is exists or false if no next value.
	MoveNext() bool
	// Current return current element value.
	//
	// Return current value if iterable position or should get zero value if out of iterable position.
	// Note that return zero value before MoveNext is called.
	Current() T
}

Iterator provide iterative process for collections.

Jump to

Keyboard shortcuts

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