From 8b637f9bae17f5800174b5538f61a94f0a8971c0 Mon Sep 17 00:00:00 2001
From: Deng Ming <mingflycash@gmail.com>
Date: Fri, 9 Sep 2022 18:56:30 +0800
Subject: [PATCH] =?UTF-8?q?slice:=20=E8=81=9A=E5=90=88=E5=87=BD=E6=95=B0?=
 =?UTF-8?q?=20Max,=20Min=20=E5=92=8C=20Sum?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .CHANGELOG.md           |   3 +-
 constrain.go            |  27 ++++++
 slice/aggregate.go      |  55 +++++++++++
 slice/aggregate_test.go | 205 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 289 insertions(+), 1 deletion(-)
 create mode 100644 constrain.go
 create mode 100644 slice/aggregate.go
 create mode 100644 slice/aggregate_test.go

diff --git a/.CHANGELOG.md b/.CHANGELOG.md
index 643e9562..24592bfb 100644
--- a/.CHANGELOG.md
+++ b/.CHANGELOG.md
@@ -23,4 +23,5 @@
 - [ekit: 实现了 TaskPool](https://github.com/gotomicro/ekit/pull/57)
 - [ekit: 修复OnDemandBlockTaskPool测试不稳定](https://github.com/gotomicro/ekit/pull/70)
 - [syncx: 使用泛型封装 sync.Map](https://github.com/gotomicro/ekit/pull/79)
-- [slice: 支持 Diff*, Intersection*, Union*, Index* 类方法](https://github.com/gotomicro/ekit/pull/83)
\ No newline at end of file
+- [slice: 支持 Diff*, Intersection*, Union*, Index* 类方法](https://github.com/gotomicro/ekit/pull/83)
+- [slice: 聚合函数 Max, Min 和 Sum](https://github.com/gotomicro/ekit/pull/82)
\ No newline at end of file
diff --git a/constrain.go b/constrain.go
new file mode 100644
index 00000000..33faccff
--- /dev/null
+++ b/constrain.go
@@ -0,0 +1,27 @@
+// Copyright 2021 gotomicro
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ekit
+
+// RealNumber 实数
+// 绝大多数情况下,你都应该用这个来表达数字的含义
+type RealNumber interface {
+	~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
+		~int | ~int8 | ~int16 | ~int32 | ~int64 |
+		~float32 | ~float64
+}
+
+type Number interface {
+	RealNumber | ~complex64 | ~complex128
+}
diff --git a/slice/aggregate.go b/slice/aggregate.go
new file mode 100644
index 00000000..889841f5
--- /dev/null
+++ b/slice/aggregate.go
@@ -0,0 +1,55 @@
+// Copyright 2021 gotomicro
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package slice
+
+import (
+	"github.com/gotomicro/ekit"
+)
+
+// Max 返回最大值。
+// 该方法假设你至少会传入一个值。
+// 在使用 float32 或者 float64 的时候要小心精度问题
+func Max[T ekit.RealNumber](ts []T) T {
+	res := ts[0]
+	for i := 1; i < len(ts); i++ {
+		if ts[i] > res {
+			res = ts[i]
+		}
+	}
+	return res
+}
+
+// Min 返回最小值
+// 该方法会假设你至少会传入一个值
+// 在使用 float32 或者 float64 的时候要小心精度问题
+func Min[T ekit.RealNumber](ts []T) T {
+	res := ts[0]
+	for i := 1; i < len(ts); i++ {
+		if ts[i] < res {
+			res = ts[i]
+		}
+	}
+	return res
+}
+
+// Sum 求和
+// 在使用 float32 或者 float64 的时候要小心精度问题
+func Sum[T ekit.Number](ts []T) T {
+	var res T
+	for _, n := range ts {
+		res += n
+	}
+	return res
+}
diff --git a/slice/aggregate_test.go b/slice/aggregate_test.go
new file mode 100644
index 00000000..a1778822
--- /dev/null
+++ b/slice/aggregate_test.go
@@ -0,0 +1,205 @@
+// Copyright 2021 gotomicro
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package slice
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/gotomicro/ekit"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestMax(t *testing.T) {
+	testCases := []struct {
+		name  string
+		input []Integer
+		want  Integer
+	}{
+		{
+			name:  "value",
+			input: []Integer{1},
+			want:  1,
+		},
+		{
+			name:  "values",
+			input: []Integer{2, 3, 1},
+			want:  3,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			res := Max[Integer](tc.input)
+			assert.Equal(t, tc.want, res)
+		})
+	}
+
+	assert.Panics(t, func() {
+		Max[int](nil)
+	})
+	assert.Panics(t, func() {
+		Max[int]([]int{})
+	})
+
+	testMaxTypes[uint](t)
+	testMaxTypes[uint8](t)
+	testMaxTypes[uint16](t)
+	testMaxTypes[uint32](t)
+	testMaxTypes[uint64](t)
+	testMaxTypes[int](t)
+	testMaxTypes[int8](t)
+	testMaxTypes[int16](t)
+	testMaxTypes[int32](t)
+	testMaxTypes[int64](t)
+	testMaxTypes[float32](t)
+	testMaxTypes[float64](t)
+}
+
+func TestMin(t *testing.T) {
+	testCases := []struct {
+		name  string
+		input []Integer
+		want  Integer
+	}{
+		{
+			name:  "value",
+			input: []Integer{3},
+			want:  3,
+		},
+		{
+			name:  "values",
+			input: []Integer{3, 1, 2},
+			want:  1,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			res := Min[Integer](tc.input)
+			assert.Equal(t, tc.want, res)
+		})
+	}
+
+	assert.Panics(t, func() {
+		Min[int](nil)
+	})
+	assert.Panics(t, func() {
+		Min[int]([]int{})
+	})
+
+	testMinTypes[uint](t)
+	testMinTypes[uint8](t)
+	testMinTypes[uint16](t)
+	testMinTypes[uint32](t)
+	testMinTypes[uint64](t)
+	testMinTypes[int](t)
+	testMinTypes[int8](t)
+	testMinTypes[int16](t)
+	testMinTypes[int32](t)
+	testMinTypes[int64](t)
+	testMinTypes[float32](t)
+	testMinTypes[float64](t)
+}
+
+func TestSum(t *testing.T) {
+	testCases := []struct {
+		name  string
+		input []Integer
+		want  Integer
+	}{
+		{
+			name: "nil",
+		},
+		{
+			name:  "empty",
+			input: []Integer{},
+		},
+		{
+			name:  "value",
+			input: []Integer{1},
+			want:  1,
+		},
+		{
+			name:  "values",
+			input: []Integer{1, 2, 3},
+			want:  6,
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			res := Sum[Integer](tc.input)
+			assert.Equal(t, tc.want, res)
+		})
+	}
+
+	testSumTypes[uint](t)
+	testSumTypes[uint8](t)
+	testSumTypes[uint16](t)
+	testSumTypes[uint32](t)
+	testSumTypes[uint64](t)
+	testSumTypes[int](t)
+	testSumTypes[int8](t)
+	testSumTypes[int16](t)
+	testSumTypes[int32](t)
+	testSumTypes[int64](t)
+	testSumTypes[float32](t)
+	testSumTypes[float64](t)
+}
+
+// testMaxTypes 只是用来测试一下满足 Max 方法约束的所有类型
+func testMaxTypes[T ekit.RealNumber](t *testing.T) {
+	res := Max[T]([]T{1, 2, 3})
+	assert.Equal(t, T(3), res)
+}
+
+// testMinTypes 只是用来测试一下满足 Min 方法约束的所有类型
+func testMinTypes[T ekit.RealNumber](t *testing.T) {
+	res := Min[T]([]T{1, 2, 3})
+	assert.Equal(t, T(1), res)
+}
+
+// testSumTypes 只是用来测试一下满足 Sum 方法约束的所有类型
+func testSumTypes[T ekit.RealNumber](t *testing.T) {
+	res := Sum[T]([]T{1, 2, 3})
+	assert.Equal(t, T(6), res)
+}
+
+type Integer int
+
+func ExampleSum() {
+	res := Sum[int]([]int{1, 2, 3})
+	fmt.Println(res)
+	res = Sum[int](nil)
+	fmt.Println(res)
+	// Output:
+	// 6
+	// 0
+}
+
+func ExampleMin() {
+	res := Min[int]([]int{1, 2, 3})
+	fmt.Println(res)
+	// Output:
+	// 1
+}
+
+func ExampleMax() {
+	res := Max[int]([]int{1, 2, 3})
+	fmt.Println(res)
+	// Output:
+	// 3
+}