一些在 Golang 中高效处理 Collection 类型的库
2024-5-5 17:40:44 Author: cloudsjhan.github.io(查看原文) 阅读量:15 收藏

处理集合是构建任何应用程序的重要部分。通常,您需要以下几类操作:

  • 转换:将某个函数应用于集合中的每个元素,以创建一个新类型的新集合;
  • 过滤:选择满足特定条件的集合中的元素;
  • 聚合:从集合中计算出单个结果,通常用于汇总;
  • 排序/排序:根据某些标准重新排列集合的元素;
  • 访问:根据其属性或位置检索元素的操作;
  • 实用程序:与集合一起工作的通用操作,但不一定完全适合上述分类。

尽管Go具有许多优点,但对于高级集合操作的内置支持相对有限,因此如果需要,您需要使用第三方包。在本文中,我将探讨几个流行的Go库,这些库可以增强语言的能力,以有效地处理集合,涵盖它们的功能和功能。这篇评论将帮助您选择合适的工具,以简化Go项目中的数据处理任务。

Introduction

让我们从上面的每个集合操作类中回顾一些流行的方法。

转换

Map — 对集合中的每个元素应用一个函数,并返回结果集合;
FlatMap — 将每个元素处理为一个元素列表,然后将这些列表展平为一个列表。

过滤

Filter — 删除不匹配谓词函数的元素;
Distinct — 从集合中删除重复的元素;
TakeWhile — 返回满足给定条件的元素,直到遇到不满足条件的元素为止;
DropWhile — 删除满足给定条件的元素,然后返回剩余的元素。

聚合

Reduce — 使用给定的函数组合集合的所有元素,并返回组合结果;
Count — 返回满足特定条件的元素数量;
Sum — 计算集合中每个元素的数字属性之和;
Max/Min — 确定元素属性中的最大值或最小值;
Average — 计算集合中元素的数字属性的平均值。

排序/排序

Sort — 根据比较器规则对集合的元素进行排序;
Reverse — 颠倒集合中元素的顺序。

访问

Find — 返回匹配给定谓词的第一个元素;
AtIndex — 检索特定索引处的元素。

实用程序

GroupBy — 根据键生成器函数将元素分类为组;
Partition — 根据谓词将集合分成两个集合:一个用于满足谓词的元素,另一个用于不满足谓词的元素;
Slice Operations — 修改集合视图或划分的操作,如切片或分块。

Go 内置的能力

在Go语言中,有几种类型可用于处理数据集合:

数组(Arrays) — 固定大小的元素集合。数组大小在声明时定义,例如 var myArray [5]int
切片(Slices) — 动态大小的元素集合。切片建立在数组之上,但与数组不同的是,它们可以增长或缩小。声明方式:mySlice := []int{1, 2, 3}
映射(Maps) — 键-值对的集合。映射可以动态增长,且键的顺序不受保证。例如 myMap := map[string]int{"first": 1, "second": 2} 创建了一个字符串键和整数值的映射;
通道(Channels) — 类型化的通信原语,允许在goroutine之间共享数据。例如 myChan := make(chan int) 创建了一个传输整数的通道。

Go标准库提供了其他结构和实用程序,可以作为集合或增强集合的功能,例如:

堆(Heap) — container/heap包为任何sort.Interface提供了堆操作。堆是具有以下特性的树:每个节点都是其子树中值最小的节点;
链表(List) — container/list包实现了双向链表;
环形链表(Ring) — container/ring包实现了环形链表的操作。

此外,作为Go标准库的一部分,还有用于处理切片和映射的包:

slices — 该包定义了与任何类型的切片一起使用的各种有用的函数;
maps — 该包定义了与任何类型的映射一起使用的各种有用的函数。

通过内置功能,您可以对集合执行一些操作:

获取数组/切片/映射的长度;
通过索引/键访问元素,对切片进行“切片”;
遍历项目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import "fmt"

