diff --git a/Makefile b/Makefile index 3fd0943..0a5b573 100644 --- a/Makefile +++ b/Makefile @@ -6,4 +6,7 @@ test: bench: $(GOCMD) test -run=NONE -bench=. -benchmem ./... -.PHONY: linters-install lint test bench \ No newline at end of file +lint: + golangci-lint run + +.PHONY: lint test bench \ No newline at end of file diff --git a/README.md b/README.md index e6c4ce7..6f82527 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # pkg -![Project status](https://img.shields.io/badge/version-5.10.0-green.svg) +![Project status](https://img.shields.io/badge/version-5.11.0-green.svg) [![Build Status](https://travis-ci.org/go-playground/pkg.svg?branch=master)](https://travis-ci.org/go-playground/pkg) [![Coverage Status](https://coveralls.io/repos/github/go-playground/pkg/badge.svg?branch=master)](https://coveralls.io/github/go-playground/pkg?branch=master) [![GoDoc](https://godoc.org/github.com/go-playground/pkg?status.svg)](https://pkg.go.dev/mod/github.com/go-playground/pkg/v5) diff --git a/map/map.go b/map/map.go new file mode 100644 index 0000000..037941a --- /dev/null +++ b/map/map.go @@ -0,0 +1,23 @@ +//go:build go1.18 +// +build go1.18 + +package mapext + +// Retain retains only the elements specified by the function and removes others. +func Retain[K comparable, V any](m map[K]V, fn func(key K, value V) bool) { + for k, v := range m { + if fn(k, v) { + continue + } + delete(m, k) + } +} + +// Map allows mapping of a map[K]V -> U. +func Map[K comparable, V any, U any](m map[K]V, init U, fn func(accum U, key K, value V) U) U { + accum := init + for k, v := range m { + accum = fn(accum, k, v) + } + return accum +} diff --git a/map/map_test.go b/map/map_test.go new file mode 100644 index 0000000..3f1cd95 --- /dev/null +++ b/map/map_test.go @@ -0,0 +1,49 @@ +//go:build go1.18 +// +build go1.18 + +package mapext + +import ( + . "github.com/go-playground/assert/v2" + "sort" + "testing" +) + +func TestRetain(t *testing.T) { + m := map[string]int{ + "0": 0, + "1": 1, + "2": 2, + "3": 3, + } + Retain(m, func(key string, value int) bool { + return value < 1 || value > 2 + }) + Equal(t, len(m), 2) + Equal(t, m["0"], 0) + Equal(t, m["3"], 3) +} + +func TestMap(t *testing.T) { + // Test Map to slice + m := map[string]int{ + "0": 0, + "1": 1, + } + slice := Map(m, make([]int, 0, len(m)), func(accum []int, key string, value int) []int { + return append(accum, value) + }) + sort.SliceStable(slice, func(i, j int) bool { + return i < j + }) + Equal(t, len(slice), 2) + + // Test Map to Map of different type + inverted := Map(m, make(map[int]string, len(m)), func(accum map[int]string, key string, value int) map[int]string { + accum[value] = key + return accum + }) + Equal(t, len(inverted), 2) + Equal(t, inverted[0], "0") + Equal(t, inverted[1], "1") +} diff --git a/slice/slice.go b/slice/slice.go new file mode 100644 index 0000000..091d1b8 --- /dev/null +++ b/slice/slice.go @@ -0,0 +1,82 @@ +//go:build go1.18 +// +build go1.18 + +package sliceext + +import ( + optionext "github.com/go-playground/pkg/v5/values/option" + "sort" +) + +// Retain retains only the elements specified by the function. +// +// This shuffles and returns the retained values of the slice. +func Retain[T any](slice []T, fn func(v T) bool) []T { + var j int + for _, v := range slice { + if fn(v) { + slice[j] = v + j++ + } + } + return slice[:j] +} + +// Filter filters out the elements specified by the function. +// +// This shuffles and returns the retained values of the slice. +func Filter[T any](slice []T, fn func(v T) bool) []T { + var j int + for _, v := range slice { + if fn(v) { + continue + } + slice[j] = v + j++ + } + return slice[:j] +} + +// Map maps a slice of []T -> []U using the map function. +func Map[T, U any](slice []T, init U, fn func(accum U, v T) U) U { + if len(slice) == 0 { + return init + } + accum := init + for _, v := range slice { + accum = fn(accum, v) + } + return accum +} + +// Sort sorts the sliceWrapper x given the provided less function. +// +// The sort is not guaranteed to be stable: equal elements +// may be reversed from their original order. +// +// For a stable sort, use SortStable. +func Sort[T any](slice []T, less func(i T, j T) bool) { + sort.Slice(slice, func(j, k int) bool { + return less(slice[j], slice[k]) + }) +} + +// SortStable sorts the sliceWrapper x using the provided less +// function, keeping equal elements in their original order. +func SortStable[T any](slice []T, less func(i T, j T) bool) { + sort.SliceStable(slice, func(j, k int) bool { + return less(slice[j], slice[k]) + }) +} + +// Reduce reduces the elements to a single one, by repeatedly applying a reducing function. +func Reduce[T any](slice []T, fn func(accum T, current T) T) optionext.Option[T] { + if len(slice) == 0 { + return optionext.None[T]() + } + accum := slice[0] + for _, v := range slice { + accum = fn(accum, v) + } + return optionext.Some(accum) +} diff --git a/slice/slice_test.go b/slice/slice_test.go new file mode 100644 index 0000000..f2b78be --- /dev/null +++ b/slice/slice_test.go @@ -0,0 +1,81 @@ +//go:build go1.18 +// +build go1.18 + +package sliceext + +import ( + . "github.com/go-playground/assert/v2" + optionext "github.com/go-playground/pkg/v5/values/option" + "strconv" + "testing" +) + +func TestFilter(t *testing.T) { + s := Filter([]int{0, 1, 2, 3}, func(v int) bool { + return v > 0 && v < 3 + }) + Equal(t, len(s), 2) + Equal(t, s[0], 0) + Equal(t, s[1], 3) + +} + +func TestRetain(t *testing.T) { + s := Retain([]int{0, 1, 2, 3}, func(v int) bool { + return v > 0 && v < 3 + }) + Equal(t, len(s), 2) + Equal(t, s[0], 1) + Equal(t, s[1], 2) +} + +func TestMap(t *testing.T) { + s := Map[int, []string]([]int{0, 1, 2, 3}, make([]string, 0, 4), func(accum []string, v int) []string { + return append(accum, strconv.Itoa(v)) + }) + Equal(t, len(s), 4) + Equal(t, s[0], "0") + Equal(t, s[1], "1") + Equal(t, s[2], "2") + Equal(t, s[3], "3") + + // Test Map empty slice + s2 := Map[int, []string](nil, nil, func(accum []string, v int) []string { + return append(accum, strconv.Itoa(v)) + }) + Equal(t, len(s2), 0) +} + +func TestSort(t *testing.T) { + s := []int{0, 1, 2} + Sort(s, func(i int, j int) bool { + return i > j + }) + Equal(t, s[0], 2) + Equal(t, s[1], 1) + Equal(t, s[2], 0) +} + +func TestSortStable(t *testing.T) { + s := []int{0, 1, 1, 2} + SortStable(s, func(i int, j int) bool { + return i > j + }) + Equal(t, s[0], 2) + Equal(t, s[1], 1) + Equal(t, s[2], 1) + Equal(t, s[3], 0) +} + +func TestReduce(t *testing.T) { + result := Reduce([]int{0, 1, 2}, func(accum int, current int) int { + return accum + current + }) + Equal(t, result, optionext.Some(3)) + + // Test Reduce empty slice + result = Reduce([]int{}, func(accum int, current int) int { + return accum + current + }) + Equal(t, result, optionext.None[int]()) +} diff --git a/sync/mutex.go b/sync/mutex.go index d79ca36..8e774f9 100644 --- a/sync/mutex.go +++ b/sync/mutex.go @@ -1,4 +1,5 @@ //go:build go1.18 +// +build go1.18 package syncext diff --git a/sync/mutex_test.go b/sync/mutex_test.go index 75ddd83..97f333f 100644 --- a/sync/mutex_test.go +++ b/sync/mutex_test.go @@ -1,4 +1,5 @@ //go:build go1.18 +// +build go1.18 package syncext