Go語言實現的Java Stream API

tk103331發表於2019-09-05

學習Go語言時實現的集合操作工具庫,類似於Java 8 中新增的Stream API。由於Go語言不支援泛型,所以基於反射實現。只用於學習目的,不要用於生產(PS:當然也不會有人用)。

專案地址:https://github.com/tk103331/stream

集合操作包括生成操作、中間操作和終止操作。 生成操作返回值是Steam物件,相當於資料的源頭,可以呼叫Stream的其他方法;中間操作返回值是Stream物件,可以繼續呼叫Stream的方法,即可以鏈式呼叫方法;終止操作不能繼續呼叫方法。

下面介紹下這個庫的API:


資料準備 後面的操作都是基於集合資料的,先準備一些測試資料。

type student struct {
    id int
    name   string
    ageint
    scores []int
}

func (s *student) String() string {
    return fmt.Sprintf("{id:%d, name:%s, age:%d,scores:%v}", s.id, s.name, s.age, s.scores)
}

func createStudents() []student {
    names := []string{"Tom", "Kate", "Lucy", "Jim", "Jack", "King", "Lee", "Mask"}
    students := make([]student, 10)
    rnd := func(start, end int) int { return rand.Intn(end-start) + start }
    for i := 0; i < 10; i++ {
        students[i] = student{
            id: i + 1,
            name:   names[rand.Intn(len(names))],
            age:rnd(15, 26),
            scores: []int{rnd(60, 100), rnd(60, 100), rnd(60, 100)},
        }
    }
    return students
}

type node struct {
    id   int
    next *node
}

func createNodes() *node {
    i := 10
    n := &node{id: i}
    for i > 0 {
        i--
        n = &node{id: i, next: n}
    }
    return n
}

迴圈遍歷 ForEach

迴圈遍歷集合中的每一個元素,需要提供一個包含一個引數的處理函式作為引數,形如 func(o T),迴圈遍歷時會把每個元素作為處理函式的實參。 ForEach 方法是終止操作。

func (s *stream) ForEach(actFunc interface{})

例子:

students := createStudents()
stream, _ := New(students)

stream.ForEach(func(s student) {
    fmt.Printf("\t%s\n", s.String())
})

輸出:

{id:1, name:Kate, age:16,scores:[67 79 61]}
{id:2, name:Lee, age:22,scores:[80 76 80]}
{id:3, name:Lee, age:15,scores:[62 69 68]}
{id:4, name:Lucy, age:22,scores:[65 97 86]}
{id:5, name:Mask, age:15,scores:[68 78 67]}
{id:6, name:Jim, age:20,scores:[68 90 75]}
{id:7, name:King, age:22,scores:[87 91 89]}
{id:8, name:Jack, age:16,scores:[91 65 86]}
{id:9, name:King, age:21,scores:[94 63 93]}
{id:10, name:Jim, age:20,scores:[64 99 93]}

迭代器 Iterate

It 方法可以從一個迭代器中建立一個Stream物件,迭代器就是一個迭代產生資料的迭代函式,迭代函式形如 func(prev T) (next T,more bool),迭代函式的引數為上一個元素的值,返回值是下一個元素的值,和是否還有更多元素。 It 方法是生成操作。

func It(initValue interface{}, itFunc interface{}) (*stream, error)

Sample:

stream, _ := It(root, func(n *node) (*node, bool) {
    return n.next, n.next.next != nil
})
stream.ForEach(func(n *node) {
    fmt.Printf("\tnode{id:%d}\n", n.id)
})

Output:

node{id:1}
node{id:2}
node{id:3}
node{id:4}
node{id:5}
node{id:6}
node{id:7}
node{id:8}
node{id:9}
node{id:10}

生成器 Generate

Gen 方法可以從一個生成器中建立一個Stream物件,生成器就是一個不斷產生資料的生成函式,生成函式形如 func() (next T,more bool),生成函式沒有引數,返回值是下一個元素的值,和是否還有更多元素。 Gen 方法是生成操作。 Gen 方法和It 方法的區別就是,它可以不依賴上一個元素的值。

func Gen(genFunc interface{}) (*stream, error)

例子:

stream, _ := Gen(func() (int, bool) {
    x := rand.Intn(10)
    return x, x < 8
})
stream.ForEach(func(x int) {
    fmt.Printf("\t%d\n", x)
})

輸出:

1
7
7
9

過濾 Filter

Filter 方法對集合中的元素進行過濾,篩選出符合條件的元素,需要提供一個過濾函式,過濾函式形如func(o T) bool,引數為集合中的元素,返回值是表示該元素是否符合條件。 Filter 方法是中間操作。

func (s *stream) Filter(filterFunc interface{}) *stream

例子:

students := createStudents()
stream, _ := New(students)

stream.Filter(func(s student) bool {
    return s.age > 20
}).ForEach(func(s student) {
    fmt.Printf("\t%s\n", s.String())
})

輸出:

{id:2, name:Lee, age:22,scores:[80 76 80]}
{id:4, name:Lucy, age:22,scores:[65 97 86]}
{id:7, name:King, age:22,scores:[87 91 89]}
{id:9, name:King, age:21,scores:[94 63 93]}

對映 Map

Map 方法可以將集合中的每個元素對映為新的值,從而得到一個新的集合,需要提供一個對映函式,形如func(o T1) T2,引數為集合中的元素,返回值是表示該元素對映的新值。 Map 方法是中間操作。

func (s *stream) Map(mapFunc interface{}) *stream

例子:

students := createStudents()
stream, _ := New(students)

stream.Map(func(s student) string {
    return s.name
}).ForEach(func(s string) {
    fmt.Printf("\t%s\n", s)
})

輸出:

Kate
Lee
Lee
Lucy
Mask
Jim
King
Jack
King
Jim

打平對映 FlatMap

FlatMap 方法可以將集合中每個元素對映為多個元素,返回新的集合包含對映的所有元素。需要提供一個對映函式,形如 func(o T1) []T2,引數為集合中的元素,返回值是表示該元素對映的新值的集合。 FlatMap 方法是中間操作。 FlatMap 方法和Map 方法的區別在於,它可以將集合中每個元素巢狀的集合打平,合併為新的集合。

func (s *stream) FlatMap(mapFunc interface{}) *stream

例子:

students := createStudents()
stream, _ := New(students)
var data []int
stream.FlatMap(func(s student) []int {
    return s.scores
}).ToSlice(&data)
fmt.Printf("\t%v\n", data)

輸出:

[67 79 61 80 76 80 62 69 68 65 97 86 68 78 67 68 90 75 87 91 89 91 65 86 94 63 93 64 99 93]

排序 Sort

Sort 方法根據一定隊則對集合中的元素進行排序,引數為比較函式,形如func(o1,o2 T) bool,引數為集合中的兩個元素,返回值為第一引數是否小於第二個引數。排序演算法使用sort中的排序演算法。 Sort 方法是中間操作。

func (s *stream) Sort(lessFunc interface{}) *stream

例子:

students := createStudents()
stream, _ := New(students)

stream.Sort(func(s1, s2 student) bool {
    return s1.scores[0]+s1.scores[1]+s1.scores[2] > s2.scores[0]+s2.scores[1]+s2.scores[2]
}).ForEach(func(s student) {
    fmt.Printf("\t%s\n", s.String())
})

輸出:

{id:7, name:King, age:22,scores:[87 91 89]}
{id:10, name:Jim, age:20,scores:[64 99 93]}
{id:9, name:King, age:21,scores:[94 63 93]}
{id:4, name:Lucy, age:22,scores:[65 97 86]}
{id:8, name:Jack, age:16,scores:[91 65 86]}
{id:2, name:Lee, age:22,scores:[80 76 80]}
{id:6, name:Jim, age:20,scores:[68 90 75]}
{id:5, name:Mask, age:15,scores:[68 78 67]}
{id:1, name:Kate, age:16,scores:[67 79 61]}
{id:3, name:Lee, age:15,scores:[62 69 68]}

去重 Distinct

Distinct 方法會對集合中的元素進行比較,並將重複的元素過濾掉. 引數為比較函式,形如 func(o1,o2 T) bool,引數為集合中的兩個元素,返回值為兩個元素是否相等。 Distinct 方法是中間操作。

func (s *stream) Distinct(equalFunc interface{}) *stream

例子:

students := createStudents()
stream, _ := New(students)

stream.Map(func(s student) string {
    return s.name
}).Distinct(func(p1, p2 string) bool {
    return p1 == p2
}).ForEach(func(s string) {
    fmt.Printf("\t%s\n", s)
})

輸出:

Kate
Lee
Lucy
Mask
Jim
King
Jack

提取 Peek

Peek 方法遍歷集合的每個元素,執行一定的處理,處理函式形如func(o T),引數為集合每一個元素,沒有返回值。 Peek 方法和 ForEach 方法的區別,它是一箇中間操作,可以繼續呼叫Stream的其他方法。

func (s *stream) Peek(peekFunc interface{}) *stream

例子:

students := createStudents()
stream, _ := New(students)

stream.Filter(func(s student) bool {
    return s.age%2 == 0
}).Call(func() {
    fmt.Println("\tfilter by age % 2 == 0")
}).Peek(func(s student) {
    fmt.Printf("\t%s\n", s.String())
}).Filter(func(s student) bool {
    return s.age > 18
}).Call(func() {
    fmt.Println("\tfilter by age > 18")
}).Peek(func(s student) {
    fmt.Printf("\t%s\n", s.String())
}).Exec()

輸出:

filter by age % 2 == 0
{id:1, name:Kate, age:16,scores:[67 79 61]}
{id:2, name:Lee, age:22,scores:[80 76 80]}
{id:4, name:Lucy, age:22,scores:[65 97 86]}
{id:6, name:Jim, age:20,scores:[68 90 75]}
{id:7, name:King, age:22,scores:[87 91 89]}
{id:8, name:Jack, age:16,scores:[91 65 86]}
{id:10, name:Jim, age:20,scores:[64 99 93]}
filter by age > 18
{id:2, name:Lee, age:22,scores:[80 76 80]}
{id:4, name:Lucy, age:22,scores:[65 97 86]}
{id:6, name:Jim, age:20,scores:[68 90 75]}
{id:7, name:King, age:22,scores:[87 91 89]}
{id:10, name:Jim, age:20,scores:[64 99 93]}

呼叫 Call

Call 方法可以在Stream物件執行過程中拿到集合的所有資料,可以對中間結果做一些處理,引數為處理函式,形如func(o []T),引數為整個集合的資料。 Call 方法為中間操作。

func (s *stream) Call(callFunc interface{}) *stream

檢查 Check

Check 方法可以在Stream物件執行過程中檢查是否需要進行後續操作,引數為判斷函式,形如func(o []T) bool,引數為整個集合的資料,返回值為是否繼續處理資料。 Check 方法為中間操作。 Check 方法與Call 方法的區分是,它可以終止整個Steam的執行。

func (s *stream) Check(checkFunc interface{}) *stream

限制 Limit

Limit 方法可以限制集合中元素的數量,引數為顯示的數量。 Limit 方法為中間操作。

func (s *stream) Limit(num int) *stream

例子:

students := createStudents()
stream, _ := New(students)

stream.Limit(5).Call(func() {
    fmt.Println("\tlimit by 5")
}).ForEach(func(s student) {
    fmt.Printf("\t%s\n", s.String())
})

輸出:

limit by 5
{id:1, name:Kate, age:16,scores:[67 79 61]}
{id:2, name:Lee, age:22,scores:[80 76 80]}
{id:3, name:Lee, age:15,scores:[62 69 68]}
{id:4, name:Lucy, age:22,scores:[65 97 86]}
{id:5, name:Mask, age:15,scores:[68 78 67]}

跳過 Skip

Skip 方法可以在處理過程中跳過指定數目的元素,引數為跳過的數量.

func (s *stream) Skip(num int) *stream

例子:

stream.Skip(5).Call(func() {
    fmt.Println("\tskip by 5")
}).ForEach(func(s student) {
    fmt.Printf("\t%s\n", s.String())
})

輸出:

skip by 5
{id:6, name:Jim, age:20,scores:[68 90 75]}
{id:7, name:King, age:22,scores:[87 91 89]}
{id:8, name:Jack, age:16,scores:[91 65 86]}
{id:9, name:King, age:21,scores:[94 63 93]}
{id:10, name:Jim, age:20,scores:[64 99 93]}

全部匹配 AllMatch

AllMatch 判斷集合中的元素是否都符合條件,需要提供一個判斷函式,形如 func(o T) bool , 引數為集合中的元素,返回值為是否條件。 AllMatch 方法為終止操作,返回值為是否所有都符合條件。

func (s *stream) AllMatch(matchFunc interface{}) bool

任一匹配 AnyMatch