func main() {
s := []int{1, 2, 3, 4, 5}
m := map[int]string{1: "one", 2: "two", 3: "three"}

fmt.Printf("len(s)=%d\n", len(s))
fmt.Printf("len(m)=%d\n", len(m))
fmt.Printf("cap(s)=%d\n", cap(s))
// fmt.Printf("cap(m)=%d\n", cap(m)) // error: invalid argument m (type map[int]string) for cap

// panic: runtime error: index out of range [5] with length 5
// fmt.Printf("s[5]=%d\n", s[5])

// panic: runtime error: index out of range [5] with length 5
// s[5] = 6

s = append(s, 6)
fmt.Printf("s=%v\n", s)
fmt.Printf("len(s)=%d\n", len(s))
fmt.Printf("cap(s)=%d\n", cap(s))

m[4] = "four"
fmt.Printf("m=%v\n", m)

fmt.Printf("s[2:4]=%v\n", s[2:4])
fmt.Printf("s[2:]=%v\n", s[2:])
fmt.Printf("s[:2]=%v\n", s[:2])
fmt.Printf("s[:]=%v\n", s[:])
}

上面的代码会打印:

1
2
3
4
5
6
7
8
9
10
11
len(s)=5
len(m)=3
cap(s)=5
s=[1 2 3 4 5 6]
len(s)=6
cap(s)=10
m=map[1:one 2:two 3:three 4:four]
s[2:4]=[3 4]
s[2:]=[3 4 5 6]
s[:2]=[1 2]
s[:]=[1 2 3 4 5 6]

让我们逐个看下 Go 内置的 Package。

Slices

切片 slices包最近才出现在Go标准库中,从Go 1.21版本开始。这是语言中的一个重大进步,但我仍然更喜欢使用外部库来处理集合(您很快就会明白原因)。让我们来看看这个库如何支持所有的集合操作类别。

Aggregation

slices 能够快速在切片中找到最小/最大值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import (
"fmt"
"slices"
)

type Example struct {
Name string
Number int
}

func main() {
s := []int{1, 2, 3, 4, 5}

fmt.Printf("Min: %d\n", slices.Min(s))
fmt.Printf("Max: %d\n", slices.Max(s))

e := []Example{
{"A", 1},
{"B", 2},
{"C", 3},
{"D", 4},
}

fmt.Printf("Min: %v\n", slices.MinFunc(
e,
func(i, j Example) int {
return i.Number - j.Number
}),
)

fmt.Printf("Max: %v\n", slices.MaxFunc(
e,
func(i, j Example) int {
return i.Number - j.Number
}),
)
}

上面的代码打印:

1
2
3
4
Min: 1
Max: 5
Min: {A 1}
Max: {D 4}

Sorting/Ordering

slices 能够使用比较函数对切片进行排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
"fmt"
"slices"
)

type Example struct {
Name string
Number int
}

func main() {
s := []int{4, 2, 5, 1, 3}

slices.Sort(s)
fmt.Printf("Sorted: %v\n", s)

slices.Reverse(s)
fmt.Printf("Reversed: %v\n", s)

e := []Example{
{"C", 3},
{"A", 1},
{"D", 4},
{"B", 2},
}

slices.SortFunc(e, func(a, b Example) int {
return a.Number - b.Number
})

fmt.Printf("Sorted: %v\n", e)

slices.Reverse(e)
fmt.Printf("Reversed: %v\n", e)
}
1
2
3
4
Sorted: [1 2 3 4 5]
Reversed: [5 4 3 2 1]
Sorted: [{A 1} {B 2} {C 3} {D 4}]
Reversed: [{D 4} {C 3} {B 2} {A 1}]

不过这个方法有个缺点,就是排序是原地进行的,修改了原始切片。如果该方法返回一个新的排序后的切片,从而保留原始数组会更好一点。

访问元素

slices暴露了一些方法,允许用户在切片中查找元素的位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"fmt"
"slices"
)

type Example struct {
Name string
Number int
}

func main() {
s := []int{4, 2, 5, 1, 3}

i := slices.Index(s, 3)
fmt.Printf("Index of 3: %d\n", i)

e := []Example{
{"C", 3},
{"A", 1},
{"D", 4},
{"B", 2},
}

i = slices.IndexFunc(e, func(a Example) bool {
return a.Number == 3
})

fmt.Printf("Index of 3: %d\n", i)
}
1
2
Index of 3: 4
Index of 3: 0

