mirror of https://github.com/go-gitea/gitea
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.pull/22280/head^2
parent
dc5f2cf590
commit
477a1cc40e
@ -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 |
||||
} |
@ -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] |
||||
} |
||||
|
@ -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}) |
||||
} |
Loading…
Reference in new issue