十分鐘了解Golang集合類型數(shù)據(jù)操作
集合是構(gòu)建任何應(yīng)用程序的重要組成部分,對于集合來說,常見的有以下幾類操作:
- 轉(zhuǎn)換(transform) - 對集合中的每個元素應(yīng)用某種函數(shù)以創(chuàng)建新類型集合的操作;
- 過濾(filter) - 從集合中選擇滿足特定條件的元素的操作;
- 聚合(aggregation) - 從集合中計(jì)算出單一結(jié)果的操作,通常用于匯總;
- 分類(sorting)/排序(ordering) - 根據(jù)某些標(biāo)準(zhǔn)重新排列集合元素的操作;
- 訪問(access) - 根據(jù)元素屬性或位置檢索元素的操作;
- 通用(utility) - 與集合有關(guān)的通用操作,但不一定能夠完全歸入某個類別。
盡管 Go 有很多優(yōu)點(diǎn),但它對高級集合操作的內(nèi)置支持相對有限,因此在需要的時候有必要使用第三方軟件包。本文將探討幾種流行的 Go 庫的特性和功能,它們增強(qiáng)了 Go 高效處理集合的能力,從而幫助讀者選擇合適的工具,簡化 Go 項(xiàng)目中的數(shù)據(jù)處理任務(wù)。
導(dǎo)言
我們回顧一下上述每個集合操作的常用方法。
轉(zhuǎn)換(transform)
- Map - 對集合中的每個元素應(yīng)用一個函數(shù),并返回結(jié)果集合;
- FlatMap - 將每個元素處理為元素列表,然后將所有列表扁平化為單個列表。
過濾(filter)
- Filter - 刪除與過濾函數(shù)不匹配的元素;
- Distinct - 刪除集合中的重復(fù)元素;
- TakeWhile - 從頭開始返回滿足給定條件的元素;
- DropWhile - 從頭開始刪除符合給定條件的元素,返回剩余部分。
聚合(aggregation)
- Reduce - 使用給定函數(shù)合并集合中的所有元素,并返回合并結(jié)果;
- Count - 返回滿足特定條件的元素?cái)?shù)量;
- Sum - 計(jì)算集合中每個元素的數(shù)值屬性總和;
- Max/Min - 確定元素屬性的最大或最小值;
- Average - 計(jì)算集合中元素?cái)?shù)值屬性的平均值。
分類(sorting)/排序(ordering)
- Sort - 根據(jù)比較規(guī)則對集合中的元素進(jìn)行排序;
- Reverse - 逆序排列集合中的元素。
訪問(access)
- Find - 返回與查詢函數(shù)匹配的第一個元素;
- AtIndex - 檢索特定索引位置的元素。
通用(utility)
- GroupBy - 根據(jù)索引鍵生成器函數(shù)將元素分類;
- Partition - 根據(jù)分類函數(shù)將一個集合分成兩個集合:一個是滿足分類函數(shù)的元素集合,另一個是不滿足分類函數(shù)的元素集合;
- Slice Operations - 像切片或分塊這樣的操作,可以自定義查看或分割集合的方法。
Go 內(nèi)置功能
在 Go 中,有幾種類型可以處理數(shù)據(jù)集合:
- Arrays(數(shù)組) - 固定大小的元素集合。數(shù)組大小在聲明時定義 var myArray [5]int;
- Slices(切片) - 動態(tài)大小的元素集合。切片建立在數(shù)組之上,但與數(shù)組不同的是,它們可以增大或縮小。聲明:mySlice = []int{1, 2, 3};
- Maps(映射) - 鍵值對的集合。map 可以動態(tài)增長,但不保證鍵的順序。myMap := map[string]int{"first":1, "second":2} 創(chuàng)建了一個鍵為字符串、值為整數(shù)的 map。
- Channels(通道) - 強(qiáng)類型通信原語,允許在程序之間共享數(shù)據(jù)。myChan := make(chan int) 創(chuàng)建了一個用于傳輸整數(shù)的通道。
Go 標(biāo)準(zhǔn)庫提供了可充當(dāng)或增強(qiáng)集合的其他數(shù)據(jù)結(jié)構(gòu)及工具,例如:
- Heap - container/heap包為任何sort.Interface接口提供堆操作。堆是一棵樹,其屬性是每個節(jié)點(diǎn)都是其子樹中的最小值節(jié)點(diǎn);
- List — container/list包實(shí)現(xiàn)了雙向鏈表;
- Ring — container/ring包實(shí)現(xiàn)了對循環(huán)列表的操作。
作為 Go 標(biāo)準(zhǔn)庫的一部分,還有用于處理切片和映射的軟件包:
- slices - 該軟件包定義了對任何類型的切片都可用的各種函數(shù);
- maps - 該軟件包定義了對任何類型的映射都有用的各種函數(shù)。
通過內(nèi)置功能,可以對集合進(jìn)行如下操作:
- 獲取數(shù)組/切片/映射的長度;
- 通過索引/鍵訪問元素,"切片"一個片段;
- 遍歷元素。
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[:])
}
該代碼將會打印如下內(nèi)容:
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]
讓我們回顧一下內(nèi)置庫的功能!
切片
從 Go 1.21 開始,Go 標(biāo)準(zhǔn)庫中出現(xiàn)了slices包,這是 Go 語言向前邁出的重要一步,但我仍然更喜歡使用外部庫來處理集合(你很快就會明白為什么)。讓我們回顧一下該庫是如何支持所有集合操作類的。
支持的集合
切片
轉(zhuǎn)換
本軟件包不支持此類操作。
過濾
本軟件包不支持此類操作。
聚合
slices可以查找切片中的最小/最大值:
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
}),
)
}
上述代碼將打?。?/p>
Min: 1
Max: 5
Min: {A 1}
Max: {D 4}
不支持其他聚合。
分類/排序
slices可以使用比較函數(shù)對切片進(jìn)行排序:
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)
}
上述代碼將打?。?/p>
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}]
對我來說,最大的缺點(diǎn)是排序會修改原始切片。如果該方法能返回一個新的排序切片,從而保留原始數(shù)組,那就更好了。
訪問
slices提供了幾個方法,可以訪問元素在切片中的位置:
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)
}
上述代碼將打印:
Index of 3: 4
Index of 3: 0
如果要處理已排序的切片,可以使用BinarySearch或BinarySearchFunc在已排序切片中搜索目標(biāo),并返回找到目標(biāo)的位置或目標(biāo)在排序順序中出現(xiàn)的位置。同時它還返回一個布爾值,表示是否在片段中找到目標(biāo)。切片必須按遞增順序排序。
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)
}
上述代碼將打?。?/p>
Position of 3: 2. Found: true
Position of 6: 5. Found: false
通用
slices提供了多種通用函數(shù):
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
}))
}
上述代碼將打?。?/p>
Compare: 2
Contains: true
Delete: [{C 3} {A 1} {B 2}]
Equal: false
Is Sorted: false
文檔
https://pkg.go.dev/slices[2]
結(jié)論
在 Go 1.21 中引入的slices軟件包代表了在處理切片方面的重大改進(jìn)。然而,這個軟件包還缺乏很多重要功能,由于省略了一些更高級的集合操作,因此仍需要第三方庫來滿足更復(fù)雜的數(shù)據(jù)處理需求。
映射
與slices類似,maps從 Go 1.21 開始出現(xiàn)在 Go 標(biāo)準(zhǔn)庫中。正如所期望的那樣,它定義了各種操作映射的方法。
支持的集合
映射
轉(zhuǎn)換
本軟件包不支持此類操作。
過濾
本軟件包不支持此類操作。
匯聚
本軟件包不支持此類操作。
分類/排序
本軟件包不支持此類操作。
訪問
本軟件包不支持此類操作。
通用
本軟件包支持一組通用操作:
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 }))
}
上述代碼將打印:
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
文檔
https://pkg.go.dev/maps[3]
結(jié)論
maps包的功能比slices包更加有限。因此,如果需要對映射進(jìn)行更復(fù)雜的操作,幾乎肯定需要依賴第三方庫。
github.com/elliotchance/pie
這是我個人最喜歡的處理切片和映射的軟件包。它提供了獨(dú)特的語法,能讓我們無縫進(jìn)行鏈?zhǔn)讲僮?,提高代碼的可讀性和效率。
有四種方式使用該庫:
- 純調(diào)用 - 只需調(diào)用提供所需參數(shù)的庫方法;
- pie.Of - 對任何元素類型進(jìn)行鏈?zhǔn)蕉嘀夭僮鳎?/li>
- pie.OfOrdered - 對數(shù)字和字符串類型進(jìn)行鏈?zhǔn)蕉嘀夭僮鳎?/li>
- pie.OfNumeric - 僅對數(shù)字進(jìn)行鏈?zhǔn)蕉嘀夭僮鳌?/li>
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(),
)
}
上述代碼將打?。?/p>
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]}
該庫的鏈?zhǔn)讲僮鞣浅S邢蓿驗(yàn)镸ap等函數(shù)應(yīng)返回相同類型的集合,因此我認(rèn)為純方法調(diào)用是使用該庫的最佳方式。
支持的集合
切片、映射
轉(zhuǎn)換
庫中的Map方法允許將每個元素從一種類型轉(zhuǎn)換為另一種類型:
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
},
),
)
}
上述代碼將打?。?/p>
Map: [C A D B]
此外,還可以找到將二維切片轉(zhuǎn)換為單維的方法Flat:
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
},
),
),
),
)
}
上述代碼將打?。?/p>
Unique Tags: [b c d e a]
使用Keys或Values方法可以只獲取映射的鍵或值:
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))
}
上述代碼將打印:
Keys: [3 1 2]
Values: [one two three]
過濾
庫提供了多種過濾原始集合的方法:Bottom、DropTop、DropWhile、Filter、FilterNot、Unique等。
package main
import (
"fmt"
"github.com/elliotchance/pie/v2"
)
func main() {
v := []int{1, 2, 2, 3, 3, 3, 4, 4, 4, 4}
fmt.Printf("Bottom 3: %v\n", pie.Bottom(v, 3))
fmt.Printf("Drop top 3: %v\n", pie.DropTop(v, 3))
fmt.Printf("Drop while 3: %v\n", pie.DropWhile(v, func(value int) bool { return value < 3 }))
fmt.Printf("Filter even: %v\n", pie.Filter(v, func(value int) bool { return value%2 == 0 }))
fmt.Printf("Filter not even: %v\n", pie.FilterNot(v, func(value int) bool { return value%2 == 0 }))
fmt.Printf("Unique values: %v\n", pie.Unique(v))
}
上述代碼將打印:
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]
聚合
有一種通用方法可以進(jìn)行任何類型的聚合Reduce。我們來計(jì)算標(biāo)準(zhǔn)差:
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)
}
上述代碼將打?。?/p>
Standard deviation: 1.555635
Reduce方法第一次調(diào)用reducer時,第一個切片元素是累積值,第二個元素是值參數(shù)。這就是公式如此奇怪的原因。
從下面的示例中,可以找到另一種內(nèi)置聚合方法Average。此外,還可以找到Min、Max、Product等方法:
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))
}
上述代碼將打?。?/p>
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
分類/排序
pie提供了三種不同的方法來對切片進(jìn)行分類:
- Sort - 工作原理類似于sort.Slice,但與sort.Slice不同的是,返回的切片會重新分配內(nèi)存,以避免修改輸入切片;
- SortStableUsing - 工作原理類似于sort.SliceStable。不過,與sort.SliceStable不同的是,返回的切片會重新分配內(nèi)存,以避免修改輸入切片。
- SortUsing - 工作原理類似于sort.Slice。但是,與sort.Slice不同的是,返回的切片會重新分配內(nèi)存,以避免修改輸入切片。
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)
}
上述代碼將會打印:
Sort: [1 2 3 4 5]
SortStableUsing: [1 2 3 4 5]
SortUsing: [1 2 3 4 5]
Original: [3 5 1 4 2]
訪問
pie公開了FindFirstUsing方法,用于獲取切片中第一個匹配元素的索引:
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
},
),
)
}
上述代碼將打印:
FindFirstUsing: 2
通用
pie包含大量用于處理切片的實(shí)用方法。僅舉幾例:
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()))))
}
上述代碼將打印:
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}]
文檔
https://github.com/elliotchance/pie[4]https://pkg.go.dev/github.com/elliotchance/pie/v2[5]
結(jié)論
elliotchance/pie/v2庫提供了一套令人印象深刻的功能,極大簡化了 Go 語言中的切片處理,其用于操作和查詢切片數(shù)據(jù)的強(qiáng)大方法為開發(fā)人員提供了強(qiáng)大工具,提高了代碼的可讀性和效率。強(qiáng)烈建議所有 Go 開發(fā)人員在下一個項(xiàng)目中嘗試使用該庫。
github.com/samber/lo
這是另一個在 Go 中處理集合的流行庫。它在某些方面很像流行的 JavaScript 庫Lodash[6]。該庫基于泛型實(shí)現(xiàn),而不是反射。
支持的集合
切片、映射、通道
轉(zhuǎn)換
該庫支持用于切片的Map和FlatMap默認(rèn)方法:
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
},
),
)
}
上述代碼將打?。?/p>
Map: [C A D B]
下一個示例展示了FlatMap的使用:
下一個示例展示了 FlatMap 如何工作:
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
},
),
),
)
}
上述代碼將打印:
Unique Tags: [a b c d e]
此外還可以獲取映射鍵、值或?qū)⒂成滢D(zhuǎn)換到切片等:
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) }))
}
上述代碼將打印:
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]
過濾
lo庫中有許多Drop方法:
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 }))
}
上述代碼將打?。?/p>
Drop: [3 4 5]
DropRight: [1 2 3]
DropWhile: [3 4 5]
DropRightWhile: [1 2 3]
此外,還可以通過匿名函數(shù)過濾切片和映射:
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 }))
}
上述代碼將打印:
Filter: [3 4 5]
PickBy: map[c:3]
聚合
lo將Reduce方法暴露給聚合切片:
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)
}
上述代碼將打?。?/p>
Standard deviation: 1.555635
此外,該庫還支持一些通用聚合方法,如Sum、Min、Max:
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))
}
上述代碼將打印:
Standard deviation: 1.555635
有一些有用的方法可以使用通道:FanIn和FanOut:
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)
}
}
上述代碼將打?。?/p>
0
1
2
5
3
6
4
7
8
9
再舉一個例子:
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)
}
}
}
上述代碼將打?。?/p>
0
1
2
0
1
2
0
1
2
分類/排序
lo僅支持Reverse方法:
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))
}
上述代碼將打印:
Reverse: [5 4 3 2 1]
訪問
可以找到幾種在切片中查找元素的方法:
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)
}
上述代碼將打?。?/p>
Item: {Name:Charlie Age:35}, Found: true
FindDuplicatesBy: [{Alice 25}]
Item: {Name:Charlie Age:35}, Index: 2, Found: true
此外,還可以找到支持映射的方法:
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)
}
由于映射是無序結(jié)構(gòu),因此結(jié)果無法預(yù)測,可能會看到如下輸出:
Key: Charlie, Found: true
Key: Alice, Found: true
Key: Eve, Found: true
通用
可以在lo中找到大量方法。舉幾個例子:
package main
import (
"fmt"
"github.com/samber/lo"
)
func main() {
v1 := []int{1, 2, 3, 4, 5}
v2 := []int{3, 4, 5, 6, 7}
fmt.Printf("Chunk: %v\n", lo.Chunk(v1, 3))
fmt.Printf("Intersect: %v\n", lo.Intersect(v1, v2))
fmt.Printf("Union: %v\n", lo.Union(v1, v2))
diff1, diff2 := lo.Difference(v1, v2)
fmt.Printf("Difference: %v, %v\n", diff1, diff2)
}
上述代碼將打?。?/p>
Chunk: [[1 2 3] [4 5]]
Intersect: [3 4 5]
Union: [1 2 3 4 5 6 7]
Difference: [1 2], [6 7]
文檔
https://github.com/samber/lo[7]https://pkg.go.dev/github.com/samber/lo[8]
結(jié)論
上面只演示了github.com/samber/lo庫中約 10% 的方法,還有很多其他工具可以簡化函數(shù)的使用,該庫是 Go 開發(fā)人員的綜合工具包,是優(yōu)化 Go 開發(fā)工作流程的寶貴財(cái)富。
結(jié)論
Go 生態(tài)系統(tǒng)擁有豐富的工具,可以簡化和擴(kuò)展數(shù)據(jù)操作的功能。本文介紹了不同的方法來對切片、映射和通道執(zhí)行不同的操作。庫的選擇主要取決于項(xiàng)目的具體要求和開發(fā)人員對某些編程風(fēng)格的偏好。