diff --git a/sliceutil/util.go b/sliceutil/util.go index 23a5109..bab38f0 100644 --- a/sliceutil/util.go +++ b/sliceutil/util.go @@ -1,8 +1,28 @@ package sliceutil -import "github.com/rabee-inc/go-pkg/randutil" +import ( + "sort" -func Filter[T any](srcs []T, fn func(src T) bool) []T { + "github.com/rabee-inc/go-pkg/randutil" +) + +type Slice[T any] []T + +// NewSlice ... constructor +func NewSlice[T any](srcs []T) Slice[T] { + return srcs +} + +// ForEach ... ループ +func ForEach[T any](srcs []T, fn func(src T)) Slice[T] { + for _, src := range srcs { + fn(src) + } + return srcs +} + +// Filter ... 条件に合う要素のみを抽出する +func Filter[T any](srcs []T, fn func(src T) bool) Slice[T] { dsts := []T{} for _, src := range srcs { if fn(src) { @@ -12,7 +32,19 @@ func Filter[T any](srcs []T, fn func(src T) bool) []T { return dsts } -func Map[T, E any](srcs []T, fn func(src T) E) []E { +// FilterWithIndex ... 条件に合う要素のみを抽出する +func FilterWithIndex[T any](srcs []T, fn func(index int) bool) Slice[T] { + dsts := []T{} + for i, src := range srcs { + if fn(i) { + dsts = append(dsts, src) + } + } + return dsts +} + +// Map ... 配列の要素を変換する +func Map[T, E any](srcs []T, fn func(src T) E) Slice[E] { dsts := []E{} for _, src := range srcs { dsts = append(dsts, fn(src)) @@ -20,6 +52,16 @@ func Map[T, E any](srcs []T, fn func(src T) E) []E { return dsts } +// MapWithIndex ... 配列の要素を変換する +func MapWithIndex[T, E any](srcs []T, fn func(index int) E) Slice[E] { + dsts := []E{} + for i := range srcs { + dsts = append(dsts, fn(i)) + } + return dsts +} + +// Reduce ... reduce func Reduce[T, E any](srcs []T, fn func(dst E, src T) E) E { var dst E for _, src := range srcs { @@ -28,7 +70,7 @@ func Reduce[T, E any](srcs []T, fn func(dst E, src T) E) E { return dst } -// 配列の値の存在確認 +// Contains ... 配列に要素が含まれているか func Contains[T comparable](srcs []T, e T) bool { for _, v := range srcs { if e == v { @@ -38,6 +80,7 @@ func Contains[T comparable](srcs []T, e T) bool { return false } +// ContainsFunc ... 配列に要素が含まれているか func ContainsFunc[T any](srcs []T, fn func(src T) bool) bool { for _, src := range srcs { if fn(src) { @@ -47,8 +90,13 @@ func ContainsFunc[T any](srcs []T, fn func(src T) bool) bool { return false } -// 配列をシャッフルする -func Shuffle[T any](srcs []T) []T { +// Some ... ContainsFunc のエイリアス +func Some[T any](srcs []T, fn func(src T) bool) bool { + return ContainsFunc(srcs, fn) +} + +// Shuffle ... 配列をシャッフルする +func Shuffle[T any](srcs []T) Slice[T] { n := len(srcs) for i := n - 1; i >= 0; i-- { j := randutil.Int(0, i) @@ -57,28 +105,44 @@ func Shuffle[T any](srcs []T) []T { return srcs } -// 配列の任意の場所に挿入する -func Insert[T any](srcs []T, v T, i int) []T { - return append(srcs[:i], append([]T{v}, srcs[i:]...)...) +// Sort ... ソート +func Sort[T any](srcs []T, fn func(i, j int) bool) Slice[T] { + sort.SliceStable(srcs, fn) + return srcs +} + +// Insert ... 配列の任意の場所に挿入する +func Insert[T any](srcs []T, i int, v ...T) Slice[T] { + return append(srcs[:i], append(v, srcs[i:]...)...) } -// 配列の任意の値を削除する -func Delete[T any](srcs []T, i int) []T { +// Delete ... 配列の任意の値をindex指定で削除する +func Delete[T any](srcs []T, i int) Slice[T] { return append(srcs[:i], srcs[i+1:]...) } -// 配列の先頭を切り取る -func Shift[T any](srcs []T) (T, []T) { +// First ... 配列の先頭の要素を取得 +func First[T any](srcs []T) T { + return srcs[0] +} + +// Last ... 配列の最後の要素を取得 +func Last[T any](srcs []T) T { + return srcs[len(srcs)-1] +} + +// Shift ... 配列の先頭を切り取る (破壊) +func Shift[T any](srcs []T) (T, Slice[T]) { return srcs[0], srcs[1:] } -// 配列の後尾を切り取る -func Back[T any](srcs []T) (T, []T) { +// Pop ... 配列の後尾を切り取る (破壊) +func Pop[T any](srcs []T) (T, Slice[T]) { return srcs[len(srcs)-1], srcs[:len(srcs)-1] } -// 配列の重複を排除する -func Uniq[T comparable](srcs []T) []T { +// Uniq ... 配列の重複を排除する +func Uniq[T comparable](srcs []T) Slice[T] { dsts := make([]T, 0, len(srcs)) m := make(map[T]bool) for _, src := range srcs { @@ -90,9 +154,9 @@ func Uniq[T comparable](srcs []T) []T { return dsts } -// 配列の分割 -func Chunk[T any](srcs []T, size int) [][]T { - var chunks [][]T +// Chunk ... 配列の分割 +func Chunk[T any](srcs []T, size int) []Slice[T] { + var chunks []Slice[T] srcsSize := len(srcs) for i := 0; i < srcsSize; i += size { end := i + size @@ -104,8 +168,8 @@ func Chunk[T any](srcs []T, size int) [][]T { return chunks } -// 配列で指定したbaseの中でtargetに含まれない値の配列を取得 -func Excludes[T comparable](base []T, target []T) []T { +// Excludes ... 配列で指定したbaseの中でtargetに含まれない値の配列を取得 +func Excludes[T comparable](base []T, target []T) Slice[T] { dsts := []T{} for _, b := range base { if !Contains(target, b) { @@ -114,3 +178,80 @@ func Excludes[T comparable](base []T, target []T) []T { } return dsts } + +// --- メソッド --- + +// ForEach ... ループ +func (s Slice[T]) ForEach(fn func(src T)) Slice[T] { + return ForEach(s, fn) +} + +// Len ... 要素数を取得する +func (s Slice[T]) Len() int { + return len(s) +} + +// Filter ... 条件に合う要素のみを抽出する +func (s Slice[T]) Filter(fn func(src T) bool) Slice[T] { + return Filter(s, fn) +} + +// FilterWithIndex ... 条件に合う要素のみを抽出する +func (s Slice[T]) FilterWithIndex(fn func(index int) bool) Slice[T] { + return FilterWithIndex(s, fn) +} + +// Shuffle ... 配列をシャッフルする +func (s Slice[T]) Shuffle() Slice[T] { + return Shuffle(s) +} + +// Sort ... ソート +func (s Slice[T]) Sort(fn func(i, j int) bool) Slice[T] { + return Sort(s, fn) +} + +// ContainsFunc ... 配列に要素が含まれているか +func (s Slice[T]) ContainsFunc(fn func(src T) bool) bool { + return ContainsFunc(s, fn) +} + +// Some ... ContainsFunc のエイリアス +func (s Slice[T]) Some(fn func(src T) bool) bool { + return s.ContainsFunc(fn) +} + +// Insert ... 配列の任意の場所に挿入する +func (s Slice[T]) Insert(i int, v ...T) Slice[T] { + return Insert(s, i, v...) +} + +// Delete ... 配列の任意の値をindex指定で削除する +func (s Slice[T]) Delete(i int) Slice[T] { + return Delete(s, i) +} + +// First ... 配列の先頭の要素を取得 +func (s Slice[T]) First() T { + return First(s) +} + +// Last ... 配列の最後の要素を取得 +func (s Slice[T]) Last() T { + return Last(s) +} + +// Shift ... 配列の先頭を切り取る +func (s Slice[T]) Shift() (T, Slice[T]) { + return Shift(s) +} + +// Pop ... 配列の後尾を切り取る +func (s Slice[T]) Pop() (T, Slice[T]) { + return Pop(s) +} + +// Chunk ... 配列の分割 +func (s Slice[T]) Chunk(size int) []Slice[T] { + return Chunk(s, size) +} diff --git a/sliceutil/util_test.go b/sliceutil/util_test.go new file mode 100644 index 0000000..274b51f --- /dev/null +++ b/sliceutil/util_test.go @@ -0,0 +1,372 @@ +package sliceutil_test + +import ( + "fmt" + "math" + "strconv" + "testing" + + "github.com/rabee-inc/go-pkg/sliceutil" + "gopkg.in/go-playground/assert.v1" +) + +func main() { + +} + +func Test(t *testing.T) { + + // ForEach + t.Run("ForEach", func(t *testing.T) { + // 関数 + expect := []int{1, 2, 3, 4} + actual := []int{} + s := sliceutil.ForEach(expect, func(v int) { + actual = append(actual, v) + }) + assertSlice(t, expect, actual) + + // メソッド + actual = []int{} + s.ForEach(func(v int) { + actual = append(actual, v) + }) + assertSlice(t, expect, actual) + }) + + // Filter + t.Run("Filter", func(t *testing.T) { + // 関数 + expect := []int{3, 4} + actual := sliceutil.Filter([]int{1, 2, 3, 4}, func(v int) bool { + return v > 2 + }) + assertSlice(t, expect, actual) + + // メソッド + actual = actual.Filter(func(v int) bool { + return v > 3 + }) + assertSlice(t, []int{4}, actual) + }) + + // FilterWithIndex + t.Run("FilterWithIndex", func(t *testing.T) { + // 関数 + expect := []int{1, 2} + input := []int{1, 2, 3, 4} + actual := sliceutil.FilterWithIndex(input, func(i int) bool { + return input[i] < 3 + }) + assertSlice(t, expect, actual) + + // メソッド + actual = actual.FilterWithIndex(func(i int) bool { + return actual[i] < 2 + }) + assertSlice(t, []int{1}, actual) + }) + + // Map + t.Run("Map", func(t *testing.T) { + // 関数 + expect := []string{"1", "2", "3", "4"} + actual := sliceutil.Map([]int{1, 2, 3, 4}, func(v int) string { + return strconv.Itoa(v) + }) + assertSlice(t, expect, actual) + + // メソッドチェーン + actual = actual.Filter(func(v string) bool { + return v != "2" + }) + assertSlice(t, []string{"1", "3", "4"}, actual) + }) + + // MapWithIndex + t.Run("MapWithIndex", func(t *testing.T) { + // 関数 + expect := []string{"1", "2", "3", "4"} + input := []int{1, 2, 3, 4} + actual := sliceutil.MapWithIndex(input, func(i int) string { + return strconv.Itoa(input[i]) + }) + assertSlice(t, expect, actual) + + // メソッドチェーン + actual = actual.Filter(func(v string) bool { + return v != "2" + }) + assertSlice(t, []string{"1", "3", "4"}, actual) + }) + + // Reduce + t.Run("Reduce", func(t *testing.T) { + // 関数 + expect := 10 + actual := sliceutil.Reduce([]int{1, 2, 3, 4}, func(acc, v int) int { + return acc + v + }) + assert.Equal(t, expect, actual) + }) + + // Contains + t.Run("Contains", func(t *testing.T) { + // 関数 + expect := true + actual := sliceutil.Contains([]int{1, 2, 3, 4}, 2) + assert.Equal(t, expect, actual) + + expect = false + actual = sliceutil.Contains([]int{1, 2, 3, 4}, 5) + assert.Equal(t, expect, actual) + }) + + // ContainsFunc + t.Run("ContainsFunc", func(t *testing.T) { + // 関数 + expect := true + actual := sliceutil.ContainsFunc([]int{1, 2, 3, 4}, func(v int) bool { + return v == 2 + }) + assert.Equal(t, expect, actual) + + expect = false + actual = sliceutil.ContainsFunc([]int{1, 2, 3, 4}, func(v int) bool { + return v == 5 + }) + assert.Equal(t, expect, actual) + + actual = sliceutil.NewSlice([]int{1, 2, 3, 4}).ContainsFunc(func(v int) bool { + return v == 2 + }) + assert.Equal(t, true, actual) + + }) + + // Shuffle + t.Run("Shuffle", func(t *testing.T) { + // 関数 + input := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + + // 3000 回実行して、各数値のばらつきが誤差 30% 以内なら OK とする + N := 3000 + + count := make(map[int]map[int]int) + // map 初期化 + for _, v := range input { + count[v] = make(map[int]int) + for i := range input { + count[v][i] = 0 + } + } + + for i := 0; i < N; i++ { + // input をコピー + ip := append([]int{}, input...) + shuffled := sliceutil.Shuffle(ip) + for i, v := range shuffled { + count[v][i]++ + } + } + + // 平均と誤差が 30% 以内かどうか + for _, v := range count { + average := float64(N) / float64(len(v)) + for _, v2 := range v { + diff := math.Abs(float64(v2) - average) + if diff > average*0.3 { + t.Errorf("expected: %v, actual: %v", diff, average*0.3) + } + } + } + + // メソッド + count = make(map[int]map[int]int) + + // map 初期化 + for _, v := range input { + count[v] = make(map[int]int) + for i := range input { + count[v][i] = 0 + } + } + + for i := 0; i < N; i++ { + // input をコピー + ip := append([]int{}, input...) + shuffled := sliceutil.NewSlice(ip).Shuffle() + for i, v := range shuffled { + count[v][i]++ + } + } + + // 平均と誤差が 30% 以内かどうか + for _, v := range count { + average := float64(N) / float64(len(v)) + for _, v2 := range v { + diff := math.Abs(float64(v2) - average) + if diff > average*0.3 { + t.Errorf("expected: %v, actual: %v", diff, average*0.3) + } + } + } + + }) + + // Sort + t.Run("Sort", func(t *testing.T) { + // 関数 + expect := []int{1, 2, 3, 4, 10} + input := []int{10, 4, 3, 2, 1} + actual := sliceutil.Sort(input, func(i, j int) bool { + return input[i] < input[j] + }) + assertSlice(t, expect, actual) + + // メソッド + expect = []int{1, 2, 3, 4} + input = []int{4, 3, 2, 1} + actual = sliceutil.NewSlice(input).Sort(func(i, j int) bool { + return input[i] < input[j] + }) + assertSlice(t, expect, actual) + }) + + // Insert + t.Run("Insert", func(t *testing.T) { + // 関数 + expect := []int{1, 2, 3, 4, 5} + input := []int{1, 2, 4, 5} + actual := sliceutil.Insert(input, 2, 3) + assertSlice(t, expect, actual) + + // メソッド + expect = []int{1, 2, 3, 3, 4, 5} + input = []int{1, 2, 4, 5} + actual = sliceutil.NewSlice(input).Insert(2, 3, 3) + assertSlice(t, expect, actual) + }) + + // Delete + t.Run("Delete", func(t *testing.T) { + // 関数 + expect := []int{1, 2, 4, 5} + input := []int{1, 2, 3, 4, 5} + actual := sliceutil.Delete(input, 2) + assertSlice(t, expect, actual) + + // メソッド + expect = []int{1, 2, 4, 5} + input = []int{1, 2, 3, 4, 5} + actual = sliceutil.NewSlice(input).Delete(2) + assertSlice(t, expect, actual) + }) + + // First + t.Run("First", func(t *testing.T) { + // 関数 + expect := 1 + input := []int{1, 2, 3, 4, 5} + actual := sliceutil.First(input) + assert.Equal(t, expect, actual) + assertSlice(t, []int{1, 2, 3, 4, 5}, input) + + // メソッド + expect = 1 + input = []int{1, 2, 3, 4, 5} + actual = sliceutil.NewSlice(input).First() + assert.Equal(t, expect, actual) + assertSlice(t, []int{1, 2, 3, 4, 5}, input) + + }) + + // Last + t.Run("Last", func(t *testing.T) { + // 関数 + expect := 5 + input := []int{1, 2, 3, 4, 5} + actual := sliceutil.Last(input) + assert.Equal(t, expect, actual) + assertSlice(t, []int{1, 2, 3, 4, 5}, input) + + // メソッド + expect = 5 + input = []int{1, 2, 3, 4, 5} + actual = sliceutil.NewSlice(input).Last() + assert.Equal(t, expect, actual) + assertSlice(t, []int{1, 2, 3, 4, 5}, input) + }) + + // Shift + t.Run("Shift", func(t *testing.T) { + // 関数 + expect := 1 + input := []int{1, 2, 3, 4, 5} + actual, output := sliceutil.Shift(input) + assert.Equal(t, expect, actual) + fmt.Println("input: ", input, "actual: ", actual, "expect: ", expect) + assertSlice(t, []int{2, 3, 4, 5}, output) + + // メソッド + expect = 2 + actual, s := output.Shift() + assert.Equal(t, expect, actual) + assertSlice(t, []int{3, 4, 5}, s) + }) + + // Pop + t.Run("Pop", func(t *testing.T) { + // 関数 + expect := 5 + input := []int{1, 2, 3, 4, 5} + actual, output := sliceutil.Pop(input) + assert.Equal(t, expect, actual) + assertSlice(t, []int{1, 2, 3, 4}, output) + + // メソッド + expect = 4 + + actual, s := output.Pop() + assert.Equal(t, expect, actual) + assertSlice(t, []int{1, 2, 3}, s) + }) + + // Chunk + t.Run("Chunk", func(t *testing.T) { + // 関数 + expect := [][]int{{1, 2}, {3, 4}, {5}} + input := []int{1, 2, 3, 4, 5} + actual := sliceutil.Chunk(input, 2) + for i, s := range actual { + assertSlice(t, expect[i], s) + } + + // メソッド + expect = [][]int{{1, 2}, {3, 4}, {5}} + input = []int{1, 2, 3, 4, 5} + actual = sliceutil.NewSlice(input).Chunk(2) + for i, s := range actual { + assertSlice(t, expect[i], s) + } + }) + +} + +func eqSlice[T comparable](a, b []T) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if v != b[i] { + return false + } + } + return true +} + +func assertSlice[T comparable](t *testing.T, expected, actual []T) { + if !eqSlice(expected, actual) { + t.Errorf("expected: %v, actual: %v", expected, actual) + } +}