如果你正在处理已排序的切片,你可以使用 BinarySearch 或 BinarySearchFunc 在排序的切片中搜索目标,并返回目标被找到的位置或目标将出现在排序顺序中的位置;它还返回一个布尔值,指示目标是否在切片中被找到。切片必须按递增顺序排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"slices"
)

func main() {
s := []int{4, 2, 5, 1, 3}

slices.Sort(s)

i, found := slices.BinarySearch(s, 3)
fmt.Printf("Position of 3: %d. Found: %t\n", i, found)

i, found = slices.BinarySearch(s, 6)
fmt.Printf("Position of 6: %d. Found: %t\n", i, found)
}
1
2
Position of 3: 2. Found: true
Position of 6: 5. Found: false

实用函数

slices提供了许多实用函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import (
"fmt"
"slices"
)

type Example struct {
Name string
Number int
}

func main() {
e1 := []Example{
{"C", 3},
{"A", 1},
{"D", 4},
{"B", 2},
}

e2 := []Example{
{"A", 1},
{"B", 2},
{"C", 3},
{"D", 4},
}

fmt.Printf("Compare: %v\n", slices.CompareFunc(e1, e2, func(a, b Example) int {
return a.Number - b.Number
}))

fmt.Printf("Contains: %v\n", slices.ContainsFunc(e1, func(a Example) bool {
return a.Number == 2
}))

fmt.Printf("Delete: %v\n", slices.Delete(e1, 2, 3))
fmt.Printf("Equal: %v\n", slices.Equal(e1, e2))

fmt.Printf("Is Sorted: %v\n", slices.IsSortedFunc(e1, func(a, b Example) int {
return a.Number - b.Number
}))
}
1
2
3
4
5
Compare: 2
Contains: true
Delete: [{C 3} {A 1} {B 2}]
Equal: false
Is Sorted: false

slices 包的官方文档地址

Map

类似于slices,maps也是从Go 1.21开始出现在Go标准库中的。它定义了各种方法来操作 maps。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
"maps"
)

func main() {
m := map[int]string{1: "one", 2: "two", 3: "three"}
c := maps.Clone(m)

c[4] = "four"

fmt.Printf("Original: %v\n", m)
fmt.Printf("Clone: %v\n", c)

maps.DeleteFunc(c, func(k int, v string) bool { return k%2 == 0 })
fmt.Printf("DeleteFunc: %v\n", c)

fmt.Printf("Equal: %v\n", maps.Equal(m, c))
fmt.Printf("EqualFunc: %v\n", maps.EqualFunc(m, c, func(v1, v2 string) bool { return v1 == v2 }))
}
1
2
3
4
5
Original: map[1:one 2:two 3:three]
Clone: map[1:one 2:two 3:three 4:four]
DeleteFunc: map[1:one 3:three]
Equal: false
EqualFunc: false

maps 包的官方文档地址

github.com/elliotchance/pie

这是我个人最喜欢的用来操作切片和映射的包。它提供了一种独特的语法,使您能够无缝地链接操作,提高了代码的可读性和效率。

使用库方法有四种方式:

  1. 纯调用 — 只需调用库方法并提供所需的参数;
  2. pie.Of — 链接多个操作,支持任何元素类型;
  3. pie.OfOrdered — 链接多个操作,支持数字和字符串类型;
  4. pie.OfNumeric — 链接多个操作,仅支持数字类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package main

import (
"fmt"
"strings"

"github.com/elliotchance/pie/v2"
)

type Example struct {
Name string
Number int
}

