samber/lo 库的使用方法: 处理切片
samber/lo 是一个 Go 语言库,提供了一些常用的集合操作函数,如 Filter、Map 和 FilterMap。汇总目录页面
这个库函数太多,因此我决定按照功能分别介绍,本文介绍的是 samber/lo 库中处理切片的函数。主要参考库的README
Filter
Filter 函数遍历一个切片,返回一个由所有使谓词函数返回 true 的元素组成的数组, 注意index是遍历的索引。 和for循环相比,这个函数更加简洁。
even := lo.Filter([]int{1, 2, 3, 4}, func(x int, index int) bool {
return x%2 == 0
})
// []int{2, 4}
[play]
Map
将一种类型的切片转换为另一种类型的切片。
import "github.com/samber/lo"
lo.Map([]int64{1, 2, 3, 4}, func(x int64, index int) string {
return strconv.FormatInt(x, 10)
})
// []string{"1", "2", "3", "4"}
[play]
并发处理:和lo.Map()
类似,但是映射函数在goroutine中调用。结果按照相同的顺序返回。比直接go 协程更加简洁。
import lop "github.com/samber/lo/parallel"
lop.Map([]int64{1, 2, 3, 4}, func(x int64, _ int) string {
return strconv.FormatInt(x, 10)
})
// []string{"1", "2", "3", "4"}
FilterMap
同时执行过滤及映射操作, callback函数返回两个值: 映射操作的结果和是否包含在结果中。
matching := lo.FilterMap([]string{"cpu", "gpu", "mouse", "keyboard"}, func(x string, _ int) (string, bool) {
if strings.HasSuffix(x, "pu") {
return "xpu", true
}
return "", false
})
// []string{"xpu", "xpu"}
[play]
FlatMap
把callback函数返回的切片展开,添加到最终的切片中。
lo.FlatMap([]int{0, 1, 2}, func(x int, _ int) []string {
return []string{
strconv.FormatInt(x, 10),
strconv.FormatInt(x, 10),
}
})
// []string{"0", "0", "1", "1", "2", "2"}
[play]
Reduce
将集合减少到单个值。该值是通过将集合中的每个元素通过累加器函数运行的结果累积得出的。每次调用都会提供上一次调用返回的返回值。下面的agg初始值为0。
sum := lo.Reduce([]int{1, 2, 3, 4}, func(agg int, item int, _ int) int {
return agg + item
}, 0)
// 10
[play]
ReduceRight
和Reduce类似,但是从右到左遍历。
result := lo.ReduceRight([][]int{{0, 1}, {2, 3}, {4, 5}}, func(agg []int, item []int, _ int) []int {
return append(agg, item...)
}, []int{})
// []int{4, 5, 2, 3, 0, 1}
[play]
ForEach
对集合中的每个元素执行函数, 并发形式尤其方便!
import "github.com/samber/lo"
lo.ForEach([]string{"hello", "world"}, func(x string, _ int) {
println(x)
})
// prints "hello\nworld\n"
[play]
import lop "github.com/samber/lo/parallel"
lop.ForEach([]string{"hello", "world"}, func(x string, _ int) {
println(x)
})
// prints "hello\nworld\n" or "world\nhello\n"
Times
对某一个函数调用n次,返回结果的切片。i为索引, 从0开始。
import "github.com/samber/lo"
lo.Times(3, func(i int) string {
return strconv.FormatInt(int64(i), 10)
})
// []string{"0", "1", "2"}
[play]
并发形式
import lop "github.com/samber/lo/parallel"
lop.Times(3, func(i int) string {
return strconv.FormatInt(int64(i), 10)
})
// []string{"0", "1", "2"}
Uniq
将切片中的重复元素去掉,只保留第一个出现的元素。
uniqValues := lo.Uniq([]int{1, 2, 2, 1})
// []int{1, 2}
[play]
UniqBy
加上了callback函数,用于生成唯一性的标准。
uniqValues := lo.UniqBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
return i%3
})
// []int{0, 1, 2}
[play]
GroupBy
分组函数,返回一个map,key是分组的标准,value是由key相同的元素组成的切片。
import lo "github.com/samber/lo"
groups := lo.GroupBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
return i%3
})
// map[int][]int{0: []int{0, 3}, 1: []int{1, 4}, 2: []int{2, 5}}
[play]
并发形式:
import lop "github.com/samber/lo/parallel"
lop.GroupBy([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
return i%3
})
// map[int][]int{0: []int{0, 3}, 1: []int{1, 4}, 2: []int{2, 5}}
Chunk
返回一个切片,切片中的元素是原切片中连续的size个元素, 最后一个切片可能不足size个元素。对字节流的处理很有用。
lo.Chunk([]int{0, 1, 2, 3, 4, 5}, 2)
// [][]int{{0, 1}, {2, 3}, {4, 5}}
lo.Chunk([]int{0, 1, 2, 3, 4, 5, 6}, 2)
// [][]int{{0, 1}, {2, 3}, {4, 5}, {6}}
lo.Chunk([]int{}, 2)
// [][]int{}
lo.Chunk([]int{0}, 2)
// [][]int{{0}}
[play]
PartitionBy
将切片分成多个切片,分组的标准是callback函数的返回值。
import lo "github.com/samber/lo"
partitions := lo.PartitionBy([]int{-2, -1, 0, 1, 2, 3, 4, 5}, func(x int) string {
if x < 0 {
return "negative"
} else if x%2 == 0 {
return "even"
}
return "odd"
})
// [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}}
[play]
并发形式,元素出现的顺序保持不变。
import lop "github.com/samber/lo/parallel"
partitions := lop.PartitionBy([]int{-2, -1, 0, 1, 2, 3, 4, 5}, func(x int) string {
if x < 0 {
return "negative"
} else if x%2 == 0 {
return "even"
}
return "odd"
})
// [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}}
Flatten
将二维切片展开成一维切片。
flat := lo.Flatten([][]int{{0, 1}, {2, 3, 4, 5}})
// []int{0, 1, 2, 3, 4, 5}
[play]
Interleave
Round-robin 方式遍历切片,并将索引处的值顺序添加到结果中。
interleaved := lo.Interleave([]int{1, 4, 7}, []int{2, 5, 8}, []int{3, 6, 9})
// []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
interleaved := lo.Interleave([]int{1}, []int{2, 5, 8}, []int{3, 6}, []int{4, 7, 9, 10})
// []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
[play]
Shuffle
返回一个打乱顺序的切片, 使用Fisher-Yates shuffle算法。
randomOrder := lo.Shuffle([]int{0, 1, 2, 3, 4, 5})
// []int{1, 4, 0, 3, 5, 2}
[play]
Reverse
反转切片,第一个元素变成最后一个,第二个元素变成倒数第二个,以此类推。
reverseOrder := lo.Reverse([]int{0, 1, 2, 3, 4, 5})
// []int{5, 4, 3, 2, 1, 0}
[play]
Fill
使用initial
值填充数组的元素。需要实现Clone方法。
type foo struct {
bar string
}
func (f foo) Clone() foo {
return foo{f.bar}
}
initializedSlice := lo.Fill([]foo{foo{"a"}, foo{"a"}}, foo{"b"})
// []foo{foo{"b"}, foo{"b"}}
[play]
Repeat
生成一个切片,包含N个相同的元素。
type foo struct {
bar string
}
func (f foo) Clone() foo {
return foo{f.bar}
}
slice := lo.Repeat(2, foo{"a"})
// []foo{foo{"a"}, foo{"a"}}
[play]
RepeatBy
同Repeat, 但是使用callback函数生成元素。
slice := lo.RepeatBy(0, func (i int) string {
return strconv.FormatInt(int64(math.Pow(float64(i), 2)), 10)
})
// []string{}
slice := lo.RepeatBy(5, func(i int) string {
return strconv.FormatInt(int64(math.Pow(float64(i), 2)), 10)
})
// []string{"0", "1", "4", "9", "16"}
[play]
KeyBy
将切片或数组转换为map,key是callback函数的返回值,value是对应的元素。 当key相同时,后面的元素会覆盖前面的元素。
m := lo.KeyBy([]string{"a", "aa", "aaa"}, func(str string) int {
return len(str)
})
// map[int]string{1: "a", 2: "aa", 3: "aaa"}
type Character struct {
dir string
code int
}
characters := []Character{
{dir: "left", code: 97},
{dir: "right", code: 100},
}
result := lo.KeyBy(characters, func(char Character) string {
return string(rune(char.code))
})
//map[a:{dir:left code:97} d:{dir:right code:100}]
[play]
Associate (alias: SliceToMap)
对切片遍历生成一个Map,key和value由callback函数生成。key是第一个返回值,value是第二个返回值。如果key相同,后面的元素会覆盖前面的元素。
in := []*foo{{baz: "apple", bar: 1}, {baz: "banana", bar: 2}}
aMap := lo.Associate(in, func (f *foo) (string, int) {
return f.baz, f.bar
})
// map[string][int]{ "apple":1, "banana":2 }
[play]
Drop
去掉切片的前n个元素。
l := lo.Drop([]int{0, 1, 2, 3, 4, 5}, 2)
// []int{2, 3, 4, 5}
[play]
DropRight
去掉切片的后n个元素。
l := lo.DropRight([]int{0, 1, 2, 3, 4, 5}, 2)
// []int{0, 1, 2, 3}
[play]
DropWhile
DropWhile 会一直删除元素,直到predicate返回false。
l := lo.DropWhile([]string{"a", "aa", "aaa", "aa", "aa"}, func(val string) bool {
return len(val) <= 2
})
// []string{"aaa", "aa", "aa"}
[play]
DropRightWhile
和DropWhile类似,但是从右到左遍历。
l := lo.DropRightWhile([]string{"a", "aa", "aaa", "aa", "aa"}, func(val string) bool {
return len(val) <= 2
})
// []string{"a", "aa", "aaa"}
[play]
Reject
和Filter相反,返回predicate返回false的元素。
odd := lo.Reject([]int{1, 2, 3, 4}, func(x int, _ int) bool {
return x%2 == 0
})
// []int{1, 3}
[play]
Count
计算切片中和value相等的元素的个数。
count := lo.Count([]int{1, 5, 1}, 1)
// 2
[play]
CountBy
计算切片中满足predicate函数的元素的个数。
count := lo.CountBy([]int{1, 5, 1}, func(i int) bool {
return i < 4
})
// 2
[play]
CountValues
计算切片中每个元素的个数。其中key是元素,value是个数。
lo.CountValues([]int{})
// map[int]int{}
lo.CountValues([]int{1, 2})
// map[int]int{1: 1, 2: 1}
lo.CountValues([]int{1, 2, 2})
// map[int]int{1: 1, 2: 2}
lo.CountValues([]string{"foo", "bar", ""})
// map[string]int{"": 1, "foo": 1, "bar": 1}
lo.CountValues([]string{"foo", "bar", "bar"})
// map[string]int{"foo": 1, "bar": 2}
[play]
CountValuesBy
计算切片中每个元素满足predicate函数的个数。其中key是元素,value是个数。
isEven := func(v int) bool {
return v%2==0
}
lo.CountValuesBy([]int{}, isEven)
// map[bool]int{}
lo.CountValuesBy([]int{1, 2}, isEven)
// map[bool]int{false: 1, true: 1}
lo.CountValuesBy([]int{1, 2, 2}, isEven)
// map[bool]int{false: 1, true: 2}
length := func(v string) int {
return len(v)
}
lo.CountValuesBy([]string{"foo", "bar", ""}, length)
// map[int]int{0: 1, 3: 2}
lo.CountValuesBy([]string{"foo", "bar", "bar"}, length)
// map[int]int{3: 3}
[play]
Subset
返回切片中从offset开始的length个元素。但是不会因为越界而panic。
in := []int{0, 1, 2, 3, 4}
sub := lo.Subset(in, 2, 3)
// []int{2, 3, 4}
sub := lo.Subset(in, -4, 3)
// []int{1, 2, 3}
sub := lo.Subset(in, -2, math.MaxUint)
// []int{3, 4}
[play]
Slice
返回一个切片的副本,从start
到end
,但是不包括end
。不会因为越界而panic。
in := []int{0, 1, 2, 3, 4}
slice := lo.Slice(in, 0, 5)
// []int{0, 1, 2, 3, 4}
slice := lo.Slice(in, 2, 3)
// []int{2}
slice := lo.Slice(in, 2, 6)
// []int{2, 3, 4}
slice := lo.Slice(in, 4, 3)
// []int{}
[play]
Replace
返回一个切片的副本,将前n个old替换为new。 第二个参数是old的值,第三个参数是new的值,第四个参数是替换的个数。
in := []int{0, 1, 0, 1, 2, 3, 0}
slice := lo.Replace(in, 0, 42, 1)
// []int{42, 1, 0, 1, 2, 3, 0}
slice := lo.Replace(in, -1, 42, 1)
// []int{0, 1, 0, 1, 2, 3, 0}
slice := lo.Replace(in, 0, 42, 2)
// []int{42, 1, 42, 1, 2, 3, 0}
slice := lo.Replace(in, 0, 42, -1)
// []int{42, 1, 42, 1, 2, 3, 42}
[play]
ReplaceAll
返回一个切片的副本,将所有的old替换为new。
in := []int{0, 1, 0, 1, 2, 3, 0}
slice := lo.ReplaceAll(in, 0, 42)
// []int{42, 1, 42, 1, 2, 3, 42}
slice := lo.ReplaceAll(in, -1, 42)
// []int{0, 1, 0, 1, 2, 3, 0}
[play]
Compact
去掉所有零值
in := []string{"", "foo", "", "bar", ""}
slice := lo.Compact[string](in)
// []string{"foo", "bar"}
[play]
IsSorted
检查切片是否是有序的。
slice := lo.IsSorted([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
// true
[play]
IsSortedByKey
检查切片是否是按照callback函数的返回值排序的。
slice := lo.IsSortedByKey([]string{"a", "bb", "ccc"}, func(s string) int {
return len(s)
})
// true
[play]