# 切片
切片作为最常用的数据结构,我们为它提供了非常多的辅助方法。以下所有的方法都定义在 "github.com/ecodeclub/ekit/slice" 下,使用前你需要引入:
import (
"github.com/ecodeclub/ekit/slice"
)
2
3
# 聚合函数
目前聚合函数支持 Sum, Max 和 Min。
Sum 使用例子:
func ExampleSum() {
res := slice.Sum[int]([]int{1, 2, 3})
fmt.Println(res)
res = slice.Sum[int](nil)
fmt.Println(res)
// Output:
// 6
// 0
}
2
3
4
5
6
7
8
9
当用户传入空切片或者 nil 的时候,返回的是对应类型的零值。
Max 使用例子:
func ExampleMax() {
res := slice.Max[int]([]int{1, 2, 3})
fmt.Println(res)
// Output:
// 3
}
2
3
4
5
6
使用 Max 需要传入至少一个元素,否则会出现 panic。
Min 使用例子:
func ExampleMin() {
res := slice.Min[int]([]int{1, 2, 3})
fmt.Println(res)
// Output:
// 1
}
2
3
4
5
6
使用 Min 需要传入至少一个元素,否则会出现 panic。
使用注意事项:
- 如果你使用的是 float32 或者 float64,那么你需要自己处理精度问题。这些聚合函数只是简单使用 Go 中默认的 +, >, <
- Min 和 Max 只能处理实数及其衍生类型,无法处理复数
# 包含
我们定义了三组方法用于检测包含关系。这三组方法中,没有 Func 后缀的方法,接收的是任意的 comparable 类型,有 Func 后缀的方法则可以接收任意类型。
- Contains 和 ContainsFunc用于检测切片 src 是否包含某个元素:
func ExampleContains() {
res := slice.Contains[int]([]int{1, 2, 3}, 3)
fmt.Println(res)
// Output:
// true
}
func ExampleContainsFunc() {
res := slice.ContainsFunc[int]([]int{1, 2, 3}, 3, func(src, dst int) bool {
return src == dst
})
fmt.Println(res)
// Output:
// true
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- ContainsAny 和 ContainsAnyFunc 用于检测切片 src 是否包含另外一个切片 dst 中的任意元素:
func ExampleContainsAny() {
res := slice.ContainsAny[int]([]int{1, 2, 3}, []int{3, 6})
fmt.Println(res)
res = slice.ContainsAny[int]([]int{1, 2, 3}, []int{4, 5, 9})
fmt.Println(res)
// Output:
// true
// false
}
func ExampleContainsAnyFunc() {
res := slice.ContainsAnyFunc[int]([]int{1, 2, 3}, []int{3, 1}, func(src, dst int) bool {
return src == dst
})
fmt.Println(res)
res = slice.ContainsAllFunc[int]([]int{1, 2, 3}, []int{4, 7, 6}, func(src, dst int) bool {
return src == dst
})
fmt.Println(res)
// Output:
// true
// false
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
如果 src 是空切片或者 nil,那么会返回 false。
- ContainsAll 和 ContainsAllFunc 用于判断切片 src 是否包含另一个切片 dst 中的所有元素。
func ExampleContainsAll() {
res := slice.ContainsAll[int]([]int{1, 2, 3}, []int{3, 1})
fmt.Println(res)
res = slice.ContainsAll[int]([]int{1, 2, 3}, []int{3, 1, 4})
fmt.Println(res)
// Output:
// true
// false
}
func ExampleContainsAllFunc() {
res := slice.ContainsAllFunc[int]([]int{1, 2, 3}, []int{3, 1}, func(src, dst int) bool {
return src == dst
})
fmt.Println(res)
res = slice.ContainsAllFunc[int]([]int{1, 2, 3}, []int{3, 1, 4}, func(src, dst int) bool {
return src == dst
})
fmt.Println(res)
// Output:
// true
// false
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
如果 src 是空切片或者 nil:
- 如果 dst 是空切片或者 nil,返回 true
- 如果 dst 不为空,返回 false
使用注意事项:
- ContainsAllFunc 在 src 或者 dst 很大的情况下,性能会比较差,因为它使用了循环
# 差集和对称差集
差集和对称差集用于计算 src 和 dst 两个切片之间的不同元素。它们遵循严格的数学语义。
# DiffSet 和 DiffSetFunc
使用 DiffSet 和 DiffSetFunc 非常简单:
func ExampleDiffSet() {
res := slice.DiffSet[int]([]int{1, 3, 2, 2, 4}, []int{3, 4, 5, 6})
sort.Ints(res)
fmt.Println(res)
// Output:
// [1 2]
}
func ExampleDiffSetFunc() {
res := slice.DiffSetFunc[int]([]int{1, 3, 2, 2, 4}, []int{3, 4, 5, 6}, func(src, dst int) bool {
return src == dst
})
fmt.Println(res)
// Output:
// [1 2]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DiffSet 只能处理 comparable 的任意类型,而 DiffSetFunc 可以处理任意类型。
使用注意事项:
- DiffSet 和 DiffSetFunc 计算的都是 src - dst,而不是 (src - dst) 和 (dst - src)
- DiffSet 和 DiffSetFunc 的返回值都已经去重了
- DiffSet 返回值的元素顺序,和原本的 src 中元素的顺序不一样
- DiffSetFunc 本身采用的是循环来处理,所以在元素数量非常多的时候,性能会比较差
# SymmetricDiffSet 和 SymmetricDiffSetFunc
这两个方法用于计算对称差集。对称差集和差集相比:
- 差集计算的是 src - dst
- 对称差集计算的是 (src - dst) U (dst - src)
这两个方法使用例子:
func ExampleSymmetricDiffSet() {
res := slice.SymmetricDiffSet[int]([]int{1, 3, 4, 2}, []int{2, 5, 7, 3})
sort.Ints(res)
fmt.Println(res)
// Output:
// [1 4 5 7]
}
func ExampleSymmetricDiffSetFunc() {
res := slice.SymmetricDiffSetFunc[int]([]int{1, 3, 4, 2}, []int{2, 5, 7, 3}, func(src, dst int) bool {
return src == dst
})
sort.Ints(res)
fmt.Println(res)
// Output:
// [1 4 5 7]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SymmetricDiffSet 只能处理 comparable 类型,而 SymmetricDiffSetFunc 可以处理任意类型。
- SymmetricDiffSet 和 SymmetricDiffSetFunc 的返回值都已经去重了
- SymmetricDiffSet 返回值的元素顺序,和原本的 src 中元素的顺序不一样
- SymmetricDiffSetFunc 本身采用的是循环来处理,所以在元素数量非常多的时候,性能会比较差
# 交集
交集用于计算两个切片的公共元素,严格遵循数学语义。
交集使用起来非常简单:
func ExampleIntersectSet() {
res := slice.IntersectSet[int]([]int{1, 2, 3, 3, 4}, []int{1, 1, 3})
sort.Ints(res)
fmt.Println(res)
res = IntersectSet[int]([]int{1, 2, 3, 3, 4}, []int{5, 7})
fmt.Println(res)
// Output:
// [1 3]
// []
}
func ExampleIntersectSetFunc() {
res := slice.IntersectSetFunc[int]([]int{1, 2, 3, 3, 4}, []int{1, 1, 3}, func(src, dst int) bool {
return src == dst
})
sort.Ints(res)
fmt.Println(res)
res = IntersectSetFunc[int]([]int{1, 2, 3, 3, 4}, []int{5, 7}, func(src, dst int) bool {
return src == dst
})
fmt.Println(res)
// Output:
// [1 3]
// []
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
IntersectSet 只能处理 comparable 类型,而 IntersectSetFunc 可以处理任意类型。
使用注意事项:
- IntersectSet 和 IntersectSetFunc 的返回值都已经被去重了
- IntersectSet 返回值中的元素顺序,和它在 src 中的顺序不一致
- IntersectSetFunc 本身采用循环来处理,所以如果 src 或者 dst 元素很多的时候,性能会比较差
# 并集
并集将返回 src 和 dst 中的所有元素:
func ExampleUnionSet() {
res := slice.UnionSet[int]([]int{1, 3, 4, 5}, []int{1, 4, 7})
sort.Ints(res)
fmt.Println(res)
// Output:
// [1 3 4 5 7]
}
func ExampleUnionSetFunc() {
res := slice.UnionSetFunc[int]([]int{1, 3, 4, 5}, []int{1, 4, 7}, func(src, dst int) bool {
return src == dst
})
sort.Ints(res)
fmt.Println(res)
// Output:
// [1 3 4 5 7]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
UnionSet 只能处理 comparable 类型,而 UnionSetFunc 可以处理任意类型。
使用注意事项:
- UnionSet 和 UnionSetFunc 的返回值都已经被去重了
- UnionSet 返回值中的元素顺序,和它在 src 中的顺序不一致
- UnionSetFunc 本身采用循环来处理,所以如果 src 或者 dst 元素很多的时候,性能会比较差
# 下标查找
我们提供了三组方法用于查找 src 中某个元素的下标。
Index 和 IndexFunc 用于从前往后查找元素,将会返回找到的第一个元素的下标。在没有找到的情况下,会返回 -1。使用例子:
func ExampleIndex() {
res := slice.Index[int]([]int{1, 2, 3}, 1)
fmt.Println(res)
res = slice.Index[int]([]int{1, 2, 3}, 4)
fmt.Println(res)
// Output:
// 0
// -1
}
func ExampleIndexFunc() {
res := slice.IndexFunc[int]([]int{1, 2, 3}, 1, func(src, dst int) bool {
return src == dst
})
fmt.Println(res)
res = slice.IndexFunc[int]([]int{1, 2, 3}, 4, func(src, dst int) bool {
return src == dst
})
fmt.Println(res)
// Output:
// 0
// -1
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
注意 Index 只能处理 comparable 类型,而 IndexFunc 可以处理任意类型。
LastIndex 和 LastIndexFunc 返回 src 中目标元素的最后一个下标。换句话来说,返回从后往前遍历,找到的第一个元素的下标。如果没有找到,会返回 -1。使用例子:
func ExampleLastIndex() {
res := slice.LastIndex[int]([]int{1, 2, 3, 1}, 1)
fmt.Println(res)
res = slice.LastIndex[int]([]int{1, 2, 3}, 4)
fmt.Println(res)
// Output:
// 3
// -1
}
func ExampleLastIndexFunc() {
res := slice.LastIndexFunc[int]([]int{1, 2, 3, 1}, 1, func(src, dst int) bool {
return src == dst
})
fmt.Println(res)
res = slice.LastIndexFunc[int]([]int{1, 2, 3}, 4, func(src, dst int) bool {
return src == dst
})
fmt.Println(res)
// Output:
// 3
// -1
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
注意 LastIndex 只能处理 comparable 类型,而 LastIndexFunc 可以处理任意类型。
IndexAll 和 IndexAllFunc 会返回所有命中的元素下标。使用例子:
func ExampleIndexAll() {
res := slice.IndexAll[int]([]int{1, 2, 3, 4, 5, 3, 9}, 3)
fmt.Println(res)
res = slice.IndexAll[int]([]int{1, 2, 3}, 4)
fmt.Println(res)
// Output:
// [2 5]
// []
}
func ExampleIndexAllFunc() {
res := slice.IndexAllFunc[int]([]int{1, 2, 3, 4, 5, 3, 9}, 3, func(src, dst int) bool {
return src == dst
})
fmt.Println(res)
res = slice.IndexAllFunc[int]([]int{1, 2, 3}, 4, func(src, dst int) bool {
return src == dst
})
fmt.Println(res)
// Output:
// [2 5]
// []
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
IndexAll 只能处理 comparable 类型,而 IndexAllFunc 可以处理任意类型。
# map reduce
目前我们还没有设计 reduce 接口,但是 map 接口我们已经设计出来了。使用 Map 方法可以将一种类型的切片转化为另一种类型的切片:
func ExampleMap() {
src := []int{1, 2, 3}
dst := slice.Map(src, func(idx int, src int) string {
return strconv.Itoa(src)
})
fmt.Println(dst)
// Output: [1 2 3]
}
2
3
4
5
6
7
8
类似地,还有一个 FilterMap 方法,与 Map 方法比起来,调用者可以决定是否忽略某些元素:
func ExampleFilterMap() {
src := []int{1, -2, 3}
dst := slice.FilterMap[int, string](src, func(idx int, src int) (string, bool) {
return strconv.Itoa(src), src >= 0
})
fmt.Println(dst)
// Output: [1 3]
}
2
3
4
5
6
7
8
9
FilterMap 的传入的方法的第二个返回值是 false,那么意味着对应的返回值会被忽略掉。因此可以注意到在例子里面我们输出的是 "[1 3]",也就是 -2 已经被我们忽略了。
# 删除
切片的删除还是稍微有点棘手的,所以我们封装了一下对切片的删除操作,提供了一个 Delete 方法:
func ExampleDelete() {
res, _ := slice.Delete[int]([]int{1, 2, 3, 4}, 2)
fmt.Println(res)
_, err := slice.Delete[int]([]int{1, 2, 3, 4}, -1)
fmt.Println(err)
// Output:
// [1 2 4]
// ekit: 下标超出范围,长度 4, 下标 -1
}
2
3
4
5
6
7
8
9
需要注意的是,你一定要重新处理 Delete 的返回值。如果你肯定你的下标不会出现问题,那么可以忽略第二个返回值。
# 翻转
我们提供了两个方法用于将一个切片翻转:
- Reverse:会创建一个新的切片,新切片的元素顺序和原本的元素顺序是相反的
- ReverseSelf:会直接调整原本切片中元素的位置
使用例子:
func ExampleReverse() {
res := slice.Reverse[int]([]int{1, 3, 2, 2, 4})
fmt.Println(res)
res2 := slice.Reverse[string]([]string{"a", "b", "c", "d", "e"})
fmt.Println(res2)
// Output:
// [4 2 2 3 1]
// [e d c b a]
}
func ExampleReverseSelf() {
src := []int{1, 3, 2, 2, 4}
slice.ReverseSelf[int](src)
fmt.Println(src)
src2 := []string{"a", "b", "c", "d", "e"}
slice.ReverseSelf[string](src2)
fmt.Println(src2)
// Output:
// [4 2 2 3 1]
// [e d c b a]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22