func main() {
e := []Example{
{"C", 3},
{"A", 1},
{"D", 4},
{"B", 2},
}

fmt.Printf(
"Map 1: %v\n",
pie.Sort(
pie.Map(
e,
func(e Example) string {
return e.Name
},
),
),
)

fmt.Printf(
"Map 2: %v\n",
pie.Of(e).
Map(func(e Example) Example {
return Example{
Name: e.Name,
Number: e.Number * 2,
}
}).
SortUsing(func(a, b Example) bool {
return a.Number < b.Number
}),
)

fmt.Printf(
"Map 3: %v\n",
pie.OfOrdered([]string{"A", "C", "B", "A"}).
Map(func(e string) string {
return strings.ToLower(e)
}).
Sort(),
)

fmt.Printf(
"Map 4: %v\n",
pie.OfNumeric([]int{4, 1, 3, 2}).
Map(func(e int) int {
return e * 2
}).
Sort(),
)
}
1
2
3
4
Map 1: [A B C D]
Map 2: {[{A 2} {B 4} {C 6} {D 8}]}
Map 3: {[a a b c]}
Map 4: {[2 4 6 8]}

由于诸如 Map 等函数应该返回相同类型的集合,因此这个库的链式操作相当受限。因此,我认为纯方法调用是使用这个库的最佳方式。

该库提供了 Map 方法,允许将每个元素从一种类型转换为另一种类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"fmt"

"github.com/elliotchance/pie/v2"
)

type Example struct {
Name string
Number int
}

func main() {
e := []Example{
{"C", 3},
{"A", 1},
{"D", 4},
{"B", 2},
}

fmt.Printf(
"Map: %v\n",
pie.Map(
e,
func(e Example) string {
return e.Name
},
),
)
}

还提供了 Flat 方法,它将二维切片转换为一维切片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
"fmt"

"github.com/elliotchance/pie/v2"
)

type Person struct {
Name string
Tags []string
}

func main() {
p := []Person{
{"Alice", []string{"a", "b", "c"}},
{"Bob", []string{"b", "c", "d"}},
{"Charlie", []string{"c", "d", "e"}},
}

fmt.Printf(
"Unique Tags: %v\n",
pie.Unique(
pie.Flat(
pie.Map(
p,
func(e Person) []string {
return e.Tags
},
),
),
),
)
}
1
Unique Tags: [b c d e a]

使用 Keys 或 Values 方法可以仅获取 Map 的键或值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"

"github.com/elliotchance/pie/v2"
)

func main() {
m := map[int]string{
1: "one",
2: "two",
3: "three",
}

fmt.Printf("Keys: %v\n", pie.Keys(m))
fmt.Printf("Values: %v\n", pie.Values(m))
}

Output:

1
2
Keys: [3 1 2]
Values: [one two three]

Filter

该库提供了几种过滤原始集合的方法:Bottom、DropTop、DropWhile、Filter、FilterNot、Unique 等。

1
2
3
4
5
6
Bottom 3: [4 4 4]
Drop top 3: [3 3 3 4 4 4 4]
Drop while 3: [3 3 3 4 4 4 4]
Filter even: [2 2 4 4 4 4]
Filter not even: [1 3 3 3]
Unique values: [1 2 3 4]

Aggregation

有一个通用的聚合方法 Reduce。让我们来计算标准差:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
"math"

"github.com/elliotchance/pie/v2"
)

func main() {
v := []float64{1.1, 2.2, 3.3, 4.4, 5.5}

avg := pie.Average(v)
count := len(v)

sum2 := pie.Reduce(
v,
func(acc, value float64) float64 {
return acc + (value-avg)*(value-avg)
},
) - v[0] + (v[0]-avg)*(v[0]-avg)

d := math.Sqrt(sum2 / float64(count))

fmt.Printf("Standard deviation: %f\n", d)
}

Output:

1
Standard deviation: 1.555635

Reduce 方法首先将第一个切片元素作为累积值,将第二个元素作为值参数调用 reducer。这就是为什么公式看起来很奇怪。

从下面的示例中,可以找到另一个内置的聚合方法 Average。此外,您还可以找到 Min、Max、Product 等方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"

"github.com/elliotchance/pie/v2"
)

