diff options
author | Jason Song <i@wolfogre.com> | 2023-01-11 13:31:16 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-11 13:31:16 +0800 |
commit | 477a1cc40ebd3ecb116c632b0717bba748e914d2 (patch) | |
tree | f8834d481acbf410d53828d21f5aa149c21a44bd /modules | |
parent | dc5f2cf5906ec2f87ad47ea4724cc245c401eef6 (diff) | |
download | gitea-477a1cc40ebd3ecb116c632b0717bba748e914d2.tar.gz gitea-477a1cc40ebd3ecb116c632b0717bba748e914d2.zip |
Improve utils of slices (#22379)
- Move the file `compare.go` and `slice.go` to `slice.go`.
- Fix `ExistsInSlice`, it's buggy
- It uses `sort.Search`, so it assumes that the input slice is sorted.
- It passes `func(i int) bool { return slice[i] == target })` to
`sort.Search`, that's incorrect, check the doc of `sort.Search`.
- Conbine `IsInt64InSlice(int64, []int64)` and `ExistsInSlice(string,
[]string)` to `SliceContains[T]([]T, T)`.
- Conbine `IsSliceInt64Eq([]int64, []int64)` and `IsEqualSlice([]string,
[]string)` to `SliceSortedEqual[T]([]T, T)`.
- Add `SliceEqual[T]([]T, T)` as a distinction from
`SliceSortedEqual[T]([]T, T)`.
- Redesign `RemoveIDFromList([]int64, int64) ([]int64, bool)` to
`SliceRemoveAll[T]([]T, T) []T`.
- Add `SliceContainsFunc[T]([]T, func(T) bool)` and
`SliceRemoveAllFunc[T]([]T, func(T) bool)` for general use.
- Add comments to explain why not `golang.org/x/exp/slices`.
- Add unit tests.
Diffstat (limited to 'modules')
-rw-r--r-- | modules/repository/init.go | 6 | ||||
-rw-r--r-- | modules/util/compare.go | 92 | ||||
-rw-r--r-- | modules/util/slice.go | 91 | ||||
-rw-r--r-- | modules/util/slice_test.go | 88 |
4 files changed, 173 insertions, 104 deletions
diff --git a/modules/repository/init.go b/modules/repository/init.go index 59284a5baf..2b0d0be7bc 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -170,7 +170,7 @@ func LoadRepoConfig() { } for _, f := range customFiles { - if !util.IsStringInSlice(f, files, true) { + if !util.SliceContainsString(files, f, true) { files = append(files, f) } } @@ -200,12 +200,12 @@ func LoadRepoConfig() { // Filter out invalid names and promote preferred licenses. sortedLicenses := make([]string, 0, len(Licenses)) for _, name := range setting.Repository.PreferredLicenses { - if util.IsStringInSlice(name, Licenses, true) { + if util.SliceContainsString(Licenses, name, true) { sortedLicenses = append(sortedLicenses, name) } } for _, name := range Licenses { - if !util.IsStringInSlice(name, setting.Repository.PreferredLicenses, true) { + if !util.SliceContainsString(setting.Repository.PreferredLicenses, name, true) { sortedLicenses = append(sortedLicenses, name) } } diff --git a/modules/util/compare.go b/modules/util/compare.go deleted file mode 100644 index 9ac778dfd3..0000000000 --- a/modules/util/compare.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package util - -import ( - "sort" - "strings" -) - -// Int64Slice attaches the methods of Interface to []int64, sorting in increasing order. -type Int64Slice []int64 - -func (p Int64Slice) Len() int { return len(p) } -func (p Int64Slice) Less(i, j int) bool { return p[i] < p[j] } -func (p Int64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } - -// IsSliceInt64Eq returns if the two slice has the same elements but different sequences. -func IsSliceInt64Eq(a, b []int64) bool { - if len(a) != len(b) { - return false - } - sort.Sort(Int64Slice(a)) - sort.Sort(Int64Slice(b)) - for i := 0; i < len(a); i++ { - if a[i] != b[i] { - return false - } - } - return true -} - -// ExistsInSlice returns true if string exists in slice. -func ExistsInSlice(target string, slice []string) bool { - i := sort.Search(len(slice), - func(i int) bool { return slice[i] == target }) - return i < len(slice) -} - -// IsStringInSlice sequential searches if string exists in slice. -func IsStringInSlice(target string, slice []string, insensitive ...bool) bool { - caseInsensitive := false - if len(insensitive) != 0 && insensitive[0] { - caseInsensitive = true - target = strings.ToLower(target) - } - - for i := 0; i < len(slice); i++ { - if caseInsensitive { - if strings.ToLower(slice[i]) == target { - return true - } - } else { - if slice[i] == target { - return true - } - } - } - return false -} - -// IsInt64InSlice sequential searches if int64 exists in slice. -func IsInt64InSlice(target int64, slice []int64) bool { - for i := 0; i < len(slice); i++ { - if slice[i] == target { - return true - } - } - return false -} - -// IsEqualSlice returns true if slices are equal. -func IsEqualSlice(target, source []string) bool { - if len(target) != len(source) { - return false - } - - if (target == nil) != (source == nil) { - return false - } - - sort.Strings(target) - sort.Strings(source) - - for i, v := range target { - if v != source[i] { - return false - } - } - - return true -} diff --git a/modules/util/slice.go b/modules/util/slice.go index 17345cbc49..74356f5496 100644 --- a/modules/util/slice.go +++ b/modules/util/slice.go @@ -1,17 +1,90 @@ // Copyright 2022 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT +// Most of the functions in this file can have better implementations with "golang.org/x/exp/slices". +// However, "golang.org/x/exp" is experimental and unreliable, we shouldn't use it. +// So lets waiting for the "slices" has be promoted to the main repository one day. + package util -// RemoveIDFromList removes the given ID from the slice, if found. -// It does not preserve order, and assumes the ID is unique. -func RemoveIDFromList(list []int64, id int64) ([]int64, bool) { - n := len(list) - 1 - for i, item := range list { - if item == id { - list[i] = list[n] - return list[:n], true +import "strings" + +// SliceContains returns true if the target exists in the slice. +func SliceContains[T comparable](slice []T, target T) bool { + return SliceContainsFunc(slice, func(t T) bool { return t == target }) +} + +// SliceContainsFunc returns true if any element in the slice satisfies the targetFunc. +func SliceContainsFunc[T any](slice []T, targetFunc func(T) bool) bool { + for _, v := range slice { + if targetFunc(v) { + return true + } + } + return false +} + +// SliceContainsString sequential searches if string exists in slice. +func SliceContainsString(slice []string, target string, insensitive ...bool) bool { + if len(insensitive) != 0 && insensitive[0] { + target = strings.ToLower(target) + return SliceContainsFunc(slice, func(t string) bool { return strings.ToLower(t) == target }) + } + + return SliceContains(slice, target) +} + +// SliceSortedEqual returns true if the two slices will be equal when they get sorted. +// It doesn't require that the slices have been sorted, and it doesn't sort them either. +func SliceSortedEqual[T comparable](s1, s2 []T) bool { + if len(s1) != len(s2) { + return false + } + + counts := make(map[T]int, len(s1)) + for _, v := range s1 { + counts[v]++ + } + for _, v := range s2 { + counts[v]-- + } + + for _, v := range counts { + if v != 0 { + return false + } + } + return true +} + +// SliceEqual returns true if the two slices are equal. +func SliceEqual[T comparable](s1, s2 []T) bool { + if len(s1) != len(s2) { + return false + } + + for i, v := range s1 { + if s2[i] != v { + return false + } + } + return true +} + +// SliceRemoveAll removes all the target elements from the slice. +func SliceRemoveAll[T comparable](slice []T, target T) []T { + return SliceRemoveAllFunc(slice, func(t T) bool { return t == target }) +} + +// SliceRemoveAllFunc removes all elements which satisfy the targetFunc from the slice. +func SliceRemoveAllFunc[T comparable](slice []T, targetFunc func(T) bool) []T { + idx := 0 + for _, v := range slice { + if targetFunc(v) { + continue } + slice[idx] = v + idx++ } - return list, false + return slice[:idx] } diff --git a/modules/util/slice_test.go b/modules/util/slice_test.go new file mode 100644 index 0000000000..b0b771a79a --- /dev/null +++ b/modules/util/slice_test.go @@ -0,0 +1,88 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package util + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSliceContains(t *testing.T) { + assert.True(t, SliceContains([]int{2, 0, 2, 3}, 2)) + assert.True(t, SliceContains([]int{2, 0, 2, 3}, 0)) + assert.True(t, SliceContains([]int{2, 0, 2, 3}, 3)) + + assert.True(t, SliceContains([]string{"2", "0", "2", "3"}, "0")) + assert.True(t, SliceContains([]float64{2, 0, 2, 3}, 0)) + assert.True(t, SliceContains([]bool{false, true, false}, true)) + + assert.False(t, SliceContains([]int{2, 0, 2, 3}, 4)) + assert.False(t, SliceContains([]int{}, 4)) + assert.False(t, SliceContains(nil, 4)) +} + +func TestSliceContainsString(t *testing.T) { + assert.True(t, SliceContainsString([]string{"c", "b", "a", "b"}, "a")) + assert.True(t, SliceContainsString([]string{"c", "b", "a", "b"}, "b")) + assert.True(t, SliceContainsString([]string{"c", "b", "a", "b"}, "A", true)) + assert.True(t, SliceContainsString([]string{"C", "B", "A", "B"}, "a", true)) + + assert.False(t, SliceContainsString([]string{"c", "b", "a", "b"}, "z")) + assert.False(t, SliceContainsString([]string{"c", "b", "a", "b"}, "A")) + assert.False(t, SliceContainsString([]string{}, "a")) + assert.False(t, SliceContainsString(nil, "a")) +} + +func TestSliceSortedEqual(t *testing.T) { + assert.True(t, SliceSortedEqual([]int{2, 0, 2, 3}, []int{2, 0, 2, 3})) + assert.True(t, SliceSortedEqual([]int{3, 0, 2, 2}, []int{2, 0, 2, 3})) + assert.True(t, SliceSortedEqual([]int{}, []int{})) + assert.True(t, SliceSortedEqual([]int(nil), nil)) + assert.True(t, SliceSortedEqual([]int(nil), []int{})) + assert.True(t, SliceSortedEqual([]int{}, []int{})) + + assert.True(t, SliceSortedEqual([]string{"2", "0", "2", "3"}, []string{"2", "0", "2", "3"})) + assert.True(t, SliceSortedEqual([]float64{2, 0, 2, 3}, []float64{2, 0, 2, 3})) + assert.True(t, SliceSortedEqual([]bool{false, true, false}, []bool{false, true, false})) + + assert.False(t, SliceSortedEqual([]int{2, 0, 2}, []int{2, 0, 2, 3})) + assert.False(t, SliceSortedEqual([]int{}, []int{2, 0, 2, 3})) + assert.False(t, SliceSortedEqual(nil, []int{2, 0, 2, 3})) + assert.False(t, SliceSortedEqual([]int{2, 0, 2, 4}, []int{2, 0, 2, 3})) + assert.False(t, SliceSortedEqual([]int{2, 0, 0, 3}, []int{2, 0, 2, 3})) +} + +func TestSliceEqual(t *testing.T) { + assert.True(t, SliceEqual([]int{2, 0, 2, 3}, []int{2, 0, 2, 3})) + assert.True(t, SliceEqual([]int{}, []int{})) + assert.True(t, SliceEqual([]int(nil), nil)) + assert.True(t, SliceEqual([]int(nil), []int{})) + assert.True(t, SliceEqual([]int{}, []int{})) + + assert.True(t, SliceEqual([]string{"2", "0", "2", "3"}, []string{"2", "0", "2", "3"})) + assert.True(t, SliceEqual([]float64{2, 0, 2, 3}, []float64{2, 0, 2, 3})) + assert.True(t, SliceEqual([]bool{false, true, false}, []bool{false, true, false})) + + assert.False(t, SliceEqual([]int{3, 0, 2, 2}, []int{2, 0, 2, 3})) + assert.False(t, SliceEqual([]int{2, 0, 2}, []int{2, 0, 2, 3})) + assert.False(t, SliceEqual([]int{}, []int{2, 0, 2, 3})) + assert.False(t, SliceEqual(nil, []int{2, 0, 2, 3})) + assert.False(t, SliceEqual([]int{2, 0, 2, 4}, []int{2, 0, 2, 3})) + assert.False(t, SliceEqual([]int{2, 0, 0, 3}, []int{2, 0, 2, 3})) +} + +func TestSliceRemoveAll(t *testing.T) { + assert.Equal(t, SliceRemoveAll([]int{2, 0, 2, 3}, 0), []int{2, 2, 3}) + assert.Equal(t, SliceRemoveAll([]int{2, 0, 2, 3}, 2), []int{0, 3}) + assert.Equal(t, SliceRemoveAll([]int{0, 0, 0, 0}, 0), []int{}) + assert.Equal(t, SliceRemoveAll([]int{2, 0, 2, 3}, 4), []int{2, 0, 2, 3}) + assert.Equal(t, SliceRemoveAll([]int{}, 0), []int{}) + assert.Equal(t, SliceRemoveAll([]int(nil), 0), []int(nil)) + assert.Equal(t, SliceRemoveAll([]int{}, 0), []int{}) + + assert.Equal(t, SliceRemoveAll([]string{"2", "0", "2", "3"}, "0"), []string{"2", "2", "3"}) + assert.Equal(t, SliceRemoveAll([]float64{2, 0, 2, 3}, 0), []float64{2, 2, 3}) + assert.Equal(t, SliceRemoveAll([]bool{false, true, false}, true), []bool{false, false}) +} |