AnyMatch 判斷集合中的元素是否有任一元素符合條件,需要提供一個判斷函式,形如 func(o T) bool ,引數為集合中的元素,返回值為是否條件。 AnyMatch 方法為終止操作,返回值為是否有任一元素符合條件。

func (s *stream) AnyMatch(matchFunc interface{}) bool

全不匹配 NoneMatch

NoneMatch 判斷集合中的元素是否所有元素都不符合條件,需要提供一個判斷函式,形如 func(o T) bool ,引數為集合中的元素,返回值為是否條件。 NoneMatch 方法為終止操作,返回值為是否所有元素都不符合條件。

func (s *stream) NoneMatch(matchFunc interface{}) bool

例子:

students := createStudents()
stream, _ := New(students)

r1 := stream.AllMatch(func(s student) bool {
    return s.age > 20
})
stream.Reset()
r2 := stream.AnyMatch(func(s student) bool {
    return s.name == "Jim"
})
stream.Reset()
r3 := stream.NoneMatch(func(s student) bool {
    return s.scores[0]+s.scores[1]+s.scores[2] > 270
})
fmt.Printf("\tAllMatch: %t, AnyMatch: %t, NoneMatch: %t \n", r1, r2, r3)

輸出:

AllMatch: false, AnyMatch: true, NoneMatch: true

計數 Count

Count 返回集合中元素的數量。 Count 為終止操作。

func (s *stream) Count() int

例子:

students := createStudents()
stream, _ := New(students)

r := stream.Count()
fmt.Printf("\t%d\n", r)

輸出:

10

分組 Group

Group 方法可以根據規則,將集合中的元素進行分組,需要提供一個分組函式,形如func(o T1) (key T2,value T3),引數為集合中的元素,返回值為分組的key和value。 Group 方法為終止操作,返回值為分組的map。

func (s *stream) Group(groupFunc interface{}) interface{}\

最大值 Max

Max 方法返回集合中最大的元素,需要提供一個比較函式,形如func(o1,o2 T) bool,引數為集合中的兩個元素,返回值為第一引數是否小於第二個引數。 Max 方法為終止操作。

func (s *stream) Max(lessFunc interface{}) interface{}

最小值 Min

Min 方法返回集合中最大的元素,需要提供一個比較函式,形如func(o1,o2 T) bool,引數為集合中的兩個元素,返回值為第一引數是否小於第二個引數。 Min 方法為終止操作。

func (s *stream) Min(lessFunc interface{}) interface{}

例子:

students := createStudents()
stream, _ := New(students)

r1 := stream.Max(func(s1, s2 student) bool {
    return s1.scores[0]+s1.scores[1]+s1.scores[2] < s2.scores[0]+s2.scores[1]+s2.scores[2]
})
stream.Reset()
r2 := stream.Min(func(s1, s2 student) bool {
    return s1.scores[0]+s1.scores[1]+s1.scores[2] < s2.scores[0]+s2.scores[1]+s2.scores[2]
})
fmt.Printf("\tMax: %v, Min: %v \n", r1, r2)

輸出:

Max: {7 King 22 [87 91 89]}, Min: {3 Lee 15 [62 69 68]} 

最先匹配 First

First 方法返回第一個符合條件的元素,需要提供一個匹配函式,形如 func(o T) bool,引數為集合中的元素,返回值表示該元素是否匹配條件。 First 為終止操作。

func (s *stream) First(matchFunc interface{}) interface{}

最後匹配 Last

First 方法返回第一個符合條件的元素,需要提供一個匹配函式,形如 func(o T) bool,引數為集合中的元素,返回值表示該元素是否匹配條件。 First 為終止操作。

func (s *stream) Last(matchFunc interface{}) interface{}

規約 Reduce

Reduce 方法可以基於一個初始值,遍歷將規約函式應用於集合中的每個元素,得到最終結果,規約函式形如 func(r T2,o T) T2,引數為前面的元素計算結果和當前元素,返回值為新的結果。 Reduce 為終止操作,返回值為規約計算後的結果。

func (s *stream) Reduce(initValue interface{}, reduceFunc interface{}) interface{}

例子:

students := createStudents()
stream, _ := New(students)

r := 0
r = stream.Map(func(s student) int {
    return s.scores[0]
}).Reduce(r, func(sum int, i int) int {
    return sum + i
}).(int)
fmt.Printf("\t%d\n", r)

輸出:

746

相關文章