func main() {
v := []float64{1.1, 2.2, 3.3, 4.4, 5.5}

fmt.Printf("Average: %f\n", pie.Average(v))
fmt.Printf("Stddev: %f\n", pie.Stddev(v))
fmt.Printf("Max: %f\n", pie.Max(v))
fmt.Printf("Min: %f\n", pie.Min(v))
fmt.Printf("Sum: %f\n", pie.Sum(v))
fmt.Printf("Product: %f\n", pie.Product(v))

fmt.Printf("All >0: %t\n", pie.Of(v).All(func(value float64) bool { return value > 0 }))
fmt.Printf("Any >5: %t\n", pie.Of(v).Any(func(value float64) bool { return value > 5 }))

fmt.Printf("First: %f\n", pie.First(v))
fmt.Printf("Last: %f\n", pie.Last(v))

fmt.Printf("Are Unique: %t\n", pie.AreUnique(v))
fmt.Printf("Are Sorted: %t\n", pie.AreSorted(v))
fmt.Printf("Contains 3.3: %t\n", pie.Contains(v, 3.3))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
Average: 3.300000
Stddev: 1.555635
Max: 5.500000
Min: 1.100000
Sum: 16.500000
Product: 193.261200
All >0: true
Any >5: true
First: 1.100000
Last: 5.500000
Are Unique: true
Are Sorted: true
Contains 3.3: true

Sorting/Ordering

有三种不同的方法可以使用 pie 对切片进行排序:

Sort — 类似于 sort.Slice。但与 sort.Slice 不同的是,返回的切片将被重新分配,以不修改输入切片;
SortStableUsing — 类似于 sort.SliceStable。但与 sort.SliceStable 不同的是,返回的切片将被重新分配,以不修改输入切片;
SortUsing — 类似于 sort.Slice。但与 sort.Slice 不同的是,返回的切片将被重新分配,以不修改输入切片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"

"github.com/elliotchance/pie/v2"
)

func main() {
v := []int{3, 5, 1, 4, 2}

less := func(a, b int) bool {
return a < b
}

fmt.Printf("Sort: %v\n", pie.Sort(v))
fmt.Printf("SortStableUsing: %v\n", pie.SortStableUsing(v, less))
fmt.Printf("SortUsing: %v\n", pie.SortUsing(v, less))
fmt.Printf("Original: %v\n", v)
}

Output:

1
2
3
4
Sort: [1 2 3 4 5]
SortStableUsing: [1 2 3 4 5]
SortUsing: [1 2 3 4 5]
Original: [3 5 1 4 2]

Access

pie 提供了 FindFirstUsing 方法,用于获取切片中第一个与谓词匹配的元素的索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"fmt"

"github.com/elliotchance/pie/v2"
)

type Person struct {
Name string
Age int
}

func main() {
p := []Person{
{"Alice", 25},
{"Bob", 30},
{"Charlie", 35},
}

fmt.Printf(
"FindFirstUsing: %v\n",
pie.FindFirstUsing(
p,
func(p Person) bool {
return p.Age >= 30
},
),
)

}
1
FindFirstUsing: 2

pie 包含许多用于处理切片的实用方法。举几个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"fmt"
"math/rand"
"time"

"github.com/elliotchance/pie/v2"
)

type Person struct {
Name string
Age int
}

func main() {
p := []Person{
{"Alice", 25},
{"Bob", 30},
{"Charlie", 35},
{"David", 25},
{"Eve", 40},
{"Frank", 35},
}

fmt.Printf("Chunk: %v\n", pie.Chunk(p, 2))
fmt.Printf("GroupBy: %v\n", pie.GroupBy(p, func(p Person) int { return p.Age }))
fmt.Printf("Shuffle: %v\n", pie.Shuffle(p, rand.New(rand.NewSource(time.Now().UnixNano()))))
}

Output:

1
2
3
Chunk: [[{Alice 25} {Bob 30}] [{Charlie 35} {David 25}] [{Eve 40} {Frank 35}]]
GroupBy: map[25:[{Alice 25} {David 25}] 30:[{Bob 30}] 35:[{Charlie 35} {Frank 35}] 40:[{Eve 40}]]
Shuffle: [{Frank 35} {Bob 30} {David 25} {Eve 40} {Alice 25} {Charlie 35}]

