概述

开发中,我们经常遇到一些操作,比如获取一个 map 的所有 key,所有 value,判断一个字符串是否出现在 slice 中,slice 中是否有重复元素等等。在 Python 中是直接支持的:d.keys()d.values()str in strListl.count(str)。Go 语言没有这样的操作,标准库也不提供。因此我们自己,或者团队会维护一些这类操作的包。

得益于 Go 泛型的发布,lo 就是这样的包,封装了大量简单操作,可以降低我们的代码量。

https://github.com/samber/lo

除了 lo,Go 官方也提供了一些实验性的包,比如 golang.org/x/exp/mapgolang.org/x/exp/slices。这些包针对特定的数据结构,更小,引入项目时更灵活,并且可能成为标准库的一部分。而 lo 则更全面。

下面主要介绍 lo

slice helpers

Filter

这个函数实现了 Python 中的 filter 函数:迭代一个 slice,计算每个元素,如果匿名函数返回 false,则被过滤掉。

even := lo.Filter[int]([]int{1, 2, 3, 4}, func(x int, _ int) bool {
    return x%2 == 0
})
// []int{2, 4}

Map

这个函数将 slice 中每个元素映射成另一个元素,新元素的类型不一定和源元素的相同。

lo.Map[int64, string]([]int64{1, 2, 3, 4}, func(x int64, _ int) string {
    return strconv.FormatInt(x, 10)
})
// []string{"1", "2", "3", "4"}

FilterMap

这个函数对 slice 同时执行 filter 和 map。分别调用 filter 和 map 也能实现这个功能,为什么还要实现 FilterMap 呢?原因也很简单,FilterMap 可以减少一个循环。

m := []string{"cpu", "gpu", "mouse", "keyboard"}
matching := lo.FilterMap[string, string](m, func(x string, _ int) (string, bool) {
    if strings.HasSuffix(x, "pu") {
        return "xpu", true
    }
    return "", false
})
// []string{"xpu", "xpu"} 

FlatMap

这个函数将 slice 中每个元素转换成另一个 slice,最后合并成一个 slice。

lo.FlatMap[int, string]([]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"}

Reduce

这个函数和 Python 中的 reduce 一样。将一个 slice 经过计算,变成一个标量值。

sum := lo.Reduce[int, int]([]int{1, 2, 3, 4}, func(agg int, item int, _ int) int {
    return agg + item
}, 0)
// 10

ReduceRight

和 reduce 一样,只不过从右向左迭代:

v := [][]int{{0, 1}, {2, 3}, {4, 5}}
result := lo.ReduceRight[[]int, []int](v, func(agg []int, item []int, _ int) []int {
  return append(agg, item...)
}, []int{}))
// []int{4, 5, 2, 3, 0, 1}

ForEach

lo.ForEach[string]([]string{"hello", "world"}, func(x string, _ int) {
    println(x)
})
// prints "hello\nworld\n"

此函数也有并行版本。每个元素在 goroutine 中执行。

import lop "github.com/samber/lo/parallel"

lop.ForEach[string]([]string{"hello", "world"}, func(x string, _ int) {
    println(x)
})
// prints "hello\nworld\n" or "world\nhello\n"

Times

调用指定函数 n 次,将每次的返回值合并成一个 slice。

import "github.com/samber/lo"

lo.Times[string](3, func(i int) string {
    return strconv.FormatInt(int64(i), 10)
})
// []string{"0", "1", "2"}

此函数也有一个并行版本:

import lop "github.com/samber/lo/parallel"

lop.Times[string](3, func(i int) string {
    return strconv.FormatInt(int64(i), 10)
})
// []string{"0", "1", "2"}

Uniq

去重。

uniqValues := lo.Uniq[int]([]int{1, 2, 2, 1})
// []int{1, 2}

UniqBy

去重的同时,对元素执行一些计算。

uniqValues := lo.UniqBy[int, int]([]int{0, 1, 2, 3, 4, 5}, func(i int) int {
    return i%3
})
// []int{0, 1, 2}

GroupBy[并行]

对 slice 元素分组,返回一个 map,key 是分组,value 是属于这个分组的元素。

import lo "github.com/samber/lo"

groups := lo.GroupBy[int, int]([]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}}

支持并行:

import lop "github.com/samber/lo/parallel"