下面是 pie 包的地址

elliotchance/pie/v2 库提供了一套非常完整的的处理集合的能力,极大地简化了在 Go 中处理切片的工作。其强大的方法用于操作和查询切片数据,为开发人员提供了一个强大的工具,增强了代码的可读性和效率。我强烈建议任何 Go 开发人员在下一个项目中尝试使用这个库。

github.com/samber/lo

另一个在 Go 中操作集合的流行库。在某些方面,它可能类似于流行的 JavaScript 库 Lodash。它在内部使用泛型,而不是反射。

Transform

该库支持默认的 MapFlatMap 方法用于切片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"fmt"

"github.com/samber/lo"
)

type Example struct {
Name string
Number int
}

func main() {
e := []Example{
{"C", 3},
{"A", 1},
{"D", 4},
{"B", 2},
}

fmt.Printf(
"Map: %v\n",
lo.Map(
e,
func(e Example, index int) string {
return e.Name
},
),
)
}
1
Map: [C A D B]

下面的代码演示如何操作 FlatMap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"fmt"

"github.com/samber/lo"
)

type Person struct {
Name string
Tags []string
}

func main() {
p := []Person{
{"Alice", []string{"a", "b", "c"}},
{"Bob", []string{"b", "c", "d"}},
{"Charlie", []string{"c", "d", "e"}},
}

fmt.Printf(
"Unique Tags: %v\n",
lo.Uniq(
lo.FlatMap(
p,
func(e Person, index int) []string {
return e.Tags
},
),
),
)
}

此外,还可以获取映射键、值或将映射对转换为某些切片等操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
"strings"

"github.com/samber/lo"
)

func main() {
m := map[int]string{
1: "one",
2: "two",
3: "three",
}

fmt.Printf("Keys: %v\n", lo.Keys(m))
fmt.Printf("Values: %v\n", lo.Values(m))
fmt.Printf("MapKeys: %v\n", lo.MapKeys(m, func(value string, num int) int { return num * 2 }))
fmt.Printf("MapValues: %v\n", lo.MapValues(m, func(value string, num int) string { return strings.ToUpper(value) }))
fmt.Printf("MapToSlice: %v\n", lo.MapToSlice(m, func(num int, value string) string { return value + ":" + fmt.Sprint(num) }))
}

Outputs:

1
2
3
4
5
Keys: [2 3 1]
Values: [one two three]
MapKeys: map[2:one 4:two 6:three]
MapValues: map[1:ONE 2:TWO 3:THREE]
MapToSlice: [three:3 one:1 two:2]

Filter

在 lo 库中有许多 Drop 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"

"github.com/samber/lo"
)

func main() {
v := []int{1, 2, 3, 4, 5}

fmt.Printf("Drop: %v\n", lo.Drop(v, 2))
fmt.Printf("DropRight: %v\n", lo.DropRight(v, 2))
fmt.Printf("DropWhile: %v\n", lo.DropWhile(v, func(i int) bool { return i < 3 }))
fmt.Printf("DropRightWhile: %v\n", lo.DropRightWhile(v, func(i int) bool { return i > 3 }))
}

Outputs:

1
2
3
4
Drop: [3 4 5]
DropRight: [1 2 3]
DropWhile: [3 4 5]
DropRightWhile: [1 2 3]

此外,还可以通过 predicate 过滤切片和映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"

"github.com/samber/lo"
)

func main() {
v := []int{1, 2, 3, 4, 5}
m := map[string]int{"a": 1, "b": 2, "c": 3}

fmt.Printf("Filter: %v\n", lo.Filter(v, func(i int, index int) bool { return i > 2 }))
fmt.Printf("PickBy: %v\n", lo.PickBy(m, func(key string, value int) bool { return value > 2 }))
}

Outputs:

1
2
Filter: [3 4 5]
PickBy: map[c:3]

Aggregation

lo package 提供了 reduce 的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
"math"

"github.com/samber/lo"
)

func main() {
v := []float64{1.1, 2.2, 3.3, 4.4, 5.5}

count := len(v)

avg := lo.Reduce(v, func(acc, val float64, index int) float64 {
return acc + val
}, 0.0) / float64(count)

sum2 := lo.Reduce(v, func(acc, val float64, index int) float64 {
return acc + (val-avg)*(val-avg)
}, 0.0)

d := math.Sqrt(sum2 / float64(count))

fmt.Printf("Standard deviation: %f\n", d)
}

Outputs:

1
Standard deviation: 1.555635

此外,它支持一些通用的聚合方法,如 Sum、Min、Max:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"

"github.com/samber/lo"
)

func main() {
v := []float64{1.1, 2.2, 3.3, 4.4, 5.5}

fmt.Printf("Sum: %v\n", lo.Sum(v))
fmt.Printf("Min: %v\n", lo.Min(v))
fmt.Printf("Max: %v\n", lo.Max(v))
}

有一些有用的方法用于处理 channel:FanIn 和 FanOut

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"fmt"

"github.com/samber/lo"
)

func main() {
ch1 := make(chan int)
ch2 := make(chan int)
ch3 := make(chan int)

ch := lo.FanIn(10, ch1, ch2, ch3)

for i := 0; i < 10; i++ {
if i%3 == 0 {
ch1 <- i
} else if i%3 == 1 {
ch2 <- i
} else {
ch3 <- i
}
}

close(ch1)
close(ch2)
close(ch3)

for v := range ch {
fmt.Println(v)
}
}

还可以这样处理 channel:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"

"github.com/samber/lo"
)

func main() {
ch := make(chan int)
chs := lo.FanOut(3, 10, ch)

for i := 0; i < 3; i++ {
ch <- i
}

close(ch)

for _, ch := range chs {
for v := range ch {
fmt.Println(v)
}
}
}

Sorting/Ordering

lo 还提供 Reverse 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"

"github.com/samber/lo"
)

func main() {
v := []int{1, 2, 3, 4, 5}

fmt.Printf("Reverse: %v\n", lo.Reverse(v))
}

Access

lo 库提供了 find 方法来访问元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import (
"fmt"

"github.com/samber/lo"
)

type Person struct {
Name string
Age int
}

func main() {
p := []Person{
{"Alice", 25},
{"Bob", 30},
{"Charlie", 35},
{"David", 25},
{"Edward", 40},
}

item, found := lo.Find(p, func(p Person) bool {
return p.Name == "Charlie"
})

fmt.Printf("Item: %+v, Found: %v\n", item, found)

fmt.Printf("FindDuplicatesBy: %v\n", lo.FindDuplicatesBy(p, func(p Person) int {
return p.Age
}))

item, index, found := lo.FindIndexOf(p, func(p Person) bool {
return p.Name == "Charlie"
})

fmt.Printf("Item: %+v, Index: %v, Found: %v\n", item, index, found)
}

Outputs:

1
2
3
Item: {Name:Charlie Age:35}, Found: true
FindDuplicatesBy: [{Alice 25}]
Item: {Name:Charlie Age:35}, Index: 2, Found: true

Find 方法同样支持 map:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"

"github.com/samber/lo"
)

func main() {
p := map[string]int{
"Alice": 34,
"Bob": 24,
"Charlie": 34,
"David": 29,
"Eve": 34,
}

key, found := lo.FindKey(p, 34)
fmt.Printf("Key: %v, Found: %v\n", key, found)
}

lo 的文档地址

总结

这里只展示了 github.com/samber/lo 库约 10% 的方法。这个库提供了许多简化函数处理的实用工具。对于 Go 开发人员来说,这个库是一个非常全面的工具包。

本文展示了一些在 Go 中处理集合时推荐的库,希望对大家的开发工作有帮助。



文章来源: https://cloudsjhan.github.io/2024/05/05/%E4%B8%80%E4%BA%9B%E5%9C%A8-Golang-%E4%B8%AD%E9%AB%98%E6%95%88%E5%A4%84%E7%90%86-Collection-%E7%B1%BB%E5%9E%8B%E7%9A%84%E5%BA%93/
如有侵权请联系:admin#unsafe.sh