lop.GroupBy[int, int]([]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

将 slice 分块。

lo. Chunk[int]([]int{0, 1, 2, 3, 4, 5}, 2)
// [][]int {{0, 1}, {2, 3}, {4, 5}}

lo. Chunk[int]([]int{0, 1, 2, 3, 4, 5, 6}, 2)
// [][]int {{0, 1}, {2, 3}, {4, 5}, {6}}

lo. Chunk[int]([]int{}, 2)
// [][]int{}

lo. Chunk[int]([]int{0}, 2)
// [][]int {{0}}

PartitionBy[并行]

和 groupBy 非常像,但返回 slice。

import lo "github. com/samber/lo"

partitions := lo. PartitionBy[int, string]([]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

将多层级的 slice 变成一层 slice。

flat := lo. Flatten[int]([][]int {{0, 1}, {2, 3, 4, 5}} )
// []int{0, 1, 2, 3, 4, 5}

Shuffle

使用 Fisher-Yates 算法洗牌。

randomOrder := lo. Shuffle[int]([]int{0, 1, 2, 3, 4, 5})
// []int{1, 4, 0, 3, 5, 2}

Reverse

反转顺序

reverseOrder := lo. Reverse[int]([]int{0, 1, 2, 3, 4, 5})
// []int{5, 4, 3, 2, 1, 0}

Fill

使用初始值对元素初始化。

type foo struct {
    bar string
}

func (f foo) Clone () foo {
    return foo{f.bar}
}

initializedSlice := lo.Fill[foo]([]foo{foo{"a"}, foo{"a"}}, foo{"b"})
// []foo{foo{"b"}, foo{"b"}}

Repeat

和 Python 中的 repeat 一样。

type foo struct {
bar string
}

func (f foo) Clone () foo {
return foo{f.bar}
}

slice := lo. Repeat[foo](2, foo{"a"})
// []foo{foo{"a"}, foo{"a"}}

RepeatBy

产生一个 slice,传入回调函数当前次数。

slice := lo. RepeatBy[string](0, func (i int) string {
    return strconv.FormatInt (math.Pow (i, 2), 10)
})
// []int{}

slice := lo. RepeatBy[string](5, func (i int) string {
    return strconv.FormatInt (math.Pow (i, 2), 10)
})
// []int{0, 1, 4, 9, 16}

KeyBy

对 slice 中的每个元素,计算出对应的 key,最后返回一个 map。

m := lo. KeyBy[int, string]([]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[string, Character](characters, func (char Character) string {
    return string (rune (char. code))
})
//map[a:{dir: left code: 97} d:{dir: right code: 100}]

Associate (alias: SliceToMap)

返回一个 map,其中的 key/value 对由回调函数给出,slice 中每个元素都会变成 map 中的一个 key/value 对。

in := []*foo {{baz: "apple", bar: 1}, {baz: "banana", bar: 2}} ,

aMap := lo. Associate[*foo, string, int](in, func (f *foo) (string, int) {
    return f.baz, f.bar
})
// map[string][int]{ "apple": 1, "banana": 2 }

Drop

丢掉前 n 个元素。

l := lo. Drop[int]([]int{0, 1, 2, 3, 4, 5}, 2)
// []int{2, 3, 4, 5}

DropRight

丢掉后 n 个元素。

l := lo. DropRight[int]([]int{0, 1, 2, 3, 4, 5}, 2)
// []int{0, 1, 2, 3}

DropWhile

从前往后,丢掉不符合回调函数要求的元素,直到某个元素不满足要求时停止,返回剩下的元素。剩下的元素不论满足不满足回调函数,都会返回。

l := lo. DropWhile[string]([]string{"a", "aa", "aaa", "aa", "aa"}, func (val string) bool {
return len (val) <= 2
})
// []string{"aaa", "aa", "aa"}

DropRightWhile

和 DropWhile 类似,从后往前计算,丢掉后面几个满足要求的元素,直到某个元素不满足要求时停止,返回剩下的元素。剩下的元素不论满足不满足回调函数,都会返回。

l := lo. DropRightWhile[string]([]string{"a", "aa", "aaa", "aa", "aa"}, func (val string) bool {
return len (val) <= 2
})
// []string{"a", "aa", "aaa"}

Reject

是 Filter 的反操作,拒绝掉会覅函数返回 true 的元素,返回剩下的元素。

odd := lo. Reject[int]([]int{1, 2, 3, 4}, func (x int, _ int) bool {
    return x%2 == 0
})
// []int{1, 3}

Count

和 python 的 list.count (x) 一样,返回某个元素出现的次数。

count := lo. Count[int]([]int{1, 5, 1}, 1)
// 2

CountBy

计算符合回调函数的元素的个数。

count := lo. CountBy[int]([]int{1, 5, 1}, func (i int) bool {
    return i < 4
})
// 2

Subset

实现了 slice[start:start+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}

Slice

实现了 slice[start: 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{}

Replace

替换 slice 中的元素,可以指定原始值,替换后的值,以及替换次数。

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}

ReplaceAll

和 Replace 一样,但不能指定替换次数。

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}

Compact

去除零值元素。

in := []string{"", "foo", "", "bar", ""}

slice := lo. Compact [string](in)
// []string{"foo", "bar"}

IsSorted

判断 slcie 是否有序

slice := lo.IsSorted ([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
// true

IsSortedByKey

判断 slice 是否关于某个 key 有序,回调函数计算每个元素的 key。

slice := lo.IsSortedByKey ([]string{"a", "bb", "ccc"}, func (s string) int {
    return len (s)
})
// true

map helpers

Keys

返回 map 的所有 key。

keys := lo. Keys[string, int](map[string]int{"foo": 1, "bar": 2})
// []string{"bar", "foo"}

Values

返回 map 的所有 value。

values := lo. Values[string, int](map[string]int{"foo": 1, "bar": 2})
// []int{1, 2}

PickBy

类似于 slice 的 filter。只返回一个 map 中符合条件的 key/value 对。

m := lo. PickBy[string, int](map[string]int{"foo": 1, "bar": 2, "baz": 3}, func (key string, value int) bool {
    return value%2 == 1
})
// map[string]int{"foo": 1, "baz": 3}

PickByKeys

只返回给定 key 的 map。

m := lo. PickByKeys[string, int](map[string]int{"foo": 1, "bar": 2, "baz": 3}, []string{"foo", "baz"})
// map[string]int{"foo": 1, "baz": 3}

PickByValues

只返回给定值的 map

m := lo. PickByValues[string, int](map[string]int{"foo": 1, "bar": 2, "baz": 3}, []int{1, 3})
// map[string]int{"foo": 1, "baz": 3}

OmitBy

忽略掉符合条件的,返回剩下的 map

m := lo. OmitBy[string, int](map[string]int{"foo": 1, "bar": 2, "baz": 3}, func (key string, value int) bool {
    return value%2 == 1
})
// map[string]int{"bar": 2}

OmitByKeys

忽略掉给定 key,剩下的 key 组成新 map

m := lo. OmitByKeys[string, int](map[string]int{"foo": 1, "bar": 2, "baz": 3}, []string{"foo", "baz"})
// map[string]int{"bar": 2}

OmitByValues

忽略掉给定 value,剩下的组成新 map

m := lo. OmitByValues[string, int](map[string]int{"foo": 1, "bar": 2, "baz": 3}, []int{1, 3})
// map[string]int{"bar": 2}

Entries (alias: ToPairs)

将 map 变成 key/value 对。

entries := lo. Entries[string, int](map[string]int{"foo": 1, "bar": 2})
// []lo. Entry[string, int]{
//     {
//         Key: "foo",
//         Value: 1,
//     },
//     {
//         Key: "bar",
//         Value: 2,
//     },
// }

FromEntries (alias: FromPairs)

将 key/value 对编程 map

m := lo. FromEntries[string, int]([]lo. Entry[string, int]{
    {
        Key: "foo",
        Value: 1,
    },
    {
        Key: "bar",
        Value: 2,
    },
})
// map[string]int{"foo": 1, "bar": 2}

Invert

key 变 value,value 变 key。可能因为重复的 value 而丢失数据。

m 1 := lo. Invert[string, int]([map[string]int{"a": 1, "b": 2})
// map[int]string{1: "a", 2: "b"}

m 2 := lo. Invert[string, int]([map[string]int{"a": 1, "b": 2, "c": 1})
// map[int]string{1: "c", 2: "b"}

Assign

从左到右,逐个合并 map,返回新 map。

mergedMaps := lo. Assign[string, int](
    map[string]int{"a": 1, "b": 2},
    map[string]int{"b": 3, "c": 4},
)
// map[string]int{"a": 1, "b": 3, "c": 4}

MapKeys

将 map 的 key 转成另一个值。

m 2 := lo. MapKeys[int, int, string](map[int]int{1: 1, 2: 2, 3: 3, 4: 4}, func (_ int, v int) string {
    return strconv.FormatInt (int 64 (v), 10)
})
// map[string]int{"1": 1, "2": 2, "3": 3, "4": 4}

MapValues

将 map 的 value 转成另一个值。

m 1 := map[int]int 64{1: 1, 2: 2, 3: 3}

m 2 := lo. MapValues[int, int 64, string](m 1, func (x int 64, _ int) string {
return strconv.FormatInt (x, 10)
})
// map[int]string{1: "1", 2: "2", 3: "3"}

MapToSlice

将 key/value 对转成一个值,放入 slice。

m := map[int]int 64{1: 4, 2: 5, 3: 6}

s := lo.MapToSlice (m, func (k int, v int 64) string {
    return fmt.Sprintf ("%d_%d", k, v)
})
// []string{"1_4", "2_5", "3_6"}

math helpers

这三个函数就是 Python 中的 range。

Range

result := Range (4)
// [0, 1, 2, 3]

result := Range (-4);
// [0, -1, -2, -3]

result := Range (0);
// []

RangeFrom

result := RangeFrom (1, 5);
// [1, 2, 3, 4]

result := RangeFrom[float 64](1.0, 5);
// [1.0, 2.0, 3.0, 4.0]

RangeWithSteps

result := RangeWithSteps (0, 20, 5);
// [0, 5, 10, 15]

result := RangeWithSteps[float 32](-1.0, -4.0, -1.0);
// [-1.0, -2.0, -3.0]

result := RangeWithSteps (1, 4, -1);
// []

Clamp

这个函数怎么用?

r 1 := lo.Clamp (0, -10, 10)
// 0

r 2 := lo.Clamp (-42, -10, 10)
// -10

r 3 := lo.Clamp (42, -10, 10)
// 10

SumBy

求和

strings := []string{"foo", "bar"}
sum := lo.SumBy (strings, func (item string) int {
    return len (item)
})
// 6

string helpers

Substring

sub := lo.Substring ("hello", 2, 3)
// "llo"

sub := lo.Substring ("hello", -4, 3)
// "ell"

sub := lo.Substring ("hello", -2, math. MaxUint)
// "lo"

ChunkString

分块

lo.ChunkString ("123456", 2)
// []string{"12", "34", "56"}

lo.ChunkString ("1234567", 2)
// []string{"12", "34", "56", "7"}

lo.ChunkString ("", 2)
// []string{""}

lo.ChunkString ("1", 2)
// []string{"1"}

RuneLength

sub := lo.RuneLength ("hellô")
// 5

sub := len ("hellô")
// 6

RandomString

随机字符串

lo.RandomString(6, lo.NumbersCharset)

tuple helpers

T 2 -> T 9

不懂

tuple 1 := lo. T 2[string, int]("x", 1)
// Tuple 2[string, int]{A: "x", B: 1}

func example () (string, int) { return "y", 2 }
tuple 2 := lo. T 2[string, int](example ())
// Tuple 2[string, int]{A: "y", B: 2}

Unpack 2 -> Unpack 9

不懂 + 1

r 1, r 2 := lo. Unpack 2[string, int](lo. Tuple 2[string, int]{"a", 1})
// "a", 1

Zip 2 -> Zip 9

不懂 + 2

tuples := lo. Zip 2[string, int]([]string{"a", "b"}, []int{1, 2})
// []Tuple 2[string, int] {{A: "a", B: 1}, {A: "b", B: 2}}

Unzip 2 -> Unzip 9

不懂 + 3

a, b := lo. Unzip 2[string, int]([]Tuple 2[string, int] {{A: "a", B: 1}, {A: "b", B: 2}} )
// []string{"a", "b"}
// []int{1, 2}

channel helpers

ChannelDispatcher

将一个 channel 扩展出多个二级 channel,然后多个 consumer 消费二级 channel。没想到这功能都被作者整进来了。

ch := make (chan int, 42)
for i := 0; i <= 10; i++ {
    ch <- i
}

children := lo.ChannelDispatcher (ch, 5, 10, DispatchingStrategyRoundRobin[int])
// []<-chan int{...}

consumer := func (c <-chan int) {
    for {
        msg, ok := <-c
        if !ok {
            println ("closed")
            break
        }

        println (msg)
    }
}

for i := range children {
    go consumer (children[i])
}

intersection helpers

Contains

present := lo. Contains[int]([]int{0, 1, 2, 3, 4, 5}, 5)
// true

ContainsBy

集合中包含符合某种条件的元素。

present := lo. ContainsBy[int]([]int{0, 1, 2, 3, 4, 5}, func (x int) bool {
    return x == 3
})
// true

Every

两个集合是否是包含关系。

这名字。。。。

ok := lo. Every[int]([]int{0, 1, 2, 3, 4, 5}, []int{0, 2})
// true

ok := lo. Every[int]([]int{0, 1, 2, 3, 4, 5}, []int{0, 6})
// false

EveryBy

集合元素是否都符合某种条件。

b := EveryBy[int]([]int{1, 2, 3, 4}, func (x int) bool {
    return x < 5
})
// true

Some

两个集合有交集就行。。。

ok := lo. Some[int]([]int{0, 1, 2, 3, 4, 5}, []int{0, 2})
// true

ok := lo. Some[int]([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6})
// false

SomeBy

集合有任意元素满足条件就行。。。

b := SomeBy[int]([]int{1, 2, 3, 4}, func (x int) bool {
    return x < 3
})
// true

None

两个集合没有交集

b := None[int]([]int{0, 1, 2, 3, 4, 5}, []int{0, 2})
// false
b := None[int]([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6})
// true

NoneBy

没有符合条件的元素就行。。。

b := NoneBy[int]([]int{1, 2, 3, 4}, func (x int) bool {
    return x < 0
})
// true

Intersect

返回两个集合的交集

result 1 := lo. Intersect[int]([]int{0, 1, 2, 3, 4, 5}, []int{0, 2})
// []int{0, 2}

result 2 := lo. Intersect[int]([]int{0, 1, 2, 3, 4, 5}, []int{0, 6}
// []int{0}

result 3 := lo. Intersect[int]([]int{0, 1, 2, 3, 4, 5}, []int{-1, 6})
// []int{}

Difference

会返回差集。left 为 A - B,right 为 B - A

left, right := lo. Difference[int]([]int{0, 1, 2, 3, 4, 5}, []int{0, 2, 6})
// []int{1, 3, 4, 5}, []int{6}

left, right := lo. Difference[int]([]int{0, 1, 2, 3, 4, 5}, []int{0, 1, 2, 3, 4, 5})
// []int{}, []int{}

Union

并集

union := lo. Union[int]([]int{0, 1, 2, 3, 4, 5}, []int{0, 2, 10})
// []int{0, 1, 2, 3, 4, 5, 10}

Without

叫 Remove 更合适

subset := lo. Without[int]([]int{0, 2, 10}, 2)
// []int{0, 10}

subset := lo. Without[int]([]int{0, 2, 10}, 0, 1, 2, 3, 4, 5)
// []int{10}

WithoutEmpty

移除 0 值

subset := lo. WithoutEmpty[int]([]int{0, 2, 10})
// []int{2, 10}

search helpers

IndexOf

found := lo. IndexOf[int]([]int{0, 1, 2, 1, 2, 3}, 2)
// 2

notFound := lo. IndexOf[int]([]int{0, 1, 2, 1, 2, 3}, 6)
// -1

LastIndexOf

found := lo. LastIndexOf[int]([]int{0, 1, 2, 1, 2, 3}, 2)
// 4

notFound := lo. LastIndexOf[int]([]int{0, 1, 2, 1, 2, 3}, 6)
// -1

conditional helpers

type manipulation helpers

function helpers

Partial

偏函数,像 Python 那样。

add := func (x, y int) int { return x + y }
f := lo.Partial (add, 5)

f (10)
// 15

f (42)
// 47

concurrency helpers

error handling

不建议使用。

这篇文章还在更新中。在看这些函数的时候,我又想起了 Python 中那些简单又易读的实现方式了。如果有一天 Go 也支持操作符重载就好。

这些函数是那么丑陋。。。

最后修改:2023 年 12 月 30 日
如果觉得我的文章对你有用,请随意赞赏