From 69fe76665dc3d4ddb5a008e9cdb79686670319c9 Mon Sep 17 00:00:00 2001 From: Deng Ming Date: Sun, 12 Jun 2022 12:27:26 +0800 Subject: [PATCH 01/32] ekit: add ToPtr function --- .CHANGELOG.md | 3 ++- .deepsource.toml | 6 +----- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- .github/ISSUE_TEMPLATE/question.md | 2 +- .github/linters/.golangci.yml | 4 ++-- .github/workflows/changelog.yml | 1 + .github/workflows/go.yml | 4 ++-- .github/workflows/license.yml | 1 + .licenserc.json | 2 +- Makefile | 2 +- README.md | 4 ++-- go.mod | 2 +- pool/pool_test.go | 2 +- ptr.go | 19 +++++++++++++++++ ptr_test.go | 26 +++++++++++++++++++++++ 16 files changed, 63 insertions(+), 19 deletions(-) create mode 100644 ptr.go create mode 100644 ptr_test.go diff --git a/.CHANGELOG.md b/.CHANGELOG.md index 78df33e9..3232ad6e 100644 --- a/.CHANGELOG.md +++ b/.CHANGELOG.md @@ -1 +1,2 @@ -# 开发中 \ No newline at end of file +# 开发中 +[ekit: add ToPtr function](https://github.com/gotomicro/ekit/pull/6) \ No newline at end of file diff --git a/.deepsource.toml b/.deepsource.toml index 28d321eb..2e9127ae 100644 --- a/.deepsource.toml +++ b/.deepsource.toml @@ -19,14 +19,10 @@ name = "go" enabled = true [analyzers.meta] - import_root = "github.com/gotomicro/ego-kit" + import_root = "github.com/gotomicro/ekit" dependencies_vendored = false [[analyzers]] name = "test-coverage" enabled = true -[[analyzers]] -name = "sql" -enabled = true - diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 369e6c96..b36b608b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -28,7 +28,7 @@ assignees: '' ### 你排查的结果,或者你觉得可行的修复方案 > 可选。我们希望你能够尽量先排查问题,帮助我们减轻维护负担。这对于你个人能力提升同样是有帮助的。 -### 你使用的是 ego-kit 哪个版本? +### 你使用的是 ekit 哪个版本? ### 你设置的的 Go 环境? > 上传 `go env` 的结果 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 51b95bda..86d29f2c 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -20,7 +20,7 @@ assignees: '' ### 其它 > 任何你觉得有利于解决问题的补充说明 -### 你使用的是 ego-kit 哪个版本? +### 你使用的是 ekit 哪个版本? ### 你设置的的 Go 环境? > 上传 `go env` 的结果 diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index b91e17c7..65d8e070 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -16,7 +16,7 @@ labels: question ### 你的问题 -### 你使用的是 ego-kit 哪个版本? +### 你使用的是 ekit 哪个版本? ### 你设置的的 Go 环境? > 上传 `go env` 的结果 diff --git a/.github/linters/.golangci.yml b/.github/linters/.golangci.yml index 513d535a..df364cb5 100644 --- a/.github/linters/.golangci.yml +++ b/.github/linters/.golangci.yml @@ -53,6 +53,6 @@ linters: linters-settings: gci: - local-prefixes: github.com/gotomicro/ego-kit + local-prefixes: github.com/gotomicro/ekit goimports: - local-prefixes: github.com/gotomicro/ego-kit + local-prefixes: github.com/gotomicro/ekit diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 0a8130cb..431f0731 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -20,6 +20,7 @@ on: branches: - develop - main + - dev jobs: changelog: diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 572814dc..4cba044a 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -16,9 +16,9 @@ name: Go on: push: - branches: [ main ] + branches: [ dev ] pull_request: - branches: [ main ] + branches: [ dev ] jobs: build: diff --git a/.github/workflows/license.yml b/.github/workflows/license.yml index 8110d5b5..24ca3167 100644 --- a/.github/workflows/license.yml +++ b/.github/workflows/license.yml @@ -19,6 +19,7 @@ on: branches: - develop - main + - dev jobs: check-license-lines: runs-on: ubuntu-latest diff --git a/.licenserc.json b/.licenserc.json index 688e5a13..a9304749 100644 --- a/.licenserc.json +++ b/.licenserc.json @@ -1,5 +1,5 @@ { "**/*.go": "// Copyright 2021 gotomicro", - "**/*.{yml,toml}": "# Copyright 2021 gotomicro" + "**/*.{yml,toml}": "# Copyright 2021 gotomicro", "**/*.sh": "# Copyright 2021 gotomicro" } \ No newline at end of file diff --git a/Makefile b/Makefile index af4cc89e..8e3cfff7 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ bench: @go test -bench=. -benchmem ./... -test: +ut: @go test -race ./... setup: diff --git a/README.md b/README.md index 8f9ff031..42453185 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# ego-kit -Stream API, Collection and ... +# ekit +泛型工具库 diff --git a/go.mod b/go.mod index 92d46dd2..2b86b347 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/gotomicro/ego-kit +module github.com/gotomicro/ekit go 1.18 diff --git a/pool/pool_test.go b/pool/pool_test.go index 22cb4576..1f2cc8f9 100644 --- a/pool/pool_test.go +++ b/pool/pool_test.go @@ -51,7 +51,7 @@ func ExampleNew() { // goos: linux // goarch: amd64 -// pkg: github.com/gotomicro/ego-kit/pkg/pool +// pkg: github.com/gotomicro/ekit/pkg/pool // cpu: Intel(R) Core(TM) i5-10400F CPU @ 2.90GHz // BenchmarkPool_Get/Pool-12 9190246 130.0 ns/op 0 B/op 0 allocs/op // BenchmarkPool_Get/sync.Pool-12 9102818 128.6 ns/op 0 B/op 0 allocs/op diff --git a/ptr.go b/ptr.go new file mode 100644 index 00000000..1b487800 --- /dev/null +++ b/ptr.go @@ -0,0 +1,19 @@ +// 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 + +func ToPtr[T any](t T) *T { + return &t +} diff --git a/ptr_test.go b/ptr_test.go new file mode 100644 index 00000000..e848ccf8 --- /dev/null +++ b/ptr_test.go @@ -0,0 +1,26 @@ +// 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 + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestToPtr(t *testing.T) { + i := 12 + res := ToPtr[int](i) + assert.Equal(t, &i, res) +} From 686daa085c86c9b505954e23fc48848830957c3c Mon Sep 17 00:00:00 2001 From: Deng Ming Date: Tue, 28 Jun 2022 21:48:25 +0800 Subject: [PATCH 02/32] =?UTF-8?q?sql:=20=E6=94=AF=E6=8C=81=20JsonColumn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .CHANGELOG.md | 3 +- sql/json.go | 65 ++++++++++++++++++++++ sql/json_test.go | 141 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 sql/json.go create mode 100644 sql/json_test.go diff --git a/.CHANGELOG.md b/.CHANGELOG.md index 3232ad6e..dc003c1e 100644 --- a/.CHANGELOG.md +++ b/.CHANGELOG.md @@ -1,2 +1,3 @@ # 开发中 -[ekit: add ToPtr function](https://github.com/gotomicro/ekit/pull/6) \ No newline at end of file +[ekit: add ToPtr function](https://github.com/gotomicro/ekit/pull/6) +[sql: 支持 JsonColumn](https://github.com/gotomicro/ekit/pull/7) \ No newline at end of file diff --git a/sql/json.go b/sql/json.go new file mode 100644 index 00000000..0838e34e --- /dev/null +++ b/sql/json.go @@ -0,0 +1,65 @@ +// 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 sql + +import ( + "database/sql" + "database/sql/driver" + "encoding/json" + "fmt" +) + +// JsonColumn 代表存储字段的 json 类型 +// 主要用于没有提供默认 json 类型的数据库 +// T 可以是结构体,也可以是切片或者 map +// 一切可以被 json 库所处理的类型都能被用作 T +type JsonColumn[T any] struct { + Val T + Valid bool +} + +// Value 返回一个 json 串。类型是 []byte +func (j JsonColumn[T]) Value() (driver.Value, error) { + if !j.Valid { + return nil, nil + } + return json.Marshal(j.Val) +} + +// Scan 将 src 转化为对象 +// src 的类型必须是 []byte, *[]byte, string, sql.RawBytes, *sql.RawBytes 之一 +func (j *JsonColumn[T]) Scan(src any) error { + var bs []byte + switch val := src.(type) { + case []byte: + bs = val + case *[]byte: + bs = *val + case string: + bs = []byte(val) + case sql.RawBytes: + bs = val + case *sql.RawBytes: + bs = *val + default: + return fmt.Errorf("ekit:JsonColumn.Scan 不支持 src 类型 %v", src) + } + + if err := json.Unmarshal(bs, &j.Val); err != nil { + return err + } + j.Valid = true + return nil +} diff --git a/sql/json_test.go b/sql/json_test.go new file mode 100644 index 00000000..6de12278 --- /dev/null +++ b/sql/json_test.go @@ -0,0 +1,141 @@ +// 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 sql + +import ( + "database/sql" + "errors" + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestJsonColumn_Value(t *testing.T) { + js := JsonColumn[User]{Valid: true, Val: User{Name: "Tom"}} + value, err := js.Value() + assert.Nil(t, err) + assert.Equal(t, []byte(`{"Name":"Tom"}`), value) + js = JsonColumn[User]{} + value, err = js.Value() + assert.Nil(t, err) + assert.Nil(t, value) +} + +func TestJsonColumn_Scan(t *testing.T) { + testCases := []struct { + name string + src any + wantErr error + wantVal User + }{ + { + name: "nil", + wantErr: errors.New("ekit:JsonColumn.Scan 不支持 src 类型 "), + }, + { + name: "string", + src: `{"Name":"Tom"}`, + wantVal: User{Name: "Tom"}, + }, + { + name: "string pointer", + src: func() string { + return `{"Name":"Tom"}` + }(), + wantVal: User{Name: "Tom"}, + }, + { + name: "bytes", + src: []byte(`{"Name":"Tom"}`), + wantVal: User{Name: "Tom"}, + }, + { + name: "bytes pointer", + src: func() *[]byte { + res := []byte(`{"Name":"Tom"}`) + return &res + }(), + wantVal: User{Name: "Tom"}, + }, + { + name: "sql.RawBytes", + src: sql.RawBytes(`{"Name":"Tom"}`), + wantVal: User{Name: "Tom"}, + }, + { + name: "sql.RawBytes pointer", + src: func() *sql.RawBytes { + res := sql.RawBytes(`{"Name":"Tom"}`) + return &res + }(), + wantVal: User{Name: "Tom"}, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + js := &JsonColumn[User]{} + err := js.Scan(tc.src) + assert.Equal(t, tc.wantErr, err) + if err != nil { + return + } + assert.Equal(t, tc.wantVal, js.Val) + assert.True(t, js.Valid) + }) + } +} + +func TestJsonColumn_ScanTypes(t *testing.T) { + jsSlice := JsonColumn[[]string]{} + err := jsSlice.Scan(`["a", "b", "c"]`) + assert.Nil(t, err) + assert.Equal(t, []string{"a", "b", "c"}, jsSlice.Val) + val, err := jsSlice.Value() + assert.Nil(t, err) + assert.Equal(t, []byte(`["a","b","c"]`), val) + + jsMap := JsonColumn[map[string]string]{} + err = jsMap.Scan(`{"a":"a value"}`) + assert.Nil(t, err) + val, err = jsMap.Value() + assert.Nil(t, err) + assert.Equal(t, []byte(`{"a":"a value"}`), val) +} + +type User struct { + Name string +} + +func ExampleJsonColumn_Value() { + js := JsonColumn[User]{Valid: true, Val: User{Name: "Tom"}} + value, err := js.Value() + if err != nil { + fmt.Println(err) + } + fmt.Print(string(value.([]byte))) + // Output: + // {"Name":"Tom"} +} + +func ExampleJsonColumn_Scan() { + js := JsonColumn[User]{} + err := js.Scan(`{"Name":"Tom"}`) + if err != nil { + fmt.Println(err) + } + fmt.Print(js.Val) + // Output: + // {Tom} +} From a45c4c36d18ff4eeebfb78cdb9feb6295398e1f9 Mon Sep 17 00:00:00 2001 From: Deng Ming Date: Thu, 11 Aug 2022 14:21:04 +0800 Subject: [PATCH 03/32] =?UTF-8?q?=E5=AE=9A=E4=B9=89=20List=20=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=8C=E5=B9=B6=E4=B8=94=E5=AE=9A=E4=B9=89=20ArrayL?= =?UTF-8?q?ist=20=E7=BB=93=E6=9E=84=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- list/array_list.go | 74 ++++++++++++++++++ list/array_list_test.go | 168 ++++++++++++++++++++++++++++++++++++++++ list/errors.go | 22 ++++++ list/types.go | 45 +++++++++++ 4 files changed, 309 insertions(+) create mode 100644 list/array_list.go create mode 100644 list/array_list_test.go create mode 100644 list/errors.go create mode 100644 list/types.go diff --git a/list/array_list.go b/list/array_list.go new file mode 100644 index 00000000..07eba69b --- /dev/null +++ b/list/array_list.go @@ -0,0 +1,74 @@ +// 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 list + +// ArrayList 基于切片的简单封装 +type ArrayList[T any] struct { + vals []T +} + +func NewArrayList[T any](cap int) *ArrayList[T] { + panic("implement me") +} + +// NewArrayListOf 直接使用 ts,而不会执行复制 +func NewArrayListOf[T any](ts []T) *ArrayList[T] { + panic("implement me") +} + +func (a *ArrayList[T]) Get(index int) (T, error) { + // TODO implement me + panic("implement me") +} + +func (a *ArrayList[T]) Append(t T) error { + // TODO implement me + panic("implement me") +} + +func (a *ArrayList[T]) Add(index int, t T) error { + // TODO implement me + panic("implement me") +} + +func (a *ArrayList[T]) Set(index int, t T) error { + // TODO implement me + panic("implement me") +} + +func (a *ArrayList[T]) Delete(index int) (T, error) { + // TODO implement me + panic("implement me") +} + +func (a *ArrayList[T]) Len() int { + // TODO implement me + panic("implement me") +} + +func (a *ArrayList[T]) Cap() int { + // TODO implement me + panic("implement me") +} + +func (a *ArrayList[T]) Range(fn func(index int, t T) error) error { + // TODO implement me + panic("implement me") +} + +func (a *ArrayList[T]) AsSlice() []T { + // TODO implement me + panic("implement me") +} diff --git a/list/array_list_test.go b/list/array_list_test.go new file mode 100644 index 00000000..0d7de9f2 --- /dev/null +++ b/list/array_list_test.go @@ -0,0 +1,168 @@ +// 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 list + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestArrayList_Add(t *testing.T) { + testCases := []struct { + name string + list *ArrayList[int] + index int + newVal int + wantSlice []int + wantErr error + }{ + // 仿照这个例子,继续添加测试 + // 你需要综合考虑下标的各种可能取值 + // 往两边增加,往中间加 + // 下标可能是负数,也可能超出你的长度 + { + name: "index 0", + list: NewArrayListOf[int]([]int{123}), + newVal: 100, + wantSlice: []int{100, 123}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.list.Add(tc.index, tc.newVal) + assert.Equal(t, tc.wantErr, err) + // 因为返回了 error,所以我们不用继续往下比较了 + if err != nil { + return + } + assert.Equal(t, tc.wantSlice, tc.list.vals) + }) + } +} + +func TestArrayList_Append(t *testing.T) { + // 这个比较简单,只需要增加元素,然后判断一下 Append 之后是否符合预期 +} + +func TestArrayList_Cap(t *testing.T) { + +} + +func TestArrayList_Delete(t *testing.T) { + testCases := []struct { + name string + list *ArrayList[int] + index int + wantSlice []int + wantVal int + wantErr error + }{ + // 仿照这个例子,继续添加测试 + // 你需要综合考虑下标的各种可能取值 + // 往两边增加,往中间加 + // 下标可能是负数,也可能超出你的长度 + { + name: "index 0", + list: NewArrayListOf[int]([]int{123, 100}), + index: 0, + wantSlice: []int{100}, + wantVal: 123, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + val, err := tc.list.Delete(tc.index) + assert.Equal(t, tc.wantErr, err) + // 因为返回了 error,所以我们不用继续往下比较了 + if err != nil { + return + } + assert.Equal(t, tc.wantSlice, tc.list.vals) + assert.Equal(t, tc.wantVal, val) + }) + } +} + +func TestArrayList_Get(t *testing.T) { + testCases := []struct { + name string + list *ArrayList[int] + index int + wantVal int + wantErr error + }{ + // 仿照这个例子,继续添加测试 + // 你需要综合考虑下标的各种可能取值 + // 往两边增加,往中间加 + // 下标可能是负数,也可能超出你的长度 + { + name: "index 0", + list: NewArrayListOf[int]([]int{123, 100}), + index: 0, + wantVal: 123, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + val, err := tc.list.Get(tc.index) + assert.Equal(t, tc.wantErr, err) + // 因为返回了 error,所以我们不用继续往下比较了 + if err != nil { + return + } + assert.Equal(t, tc.wantVal, val) + }) + } +} + +func TestArrayList_Range(t *testing.T) { + // 设计两个测试用例,用求和来作为场景 + // 一个测试用例是计算全部元素的和 + // 一个测试用例是计算元素的和,如果遇到了第一个负数,那么就中断返回 + // 测试最终断言求的和是否符合预期 +} + +func TestArrayList_Len(t *testing.T) { + +} + +func TestArrayList_Set(t *testing.T) { + +} + +func TestArrayList_AsSlice(t *testing.T) { + vals := []int{1, 2, 3} + a := NewArrayListOf[int](vals) + slice := a.AsSlice() + // 内容相同 + assert.Equal(t, slice, vals) + aAddr := fmt.Sprintf("%p", vals) + sliceAddr := fmt.Sprintf("%p", slice) + // 但是地址不同,也就是意味着 slice 必须是一个新创建的 + assert.NotEqual(t, aAddr, sliceAddr) +} + +// 为其它所有的公开方法都加上例子 +func ExampleArrayList_Add() { + list := NewArrayListOf[int]([]int{1, 2, 3}) + _ = list.Add(0, 9) + fmt.Println(list.AsSlice()) + // Output: + // [9 1 2 3] +} diff --git a/list/errors.go b/list/errors.go new file mode 100644 index 00000000..8cafe586 --- /dev/null +++ b/list/errors.go @@ -0,0 +1,22 @@ +// 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 list + +import "fmt" + +// newErrIndexOutOfRange 创建一个代表 +func newErrIndexOutOfRange(length int, index int) error { + return fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", length, index) +} diff --git a/list/types.go b/list/types.go new file mode 100644 index 00000000..9ab5ee5e --- /dev/null +++ b/list/types.go @@ -0,0 +1,45 @@ +// 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 list + +// List 接口 +// 该接口只定义清楚各个方法的行为和表现 +type List[T any] interface { + // Get 返回对应下标的元素, + // 在下标超出范围的情况下,返回错误 + Get(index int) (T, error) + // Append 在末尾追加元素 + Append(t T) error + // Add 在特定下标处增加一个新元素 + // 如果下标超出范围,应该返回错误 + Add(index int, t T) error + // Set 重置 index 位置的值 + // 如果下标超出范围,应该返回错误 + Set(index int, t T) error + // Delete 删除目标元素的位置,并且返回该位置的值 + // 如果 index 超出下标,应该返回错误 + Delete(index int) (T, error) + // Len 返回长度 + Len() int + // Cap 返回容量 + Cap() int + // Range 遍历 List 的所有元素 + Range(fn func(index int, t T) error) error + // AsSlice 将 List 转化为一个切片 + // 不允许返回nil,在没有元素的情况下, + // 必须返回一个长度和容量都为 0 的切片 + // AsSlice 每次调用都必须返回一个全新的切片 + AsSlice() []T +} From 87513dc6de01d7952bb45c883b0d3caad8b302eb Mon Sep 17 00:00:00 2001 From: Deng Ming Date: Thu, 11 Aug 2022 14:35:12 +0800 Subject: [PATCH 04/32] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E6=8E=89=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- list/array_list_test.go | 298 ++++++++++++++++++++-------------------- 1 file changed, 146 insertions(+), 152 deletions(-) diff --git a/list/array_list_test.go b/list/array_list_test.go index 0d7de9f2..97e56823 100644 --- a/list/array_list_test.go +++ b/list/array_list_test.go @@ -14,155 +14,149 @@ package list -import ( - "fmt" - "github.com/stretchr/testify/assert" - "testing" -) - -func TestArrayList_Add(t *testing.T) { - testCases := []struct { - name string - list *ArrayList[int] - index int - newVal int - wantSlice []int - wantErr error - }{ - // 仿照这个例子,继续添加测试 - // 你需要综合考虑下标的各种可能取值 - // 往两边增加,往中间加 - // 下标可能是负数,也可能超出你的长度 - { - name: "index 0", - list: NewArrayListOf[int]([]int{123}), - newVal: 100, - wantSlice: []int{100, 123}, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - err := tc.list.Add(tc.index, tc.newVal) - assert.Equal(t, tc.wantErr, err) - // 因为返回了 error,所以我们不用继续往下比较了 - if err != nil { - return - } - assert.Equal(t, tc.wantSlice, tc.list.vals) - }) - } -} - -func TestArrayList_Append(t *testing.T) { - // 这个比较简单,只需要增加元素,然后判断一下 Append 之后是否符合预期 -} - -func TestArrayList_Cap(t *testing.T) { - -} - -func TestArrayList_Delete(t *testing.T) { - testCases := []struct { - name string - list *ArrayList[int] - index int - wantSlice []int - wantVal int - wantErr error - }{ - // 仿照这个例子,继续添加测试 - // 你需要综合考虑下标的各种可能取值 - // 往两边增加,往中间加 - // 下标可能是负数,也可能超出你的长度 - { - name: "index 0", - list: NewArrayListOf[int]([]int{123, 100}), - index: 0, - wantSlice: []int{100}, - wantVal: 123, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - val, err := tc.list.Delete(tc.index) - assert.Equal(t, tc.wantErr, err) - // 因为返回了 error,所以我们不用继续往下比较了 - if err != nil { - return - } - assert.Equal(t, tc.wantSlice, tc.list.vals) - assert.Equal(t, tc.wantVal, val) - }) - } -} - -func TestArrayList_Get(t *testing.T) { - testCases := []struct { - name string - list *ArrayList[int] - index int - wantVal int - wantErr error - }{ - // 仿照这个例子,继续添加测试 - // 你需要综合考虑下标的各种可能取值 - // 往两边增加,往中间加 - // 下标可能是负数,也可能超出你的长度 - { - name: "index 0", - list: NewArrayListOf[int]([]int{123, 100}), - index: 0, - wantVal: 123, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - val, err := tc.list.Get(tc.index) - assert.Equal(t, tc.wantErr, err) - // 因为返回了 error,所以我们不用继续往下比较了 - if err != nil { - return - } - assert.Equal(t, tc.wantVal, val) - }) - } -} - -func TestArrayList_Range(t *testing.T) { - // 设计两个测试用例,用求和来作为场景 - // 一个测试用例是计算全部元素的和 - // 一个测试用例是计算元素的和,如果遇到了第一个负数,那么就中断返回 - // 测试最终断言求的和是否符合预期 -} - -func TestArrayList_Len(t *testing.T) { - -} - -func TestArrayList_Set(t *testing.T) { - -} - -func TestArrayList_AsSlice(t *testing.T) { - vals := []int{1, 2, 3} - a := NewArrayListOf[int](vals) - slice := a.AsSlice() - // 内容相同 - assert.Equal(t, slice, vals) - aAddr := fmt.Sprintf("%p", vals) - sliceAddr := fmt.Sprintf("%p", slice) - // 但是地址不同,也就是意味着 slice 必须是一个新创建的 - assert.NotEqual(t, aAddr, sliceAddr) -} - -// 为其它所有的公开方法都加上例子 -func ExampleArrayList_Add() { - list := NewArrayListOf[int]([]int{1, 2, 3}) - _ = list.Add(0, 9) - fmt.Println(list.AsSlice()) - // Output: - // [9 1 2 3] -} +// func TestArrayList_Add(t *testing.T) { +// testCases := []struct { +// name string +// list *ArrayList[int] +// index int +// newVal int +// wantSlice []int +// wantErr error +// }{ +// // 仿照这个例子,继续添加测试 +// // 你需要综合考虑下标的各种可能取值 +// // 往两边增加,往中间加 +// // 下标可能是负数,也可能超出你的长度 +// { +// name: "index 0", +// list: NewArrayListOf[int]([]int{123}), +// newVal: 100, +// wantSlice: []int{100, 123}, +// }, +// } +// +// for _, tc := range testCases { +// t.Run(tc.name, func(t *testing.T) { +// err := tc.list.Add(tc.index, tc.newVal) +// assert.Equal(t, tc.wantErr, err) +// // 因为返回了 error,所以我们不用继续往下比较了 +// if err != nil { +// return +// } +// assert.Equal(t, tc.wantSlice, tc.list.vals) +// }) +// } +// } +// +// func TestArrayList_Append(t *testing.T) { +// // 这个比较简单,只需要增加元素,然后判断一下 Append 之后是否符合预期 +// } +// +// func TestArrayList_Cap(t *testing.T) { +// +// } +// +// func TestArrayList_Delete(t *testing.T) { +// testCases := []struct { +// name string +// list *ArrayList[int] +// index int +// wantSlice []int +// wantVal int +// wantErr error +// }{ +// // 仿照这个例子,继续添加测试 +// // 你需要综合考虑下标的各种可能取值 +// // 往两边增加,往中间加 +// // 下标可能是负数,也可能超出你的长度 +// { +// name: "index 0", +// list: NewArrayListOf[int]([]int{123, 100}), +// index: 0, +// wantSlice: []int{100}, +// wantVal: 123, +// }, +// } +// +// for _, tc := range testCases { +// t.Run(tc.name, func(t *testing.T) { +// val, err := tc.list.Delete(tc.index) +// assert.Equal(t, tc.wantErr, err) +// // 因为返回了 error,所以我们不用继续往下比较了 +// if err != nil { +// return +// } +// assert.Equal(t, tc.wantSlice, tc.list.vals) +// assert.Equal(t, tc.wantVal, val) +// }) +// } +// } +// +// func TestArrayList_Get(t *testing.T) { +// testCases := []struct { +// name string +// list *ArrayList[int] +// index int +// wantVal int +// wantErr error +// }{ +// // 仿照这个例子,继续添加测试 +// // 你需要综合考虑下标的各种可能取值 +// // 往两边增加,往中间加 +// // 下标可能是负数,也可能超出你的长度 +// { +// name: "index 0", +// list: NewArrayListOf[int]([]int{123, 100}), +// index: 0, +// wantVal: 123, +// }, +// } +// +// for _, tc := range testCases { +// t.Run(tc.name, func(t *testing.T) { +// val, err := tc.list.Get(tc.index) +// assert.Equal(t, tc.wantErr, err) +// // 因为返回了 error,所以我们不用继续往下比较了 +// if err != nil { +// return +// } +// assert.Equal(t, tc.wantVal, val) +// }) +// } +// } +// +// func TestArrayList_Range(t *testing.T) { +// // 设计两个测试用例,用求和来作为场景 +// // 一个测试用例是计算全部元素的和 +// // 一个测试用例是计算元素的和,如果遇到了第一个负数,那么就中断返回 +// // 测试最终断言求的和是否符合预期 +// } +// +// func TestArrayList_Len(t *testing.T) { +// +// } +// +// func TestArrayList_Set(t *testing.T) { +// +// } +// +// func TestArrayList_AsSlice(t *testing.T) { +// vals := []int{1, 2, 3} +// a := NewArrayListOf[int](vals) +// slice := a.AsSlice() +// // 内容相同 +// assert.Equal(t, slice, vals) +// aAddr := fmt.Sprintf("%p", vals) +// sliceAddr := fmt.Sprintf("%p", slice) +// // 但是地址不同,也就是意味着 slice 必须是一个新创建的 +// assert.NotEqual(t, aAddr, sliceAddr) +// } +// +// // 为其它所有的公开方法都加上例子 +// func ExampleArrayList_Add() { +// list := NewArrayListOf[int]([]int{1, 2, 3}) +// _ = list.Add(0, 9) +// fmt.Println(list.AsSlice()) +// // Output: +// // [9 1 2 3] +// } From 153653fb24e2dfe6bb3820768be1e3e8e26615dd Mon Sep 17 00:00:00 2001 From: michaelcheungdk <84323139+michaelcheungdk@users.noreply.github.com> Date: Thu, 11 Aug 2022 23:00:30 +0800 Subject: [PATCH 05/32] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20ArrayList=20?= =?UTF-8?q?Cap()=20(#11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 实现 ArrayList Cap() Co-authored-by: zhangdq --- list/array_list.go | 3 +- list/array_list_test.go | 131 +++++++++++++++++++++++++++------------- 2 files changed, 91 insertions(+), 43 deletions(-) diff --git a/list/array_list.go b/list/array_list.go index 07eba69b..fda24889 100644 --- a/list/array_list.go +++ b/list/array_list.go @@ -59,8 +59,7 @@ func (a *ArrayList[T]) Len() int { } func (a *ArrayList[T]) Cap() int { - // TODO implement me - panic("implement me") + return cap(a.vals) } func (a *ArrayList[T]) Range(fn func(index int, t T) error) error { diff --git a/list/array_list_test.go b/list/array_list_test.go index 97e56823..6fe1be4c 100644 --- a/list/array_list_test.go +++ b/list/array_list_test.go @@ -14,47 +14,96 @@ package list -// func TestArrayList_Add(t *testing.T) { -// testCases := []struct { -// name string -// list *ArrayList[int] -// index int -// newVal int -// wantSlice []int -// wantErr error -// }{ -// // 仿照这个例子,继续添加测试 -// // 你需要综合考虑下标的各种可能取值 -// // 往两边增加,往中间加 -// // 下标可能是负数,也可能超出你的长度 -// { -// name: "index 0", -// list: NewArrayListOf[int]([]int{123}), -// newVal: 100, -// wantSlice: []int{100, 123}, -// }, -// } -// -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// err := tc.list.Add(tc.index, tc.newVal) -// assert.Equal(t, tc.wantErr, err) -// // 因为返回了 error,所以我们不用继续往下比较了 -// if err != nil { -// return -// } -// assert.Equal(t, tc.wantSlice, tc.list.vals) -// }) -// } -// } -// -// func TestArrayList_Append(t *testing.T) { -// // 这个比较简单,只需要增加元素,然后判断一下 Append 之后是否符合预期 -// } -// -// func TestArrayList_Cap(t *testing.T) { -// -// } +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +// func TestArrayList_Add(t *testing.T) { +// testCases := []struct { +// name string +// list *ArrayList[int] +// index int +// newVal int +// wantSlice []int +// wantErr error +// }{ +// // 仿照这个例子,继续添加测试 +// // 你需要综合考虑下标的各种可能取值 +// // 往两边增加,往中间加 +// // 下标可能是负数,也可能超出你的长度 +// { +// name: "index 0", +// list: NewArrayListOf[int]([]int{123}), +// newVal: 100, +// wantSlice: []int{100, 123}, +// }, +// } +// +// for _, tc := range testCases { +// t.Run(tc.name, func(t *testing.T) { +// err := tc.list.Add(tc.index, tc.newVal) +// assert.Equal(t, tc.wantErr, err) +// // 因为返回了 error,所以我们不用继续往下比较了 +// if err != nil { +// return +// } +// assert.Equal(t, tc.wantSlice, tc.list.vals) +// }) +// } +// } +// +// func TestArrayList_Append(t *testing.T) { +// // 这个比较简单,只需要增加元素,然后判断一下 Append 之后是否符合预期 +// } + +func TestArrayList_Cap(t *testing.T) { + testCases := []struct { + name string + expectCap int + list *ArrayList[int] + }{ + { + name: "与实际容量相等", + expectCap: 5, + list: &ArrayList[int]{ + vals: make([]int, 5), + }, + }, + { + name: "用户传入nil", + expectCap: 0, + list: &ArrayList[int]{ + vals: nil, + }, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + actual := testCase.list.Cap() + assert.Equal(t, testCase.expectCap, actual) + }) + } +} + +func BenchmarkArrayList_Cap(b *testing.B) { + list := &ArrayList[int]{ + vals: make([]int, 0), + } + + b.Run("Cap", func(b *testing.B) { + for i := 0; i < b.N; i++ { + list.Cap() + } + }) + + b.Run("Runtime cap", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = cap(list.vals) + } + }) +} + // // func TestArrayList_Delete(t *testing.T) { // testCases := []struct { From fe2b700d0e381edb96178d22f77e889279489f62 Mon Sep 17 00:00:00 2001 From: xxbiji <49327830+xxbiji@users.noreply.github.com> Date: Fri, 12 Aug 2022 13:22:39 +0800 Subject: [PATCH 06/32] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20ArrayList=20?= =?UTF-8?q?Range()=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .CHANGELOG.md | 3 +- list/array_list.go | 9 +- list/array_list_test.go | 204 +++++++++++++++++++++++++--------------- 3 files changed, 139 insertions(+), 77 deletions(-) diff --git a/.CHANGELOG.md b/.CHANGELOG.md index dc003c1e..171e000f 100644 --- a/.CHANGELOG.md +++ b/.CHANGELOG.md @@ -1,3 +1,4 @@ # 开发中 [ekit: add ToPtr function](https://github.com/gotomicro/ekit/pull/6) -[sql: 支持 JsonColumn](https://github.com/gotomicro/ekit/pull/7) \ No newline at end of file +[sql: 支持 JsonColumn](https://github.com/gotomicro/ekit/pull/7) +[ekit: implement Range()](https://github.com/gotomicro/ekit/pull/12) \ No newline at end of file diff --git a/list/array_list.go b/list/array_list.go index fda24889..7ca41c0a 100644 --- a/list/array_list.go +++ b/list/array_list.go @@ -63,8 +63,13 @@ func (a *ArrayList[T]) Cap() int { } func (a *ArrayList[T]) Range(fn func(index int, t T) error) error { - // TODO implement me - panic("implement me") + for key, value := range a.vals { + e := fn(key, value) + if e != nil { + return e + } + } + return nil } func (a *ArrayList[T]) AsSlice() []T { diff --git a/list/array_list_test.go b/list/array_list_test.go index 6fe1be4c..1bac789a 100644 --- a/list/array_list_test.go +++ b/list/array_list_test.go @@ -15,8 +15,11 @@ package list import ( - "github.com/stretchr/testify/assert" + "errors" + "fmt" "testing" + + "github.com/stretchr/testify/assert" ) // func TestArrayList_Add(t *testing.T) { @@ -104,82 +107,135 @@ func BenchmarkArrayList_Cap(b *testing.B) { }) } +// func TestArrayList_Append(t *testing.T) { +// // 这个比较简单,只需要增加元素,然后判断一下 Append 之后是否符合预期 +// } + +// func TestArrayList_Delete(t *testing.T) { +// testCases := []struct { +// name string +// list *ArrayList[int] +// index int +// wantSlice []int +// wantVal int +// wantErr error +// }{ +// // 仿照这个例子,继续添加测试 +// // 你需要综合考虑下标的各种可能取值 +// // 往两边增加,往中间加 +// // 下标可能是负数,也可能超出你的长度 +// { +// name: "index 0", +// list: NewArrayListOf[int]([]int{123, 100}), +// index: 0, +// wantSlice: []int{100}, +// wantVal: 123, +// }, +// } // -// func TestArrayList_Delete(t *testing.T) { -// testCases := []struct { -// name string -// list *ArrayList[int] -// index int -// wantSlice []int -// wantVal int -// wantErr error -// }{ -// // 仿照这个例子,继续添加测试 -// // 你需要综合考虑下标的各种可能取值 -// // 往两边增加,往中间加 -// // 下标可能是负数,也可能超出你的长度 -// { -// name: "index 0", -// list: NewArrayListOf[int]([]int{123, 100}), -// index: 0, -// wantSlice: []int{100}, -// wantVal: 123, -// }, -// } -// -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// val, err := tc.list.Delete(tc.index) -// assert.Equal(t, tc.wantErr, err) -// // 因为返回了 error,所以我们不用继续往下比较了 -// if err != nil { -// return -// } -// assert.Equal(t, tc.wantSlice, tc.list.vals) -// assert.Equal(t, tc.wantVal, val) -// }) -// } -// } -// -// func TestArrayList_Get(t *testing.T) { -// testCases := []struct { -// name string -// list *ArrayList[int] -// index int -// wantVal int -// wantErr error -// }{ -// // 仿照这个例子,继续添加测试 -// // 你需要综合考虑下标的各种可能取值 -// // 往两边增加,往中间加 -// // 下标可能是负数,也可能超出你的长度 -// { -// name: "index 0", -// list: NewArrayListOf[int]([]int{123, 100}), -// index: 0, -// wantVal: 123, -// }, -// } +// for _, tc := range testCases { +// t.Run(tc.name, func(t *testing.T) { +// val, err := tc.list.Delete(tc.index) +// assert.Equal(t, tc.wantErr, err) +// // 因为返回了 error,所以我们不用继续往下比较了 +// if err != nil { +// return +// } +// assert.Equal(t, tc.wantSlice, tc.list.vals) +// assert.Equal(t, tc.wantVal, val) +// }) +// } +// } // -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// val, err := tc.list.Get(tc.index) -// assert.Equal(t, tc.wantErr, err) -// // 因为返回了 error,所以我们不用继续往下比较了 -// if err != nil { -// return -// } -// assert.Equal(t, tc.wantVal, val) -// }) -// } -// } +// func TestArrayList_Get(t *testing.T) { +// testCases := []struct { +// name string +// list *ArrayList[int] +// index int +// wantVal int +// wantErr error +// }{ +// // 仿照这个例子,继续添加测试 +// // 你需要综合考虑下标的各种可能取值 +// // 往两边增加,往中间加 +// // 下标可能是负数,也可能超出你的长度 +// { +// name: "index 0", +// list: NewArrayListOf[int]([]int{123, 100}), +// index: 0, +// wantVal: 123, +// }, +// } // -// func TestArrayList_Range(t *testing.T) { -// // 设计两个测试用例,用求和来作为场景 -// // 一个测试用例是计算全部元素的和 -// // 一个测试用例是计算元素的和,如果遇到了第一个负数,那么就中断返回 -// // 测试最终断言求的和是否符合预期 -// } +// for _, tc := range testCases { +// t.Run(tc.name, func(t *testing.T) { +// val, err := tc.list.Get(tc.index) +// assert.Equal(t, tc.wantErr, err) +// // 因为返回了 error,所以我们不用继续往下比较了 +// if err != nil { +// return +// } +// assert.Equal(t, tc.wantVal, val) +// }) +// } +// } +func TestArrayList_Range(t *testing.T) { + // 设计两个测试用例,用求和来作为场景 + // 一个测试用例是计算全部元素的和 + // 一个测试用例是计算元素的和,如果遇到了第一个负数,那么就中断返回 + // 测试最终断言求的和是否符合预期 + testCases := []struct { + name string + list *ArrayList[int] + index int + wantVal int + wantErr error + }{ + { + name: "计算全部元素的和", + list: &ArrayList[int]{ + vals: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + }, + wantVal: 55, + wantErr: nil, + }, + { + name: "测试中断", + list: &ArrayList[int]{ + vals: []int{1, 2, 3, 4, -5, 6, 7, 8, -9, 10}, + }, + wantVal: 41, + wantErr: errors.New("index 4 is error"), + }, + { + name: "测试数组为nil", + list: &ArrayList[int]{ + vals: nil, + }, + wantVal: 0, + wantErr: nil, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := 0 + err := tc.list.Range(func(index int, num int) error { + if num < 0 { + return fmt.Errorf("index %d is error", index) + } + result += num + return nil + }) + + assert.Equal(t, tc.wantErr, err) + if err != nil { + return + } + assert.Equal(t, tc.wantVal, result) + }) + } +} + // // func TestArrayList_Len(t *testing.T) { // From 515c6f3f8dcaf78add8f7bb48f1cdb61e46340bb Mon Sep 17 00:00:00 2001 From: Lin Kai <1134842727@qq.com> Date: Sat, 13 Aug 2022 15:57:46 +0800 Subject: [PATCH 07/32] =?UTF-8?q?=E6=94=AF=E6=8C=81=20ArrayList=20Add()=20?= =?UTF-8?q?=E5=87=BD=E6=95=B0=20(#17)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 支持 ArrayList Add() 函数 * 补充ArrayList Add()注释说明 * 选用内存消耗更低的Add实现方法 --- list/array_list.go | 15 +++++-- list/array_list_test.go | 99 ++++++++++++++++++++++++++--------------- 2 files changed, 76 insertions(+), 38 deletions(-) diff --git a/list/array_list.go b/list/array_list.go index 7ca41c0a..58888d49 100644 --- a/list/array_list.go +++ b/list/array_list.go @@ -25,7 +25,9 @@ func NewArrayList[T any](cap int) *ArrayList[T] { // NewArrayListOf 直接使用 ts,而不会执行复制 func NewArrayListOf[T any](ts []T) *ArrayList[T] { - panic("implement me") + return &ArrayList[T]{ + vals: ts, + } } func (a *ArrayList[T]) Get(index int) (T, error) { @@ -38,9 +40,16 @@ func (a *ArrayList[T]) Append(t T) error { panic("implement me") } +// Add 在ArrayList下标为index的位置插入一个元素 +// 当index等于ArrayList长度等同于append func (a *ArrayList[T]) Add(index int, t T) error { - // TODO implement me - panic("implement me") + if index < 0 || index > len(a.vals) { + return newErrIndexOutOfRange(len(a.vals), index) + } + a.vals = append(a.vals, t) + copy(a.vals[index+1:], a.vals[index:]) + a.vals[index] = t + return nil } func (a *ArrayList[T]) Set(index int, t T) error { diff --git a/list/array_list_test.go b/list/array_list_test.go index 1bac789a..69157ae4 100644 --- a/list/array_list_test.go +++ b/list/array_list_test.go @@ -17,44 +17,73 @@ package list import ( "errors" "fmt" - "testing" - "github.com/stretchr/testify/assert" + "testing" ) -// func TestArrayList_Add(t *testing.T) { -// testCases := []struct { -// name string -// list *ArrayList[int] -// index int -// newVal int -// wantSlice []int -// wantErr error -// }{ -// // 仿照这个例子,继续添加测试 -// // 你需要综合考虑下标的各种可能取值 -// // 往两边增加,往中间加 -// // 下标可能是负数,也可能超出你的长度 -// { -// name: "index 0", -// list: NewArrayListOf[int]([]int{123}), -// newVal: 100, -// wantSlice: []int{100, 123}, -// }, -// } -// -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// err := tc.list.Add(tc.index, tc.newVal) -// assert.Equal(t, tc.wantErr, err) -// // 因为返回了 error,所以我们不用继续往下比较了 -// if err != nil { -// return -// } -// assert.Equal(t, tc.wantSlice, tc.list.vals) -// }) -// } -// } +func TestArrayList_Add(t *testing.T) { + testCases := []struct { + name string + list *ArrayList[int] + index int + newVal int + wantSlice []int + wantErr error + }{ + // 仿照这个例子,继续添加测试 + // 你需要综合考虑下标的各种可能取值 + // 往两边增加,往中间加 + // 下标可能是负数,也可能超出你的长度 + { + name: "add num to index left", + list: NewArrayListOf[int]([]int{1, 2, 3}), + newVal: 100, + index: 0, + wantSlice: []int{100, 1, 2, 3}, + }, + { + name: "add num to index right", + list: NewArrayListOf[int]([]int{1, 2, 3}), + newVal: 100, + index: 3, + wantSlice: []int{1, 2, 3, 100}, + }, + { + name: "add num to index mid", + list: NewArrayListOf[int]([]int{1, 2, 3}), + newVal: 100, + index: 1, + wantSlice: []int{1, 100, 2, 3}, + }, + { + name: "add num to index -1", + list: NewArrayListOf[int]([]int{1, 2, 3}), + newVal: 100, + index: -1, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 3, -1), + }, + { + name: "add num to index OutOfRange", + list: NewArrayListOf[int]([]int{1, 2, 3}), + newVal: 100, + index: 4, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 3, 4), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.list.Add(tc.index, tc.newVal) + assert.Equal(t, tc.wantErr, err) + // 因为返回了 error,所以我们不用继续往下比较了 + if err != nil { + return + } + assert.Equal(t, tc.wantSlice, tc.list.vals) + }) + } +} + // // func TestArrayList_Append(t *testing.T) { // // 这个比较简单,只需要增加元素,然后判断一下 Append 之后是否符合预期 From a3b17c19673d3556562476a34822bca9b3ef2964 Mon Sep 17 00:00:00 2001 From: Gevin Date: Sat, 13 Aug 2022 16:48:47 +0800 Subject: [PATCH 08/32] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20ArrayList=20AsSlice(?= =?UTF-8?q?)=E5=8A=9F=E8=83=BD=20(#19)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 实现AsSlice()功能 * change slice capacity identical to length * 移除冗余方法 --- list/array_list.go | 5 +++-- list/array_list_test.go | 24 +++++++++++++----------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/list/array_list.go b/list/array_list.go index 58888d49..1276789d 100644 --- a/list/array_list.go +++ b/list/array_list.go @@ -82,6 +82,7 @@ func (a *ArrayList[T]) Range(fn func(index int, t T) error) error { } func (a *ArrayList[T]) AsSlice() []T { - // TODO implement me - panic("implement me") + slice := make([]T, len(a.vals)) + copy(slice, a.vals) + return slice } diff --git a/list/array_list_test.go b/list/array_list_test.go index 69157ae4..2dc8d91c 100644 --- a/list/array_list_test.go +++ b/list/array_list_test.go @@ -274,17 +274,19 @@ func TestArrayList_Range(t *testing.T) { // // } // -// func TestArrayList_AsSlice(t *testing.T) { -// vals := []int{1, 2, 3} -// a := NewArrayListOf[int](vals) -// slice := a.AsSlice() -// // 内容相同 -// assert.Equal(t, slice, vals) -// aAddr := fmt.Sprintf("%p", vals) -// sliceAddr := fmt.Sprintf("%p", slice) -// // 但是地址不同,也就是意味着 slice 必须是一个新创建的 -// assert.NotEqual(t, aAddr, sliceAddr) -// } + +func TestArrayList_AsSlice(t *testing.T) { + vals := []int{1, 2, 3} + a := NewArrayListOf[int](vals) + slice := a.AsSlice() + // 内容相同 + assert.Equal(t, slice, vals) + aAddr := fmt.Sprintf("%p", vals) + sliceAddr := fmt.Sprintf("%p", slice) + // 但是地址不同,也就是意味着 slice 必须是一个新创建的 + assert.NotEqual(t, aAddr, sliceAddr) +} + // // // 为其它所有的公开方法都加上例子 // func ExampleArrayList_Add() { From 334f7fa6c17232ea8c95f6b48a24333ccb624db8 Mon Sep 17 00:00:00 2001 From: Ming Deng Date: Sat, 13 Aug 2022 23:26:25 +0800 Subject: [PATCH 09/32] =?UTF-8?q?list:=20LinkedList=20=E5=92=8C=20Concurre?= =?UTF-8?q?ntList=20=E7=9A=84=E5=AE=9A=E4=B9=89=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 4 +-- go.sum | 3 -- list/concurrent_list.go | 24 +++++++++++++ list/linked_list.go | 73 ++++++++++++++++++++++++++++++++++++++++ list/linked_list_test.go | 56 ++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 list/concurrent_list.go create mode 100644 list/linked_list.go create mode 100644 list/linked_list_test.go diff --git a/go.mod b/go.mod index 2b86b347..57dd227e 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,10 @@ module github.com/gotomicro/ekit go 1.18 +require github.com/stretchr/testify v1.7.1 + require ( github.com/davecgh/go-spew v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/objx v0.1.0 // indirect - github.com/stretchr/testify v1.7.1 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/go.sum b/go.sum index 307d5b4a..2dca7c9c 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,7 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/list/concurrent_list.go b/list/concurrent_list.go new file mode 100644 index 00000000..ae453502 --- /dev/null +++ b/list/concurrent_list.go @@ -0,0 +1,24 @@ +// 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 list + +import "sync" + +// ConcurrentList 用读写锁封装了对 List 的操作 +// 达到线程安全的目标 +type ConcurrentList[T any] struct { + List[T] + lock sync.RWMutex +} diff --git a/list/linked_list.go b/list/linked_list.go new file mode 100644 index 00000000..c811f4f2 --- /dev/null +++ b/list/linked_list.go @@ -0,0 +1,73 @@ +// 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 list + +// LinkedList 双向链表 +type LinkedList[T any] struct { + head *node[T] + tail *node[T] + // length 有多少个元素 + length int +} + +func (l *LinkedList[T]) Get(index int) (T, error) { + // TODO implement me + panic("implement me") +} + +func (l *LinkedList[T]) Append(t T) error { + // TODO implement me + panic("implement me") +} + +func (l *LinkedList[T]) Add(index int, t T) error { + // TODO implement me + panic("implement me") +} + +func (l *LinkedList[T]) Set(index int, t T) error { + // TODO implement me + panic("implement me") +} + +func (l *LinkedList[T]) Delete(index int) (T, error) { + // TODO implement me + panic("implement me") +} + +func (l *LinkedList[T]) Len() int { + // TODO implement me + panic("implement me") +} + +func (l *LinkedList[T]) Cap() int { + // TODO implement me + panic("implement me") +} + +func (l *LinkedList[T]) Range(fn func(index int, t T) error) error { + // TODO implement me + panic("implement me") +} + +func (l *LinkedList[T]) AsSlice() []T { + // TODO implement me + panic("implement me") +} + +type node[T any] struct { + next *node[T] + prev *node[T] +} diff --git a/list/linked_list_test.go b/list/linked_list_test.go new file mode 100644 index 00000000..4bcafd42 --- /dev/null +++ b/list/linked_list_test.go @@ -0,0 +1,56 @@ +// 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 list + +import ( + "fmt" + "testing" +) + +func TestLinkedList_Add(t *testing.T) { + fmt.Println("仿照 ArrayList 的测试写代码") +} + +func TestLinkedList_Append(t *testing.T) { + fmt.Println("仿照 ArrayList 的测试写代码") +} + +func TestLinkedList_AsSlice(t *testing.T) { + fmt.Println("仿照 ArrayList 的测试写代码") +} + +func TestLinkedList_Cap(t *testing.T) { + fmt.Println("仿照 ArrayList 的测试写代码") +} + +func TestLinkedList_Delete(t *testing.T) { + fmt.Println("仿照 ArrayList 的测试写代码") +} + +func TestLinkedList_Get(t *testing.T) { + fmt.Println("仿照 ArrayList 的测试写代码") +} + +func TestLinkedList_Len(t *testing.T) { + fmt.Println("仿照 ArrayList 的测试写代码") +} + +func TestLinkedList_Range(t *testing.T) { + fmt.Println("仿照 ArrayList 的测试写代码") +} + +func TestLinkedList_Set(t *testing.T) { + fmt.Println("仿照 ArrayList 的测试写代码") +} From f25bd163f63d828ab5160699570f6cac810d41b1 Mon Sep 17 00:00:00 2001 From: tangyumeng Date: Sun, 14 Aug 2022 16:30:40 +0800 Subject: [PATCH 10/32] =?UTF-8?q?=E5=AE=9E=E7=8E=B0ArrayList=20Get()=20(#1?= =?UTF-8?q?8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 实现ArrayList Get() --- .CHANGELOG.md | 4 +- list/array_list.go | 15 ++++-- list/array_list_test.go | 105 +++++++++++++++++++++++++++------------- 3 files changed, 85 insertions(+), 39 deletions(-) diff --git a/.CHANGELOG.md b/.CHANGELOG.md index 171e000f..40d82555 100644 --- a/.CHANGELOG.md +++ b/.CHANGELOG.md @@ -1,4 +1,6 @@ # 开发中 [ekit: add ToPtr function](https://github.com/gotomicro/ekit/pull/6) [sql: 支持 JsonColumn](https://github.com/gotomicro/ekit/pull/7) -[ekit: implement Range()](https://github.com/gotomicro/ekit/pull/12) \ No newline at end of file +[ekit: implement Range()](https://github.com/gotomicro/ekit/pull/12) +[ekit: implement Get()](https://github.com/gotomicro/ekit/pull/18) +[ekit: implement Len()](https://github.com/gotomicro/ekit/pull/18) \ No newline at end of file diff --git a/list/array_list.go b/list/array_list.go index 1276789d..7c3c1ed1 100644 --- a/list/array_list.go +++ b/list/array_list.go @@ -30,9 +30,12 @@ func NewArrayListOf[T any](ts []T) *ArrayList[T] { } } -func (a *ArrayList[T]) Get(index int) (T, error) { - // TODO implement me - panic("implement me") +func (a *ArrayList[T]) Get(index int) (t T, e error) { + l := a.Len() + if index < 0 || index >= l { + return t, newErrIndexOutOfRange(l, index) + } + return a.vals[index], e } func (a *ArrayList[T]) Append(t T) error { @@ -63,8 +66,10 @@ func (a *ArrayList[T]) Delete(index int) (T, error) { } func (a *ArrayList[T]) Len() int { - // TODO implement me - panic("implement me") + if a == nil { + return 0 + } + return len(a.vals) } func (a *ArrayList[T]) Cap() int { diff --git a/list/array_list_test.go b/list/array_list_test.go index 2dc8d91c..3188723d 100644 --- a/list/array_list_test.go +++ b/list/array_list_test.go @@ -175,39 +175,78 @@ func BenchmarkArrayList_Cap(b *testing.B) { // }) // } // } -// -// func TestArrayList_Get(t *testing.T) { -// testCases := []struct { -// name string -// list *ArrayList[int] -// index int -// wantVal int -// wantErr error -// }{ -// // 仿照这个例子,继续添加测试 -// // 你需要综合考虑下标的各种可能取值 -// // 往两边增加,往中间加 -// // 下标可能是负数,也可能超出你的长度 -// { -// name: "index 0", -// list: NewArrayListOf[int]([]int{123, 100}), -// index: 0, -// wantVal: 123, -// }, -// } -// -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// val, err := tc.list.Get(tc.index) -// assert.Equal(t, tc.wantErr, err) -// // 因为返回了 error,所以我们不用继续往下比较了 -// if err != nil { -// return -// } -// assert.Equal(t, tc.wantVal, val) -// }) -// } -// } + +func TestArrayList_Len(t *testing.T) { + testCases := []struct { + name string + expectLen int + list *ArrayList[int] + }{ + { + name: "与实际元素数相等", + expectLen: 5, + list: &ArrayList[int]{ + vals: make([]int, 5), + }, + }, + { + name: "用户传入nil", + expectLen: 0, + list: &ArrayList[int]{ + vals: nil, + }, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + actual := testCase.list.Cap() + assert.Equal(t, testCase.expectLen, actual) + }) + } +} + +func TestArrayList_Get(t *testing.T) { + testCases := []struct { + name string + list *ArrayList[int] + index int + wantVal int + wantErr error + }{ + { + name: "index 0", + list: NewArrayListOf[int]([]int{123, 100}), + index: 0, + wantVal: 123, + }, + { + name: "index 2", + list: NewArrayListOf[int]([]int{123, 100}), + index: 2, + wantVal: 0, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 2, 2), + }, + { + name: "index -1", + list: NewArrayListOf[int]([]int{123, 100}), + index: -1, + wantVal: 0, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 2, -1), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + val, err := tc.list.Get(tc.index) + assert.Equal(t, tc.wantErr, err) + // 因为返回了 error,所以我们不用继续往下比较了 + if err != nil { + return + } + assert.Equal(t, tc.wantVal, val) + }) + } +} func TestArrayList_Range(t *testing.T) { // 设计两个测试用例,用求和来作为场景 // 一个测试用例是计算全部元素的和 From 708ce46a95651d81cfa00ce2d8303a1942eabdde Mon Sep 17 00:00:00 2001 From: hhh111119 Date: Mon, 15 Aug 2022 23:32:38 +0800 Subject: [PATCH 11/32] =?UTF-8?q?LinkedList=20=E5=AE=9E=E7=8E=B0=20Add=20(?= =?UTF-8?q?#26)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * LinkedList - 实现了 Add 方法 - 实现了 NewLinkedListOf 函数 * 更新 changelog * 改动 - 合并 changelog - 添加 链表 add 函数 benchmark - 移除不需要代码 Co-authored-by: jinchi xie --- .CHANGELOG.md | 3 +- list/linked_list.go | 88 +++++++++++++++++++++++++++++- list/linked_list_test.go | 114 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 201 insertions(+), 4 deletions(-) diff --git a/.CHANGELOG.md b/.CHANGELOG.md index 40d82555..e432d4f8 100644 --- a/.CHANGELOG.md +++ b/.CHANGELOG.md @@ -3,4 +3,5 @@ [sql: 支持 JsonColumn](https://github.com/gotomicro/ekit/pull/7) [ekit: implement Range()](https://github.com/gotomicro/ekit/pull/12) [ekit: implement Get()](https://github.com/gotomicro/ekit/pull/18) -[ekit: implement Len()](https://github.com/gotomicro/ekit/pull/18) \ No newline at end of file +[ekit: implement Len()](https://github.com/gotomicro/ekit/pull/18) +[ekit: 实现了 LinkedList Add() NewLinkedListOf](https://github.com/gotomicro/ekit/gpull/26) \ No newline at end of file diff --git a/list/linked_list.go b/list/linked_list.go index c811f4f2..0d94f014 100644 --- a/list/linked_list.go +++ b/list/linked_list.go @@ -32,9 +32,56 @@ func (l *LinkedList[T]) Append(t T) error { panic("implement me") } +// Add 在 LinkedList 下标为 index 的位置插入一个元素 +// 当 index 等于 LinkedList 长度等同于 Append func (l *LinkedList[T]) Add(index int, t T) error { - // TODO implement me - panic("implement me") + if index < 0 || index > l.length { + return newErrIndexOutOfRange(l.length, index) + } + defer func() { + l.length += 1 + }() + + newNode := &node[T]{ + val: t, + } + + if l.length == 0 { + l.head = newNode + l.tail = newNode + return nil + } + if index == 0 { + newNode.insertAfter(l.head) + l.head = newNode + return nil + } + if index == l.length { + l.tail.insertAfter(newNode) + l.tail = newNode + return nil + } + + cur := l.head + headToLeft := true + if l.fromTailToHead(index) { + cur = l.tail + headToLeft = false + index = l.length - index - 1 + } + curIndex := 0 + for curIndex < index { + curIndex += 1 + cur = cur.move(headToLeft) + } + prev := cur.prev + prev.insertAfter(newNode) + newNode.insertAfter(cur) + return nil +} + +func (l *LinkedList[T]) fromTailToHead(index int) bool { + return index > (l.length / 2) } func (l *LinkedList[T]) Set(index int, t T) error { @@ -70,4 +117,41 @@ func (l *LinkedList[T]) AsSlice() []T { type node[T any] struct { next *node[T] prev *node[T] + val T +} + +func (n *node[T]) insertAfter(newNode *node[T]) { + n.next = newNode + newNode.prev = n +} + +func (n *node[T]) move(toNext bool) *node[T] { + if toNext { + return n.next + } + return n.prev +} + +// NewLinkedListOf 将切片转换为链表, 数组的值是浅拷贝. +func NewLinkedListOf[T any](ts []T) *LinkedList[T] { + var head *node[T] = nil + var tail *node[T] = nil + + for _, ele := range ts { + newNode := &node[T]{ + val: ele, + } + if head == nil { + head = newNode + } else { + tail.insertAfter(newNode) + } + tail = newNode + } + + return &LinkedList[T]{ + head: head, + tail: tail, + length: len(ts), + } } diff --git a/list/linked_list_test.go b/list/linked_list_test.go index 4bcafd42..1c7864fc 100644 --- a/list/linked_list_test.go +++ b/list/linked_list_test.go @@ -16,11 +16,88 @@ package list import ( "fmt" + "github.com/stretchr/testify/assert" + "math/rand" "testing" ) func TestLinkedList_Add(t *testing.T) { - fmt.Println("仿照 ArrayList 的测试写代码") + + testCases := []struct { + name string + list *LinkedList[int] + index int + newVal int + wantLinkedList *LinkedList[int] + wantErr error + }{ + // 仿照这个例子,继续添加测试 + // 你需要综合考虑下标的各种可能取值 + // 往两边增加,往中间加 + // 下标可能是负数,也可能超出你的长度 + { + name: "add num to index left", + list: NewLinkedListOf[int]([]int{1, 2, 3}), + newVal: 100, + index: 0, + wantLinkedList: NewLinkedListOf[int]([]int{100, 1, 2, 3}), + }, + { + name: "add num to index left1", + list: NewLinkedListOf([]int{1, 2, 3, 44, 55, 66, 77}), + newVal: 100, + index: 3, + wantLinkedList: NewLinkedListOf([]int{1, 2, 3, 100, 44, 55, 66, 77}), + }, + { + name: "add num to index right", + list: NewLinkedListOf([]int{1, 2, 3}), + newVal: 100, + index: 3, + wantLinkedList: NewLinkedListOf([]int{1, 2, 3, 100}), + }, + { + name: "add num to index right1", + list: NewLinkedListOf([]int{1, 2, 3, 44, 55, 66, 77}), + newVal: 100, + index: 5, + wantLinkedList: NewLinkedListOf([]int{1, 2, 3, 44, 55, 100, 66, 77}), + }, + { + name: "add num to index mid", + list: NewLinkedListOf[int]([]int{1, 2, 3}), + newVal: 100, + index: 1, + wantLinkedList: NewLinkedListOf([]int{1, 100, 2, 3}), + }, + { + name: "add num to index -1", + list: NewLinkedListOf[int]([]int{1, 2, 3}), + newVal: 100, + index: -1, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 3, -1), + }, + { + name: "add num to index OutOfRange", + list: NewLinkedListOf[int]([]int{1, 2, 3}), + newVal: 100, + index: 4, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 3, 4), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.list.Add(tc.index, tc.newVal) + assert.Equal(t, tc.wantErr, err) + // 因为返回了 error,所以我们不用继续往下比较了 + if err != nil { + return + } + assert.True(t, linkedListEqual(tc.list, tc.wantLinkedList)) + + }) + } } func TestLinkedList_Append(t *testing.T) { @@ -54,3 +131,38 @@ func TestLinkedList_Range(t *testing.T) { func TestLinkedList_Set(t *testing.T) { fmt.Println("仿照 ArrayList 的测试写代码") } + +func linkedListEqual[T comparable](l1 *LinkedList[T], l2 *LinkedList[T]) bool { + if l1.length != l2.length { + fmt.Println(l1.length, l2.length) + return false + } + + if l1.length == 0 { + return true + } + + l1Pos := l1.head + l2Pos := l2.head + for l1Pos != nil && l2Pos != nil { + if l1Pos.val != l2Pos.val { + fmt.Println(l1Pos.val, l2Pos.val) + return false + } + l1Pos = l1Pos.next + l2Pos = l2Pos.next + } + return l1Pos == l2Pos +} + +func BenchmarkLinkedList_Add(b *testing.B) { + l := NewLinkedListOf[int]([]int{1, 2, 3}) + testCase := make([]int, 0, b.N) + for i := 1; i <= b.N; i++ { + testCase = append(testCase, rand.Intn(i)) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.Add(testCase[i], testCase[i]) + } +} From c827a2ff0516e41d0141722f22df89691b35771b Mon Sep 17 00:00:00 2001 From: Mikasa <1342627550@qq.com> Date: Tue, 16 Aug 2022 13:43:08 +0800 Subject: [PATCH 12/32] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20ArrayList=20?= =?UTF-8?q?Delete()=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 实现 ArrayList Delete() * fix: adjust zero order Co-authored-by: mikasa --- list/array_list.go | 17 ++++++- list/array_list_test.go | 110 +++++++++++++++++++++++++++------------- 2 files changed, 89 insertions(+), 38 deletions(-) diff --git a/list/array_list.go b/list/array_list.go index 7c3c1ed1..3c8632cb 100644 --- a/list/array_list.go +++ b/list/array_list.go @@ -61,8 +61,21 @@ func (a *ArrayList[T]) Set(index int, t T) error { } func (a *ArrayList[T]) Delete(index int) (T, error) { - // TODO implement me - panic("implement me") + length := len(a.vals) + if index < 0 || index >= length { + var zero T + return zero, newErrIndexOutOfRange(length, index) + } + j := 0 + res := a.vals[index] + for i, v := range a.vals { + if i != index { + a.vals[j] = v + j++ + } + } + a.vals = a.vals[:j] + return res, nil } func (a *ArrayList[T]) Len() int { diff --git a/list/array_list_test.go b/list/array_list_test.go index 3188723d..7c705ec5 100644 --- a/list/array_list_test.go +++ b/list/array_list_test.go @@ -17,8 +17,9 @@ package list import ( "errors" "fmt" - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestArrayList_Add(t *testing.T) { @@ -140,41 +141,78 @@ func BenchmarkArrayList_Cap(b *testing.B) { // // 这个比较简单,只需要增加元素,然后判断一下 Append 之后是否符合预期 // } -// func TestArrayList_Delete(t *testing.T) { -// testCases := []struct { -// name string -// list *ArrayList[int] -// index int -// wantSlice []int -// wantVal int -// wantErr error -// }{ -// // 仿照这个例子,继续添加测试 -// // 你需要综合考虑下标的各种可能取值 -// // 往两边增加,往中间加 -// // 下标可能是负数,也可能超出你的长度 -// { -// name: "index 0", -// list: NewArrayListOf[int]([]int{123, 100}), -// index: 0, -// wantSlice: []int{100}, -// wantVal: 123, -// }, -// } -// -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// val, err := tc.list.Delete(tc.index) -// assert.Equal(t, tc.wantErr, err) -// // 因为返回了 error,所以我们不用继续往下比较了 -// if err != nil { -// return -// } -// assert.Equal(t, tc.wantSlice, tc.list.vals) -// assert.Equal(t, tc.wantVal, val) -// }) -// } -// } +func TestArrayList_Delete(t *testing.T) { + testCases := []struct { + name string + list *ArrayList[int] + index int + wantSlice []int + wantVal int + wantErr error + }{ + // 仿照这个例子,继续添加测试 + // 你需要综合考虑下标的各种可能取值 + // 往两边增加,往中间加 + // 下标可能是负数,也可能超出你的长度 + + { + name: "index 0", + list: &ArrayList[int]{ + vals: []int{123, 100}, + }, + index: 0, + wantSlice: []int{100}, + wantVal: 123, + }, + { + name: "index middle", + list: &ArrayList[int]{ + vals: []int{123, 124, 125}, + }, + index: 1, + wantSlice: []int{123, 125}, + wantVal: 124, + }, + { + name: "index out of range", + list: &ArrayList[int]{ + vals: []int{123, 100}, + }, + index: 12, + wantErr: newErrIndexOutOfRange(2, 12), + }, + { + name: "index less than 0", + list: &ArrayList[int]{ + vals: []int{123, 100}, + }, + index: -1, + wantErr: newErrIndexOutOfRange(2, -1), + }, + { + name: "index last", + list: &ArrayList[int]{ + vals: []int{123, 100, 101, 102, 102, 102}, + }, + index: 5, + wantSlice: []int{123, 100, 101, 102, 102}, + wantVal: 102, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + val, err := tc.list.Delete(tc.index) + assert.Equal(t, tc.wantErr, err) + // 因为返回了 error,所以我们不用继续往下比较了 + if err != nil { + return + } + assert.Equal(t, tc.wantSlice, tc.list.vals) + assert.Equal(t, tc.wantVal, val) + }) + } +} func TestArrayList_Len(t *testing.T) { testCases := []struct { From 548d53acdb644fac87a5341a8112efdfd4b167a6 Mon Sep 17 00:00:00 2001 From: Snowball Date: Tue, 16 Aug 2022 21:35:09 +0800 Subject: [PATCH 13/32] =?UTF-8?q?=E5=AE=9E=E7=8E=B0ArrayList.Append?= =?UTF-8?q?=E6=96=B9=E6=B3=95(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 实现ArrayList.Append方法 * 实现ArrayList.Append方法 Co-authored-by: miaohao --- list/array_list.go | 4 ++-- list/array_list_test.go | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/list/array_list.go b/list/array_list.go index 3c8632cb..01203f3f 100644 --- a/list/array_list.go +++ b/list/array_list.go @@ -39,8 +39,8 @@ func (a *ArrayList[T]) Get(index int) (t T, e error) { } func (a *ArrayList[T]) Append(t T) error { - // TODO implement me - panic("implement me") + a.vals = append(a.vals, t) + return nil } // Add 在ArrayList下标为index的位置插入一个元素 diff --git a/list/array_list_test.go b/list/array_list_test.go index 7c705ec5..3bdbda28 100644 --- a/list/array_list_test.go +++ b/list/array_list_test.go @@ -137,9 +137,38 @@ func BenchmarkArrayList_Cap(b *testing.B) { }) } -// func TestArrayList_Append(t *testing.T) { -// // 这个比较简单,只需要增加元素,然后判断一下 Append 之后是否符合预期 -// } +func TestArrayList_Append(t *testing.T) { + testCases := []struct { + name string + list *ArrayList[int] + newVal int + wantSlice []int + }{ + { + name: "append 234", + list: NewArrayListOf[int]([]int{123}), + newVal: 234, + wantSlice: []int{123, 234}, + }, + { + name: "nil append 123", + list: NewArrayListOf[int](nil), + newVal: 123, + wantSlice: []int{123}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.list.Append(tc.newVal) + if err != nil { + return + } + + assert.Equal(t, tc.wantSlice, tc.list.vals) + }) + } +} func TestArrayList_Delete(t *testing.T) { testCases := []struct { From 0d99ac665915b41beb5964543953047b1e81f6ea Mon Sep 17 00:00:00 2001 From: Gevin Date: Wed, 17 Aug 2022 12:29:16 +0800 Subject: [PATCH 14/32] =?UTF-8?q?Linked=20list=20=E5=AE=9E=E7=8E=B0asSlice?= =?UTF-8?q?()=20(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * LinkedList asSlice功能 --- list/linked_list.go | 9 ++++++-- list/linked_list_test.go | 46 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/list/linked_list.go b/list/linked_list.go index 0d94f014..6d525658 100644 --- a/list/linked_list.go +++ b/list/linked_list.go @@ -110,8 +110,13 @@ func (l *LinkedList[T]) Range(fn func(index int, t T) error) error { } func (l *LinkedList[T]) AsSlice() []T { - // TODO implement me - panic("implement me") + slice := make([]T, l.length) + head := l.head + for i := 0; i < l.length; i++ { + slice[i] = head.val + head = head.next + } + return slice } type node[T any] struct { diff --git a/list/linked_list_test.go b/list/linked_list_test.go index 1c7864fc..9ed90edf 100644 --- a/list/linked_list_test.go +++ b/list/linked_list_test.go @@ -17,6 +17,7 @@ package list import ( "fmt" "github.com/stretchr/testify/assert" + "math/rand" "testing" ) @@ -104,8 +105,51 @@ func TestLinkedList_Append(t *testing.T) { fmt.Println("仿照 ArrayList 的测试写代码") } +func TestNewLinkedListOf(t *testing.T) { + testCases := []struct { + name string + slice []int + wantedSlice []int + }{ + { + name: "nil", + slice: nil, + wantedSlice: []int{}, + }, + { + name: "vacant", + slice: []int{}, + wantedSlice: []int{}, + }, + { + name: "single", + slice: []int{1}, + wantedSlice: []int{1}, + }, + { + name: "normal", + slice: []int{1, 2, 3}, + wantedSlice: []int{1, 2, 3}, + }, + } + + for _, tc := range testCases { + list := NewLinkedListOf(tc.slice) + // 在这里断言你的元素,可以利用 Get 方法,也可以直接用 AsSlice 来断言 + assert.Equal(t, tc.wantedSlice, list.AsSlice()) + } +} + func TestLinkedList_AsSlice(t *testing.T) { - fmt.Println("仿照 ArrayList 的测试写代码") + vals := []int{1, 2, 3} + a := NewLinkedListOf[int](vals) + slice := a.AsSlice() + // 内容相同 + assert.Equal(t, slice, vals) + aAddr := fmt.Sprintf("%p", vals) + sliceAddr := fmt.Sprintf("%p", slice) + // 但是地址不同,也就是意味着 slice 必须是一个新创建的 + assert.NotEqual(t, aAddr, sliceAddr) } func TestLinkedList_Cap(t *testing.T) { From 1cc0e698dccd4a56df1778a67c6e56954a74534c Mon Sep 17 00:00:00 2001 From: mousseqin <23616001+mousseqin@users.noreply.github.com> Date: Thu, 18 Aug 2022 00:52:54 +0800 Subject: [PATCH 15/32] =?UTF-8?q?=E6=96=B0=E5=A2=9ENewArrayList=20Set?= =?UTF-8?q?=E6=96=B9=E6=B3=95=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- list/array_list.go | 13 ++++++++--- list/array_list_test.go | 48 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/list/array_list.go b/list/array_list.go index 01203f3f..86a1eeb3 100644 --- a/list/array_list.go +++ b/list/array_list.go @@ -19,8 +19,9 @@ type ArrayList[T any] struct { vals []T } +// NewArrayList 初始化一个len为0,cap为cap的ArrayList func NewArrayList[T any](cap int) *ArrayList[T] { - panic("implement me") + return &ArrayList[T]{vals: make([]T, 0, cap)} } // NewArrayListOf 直接使用 ts,而不会执行复制 @@ -38,6 +39,7 @@ func (a *ArrayList[T]) Get(index int) (t T, e error) { return a.vals[index], e } +// Append 往ArrayList里追加数据 func (a *ArrayList[T]) Append(t T) error { a.vals = append(a.vals, t) return nil @@ -55,9 +57,14 @@ func (a *ArrayList[T]) Add(index int, t T) error { return nil } +// Set 设置ArrayList里index位置的值为t func (a *ArrayList[T]) Set(index int, t T) error { - // TODO implement me - panic("implement me") + length := len(a.vals) + if index >= length || index < 0 { + return newErrIndexOutOfRange(length, index) + } + a.vals[index] = t + return nil } func (a *ArrayList[T]) Delete(index int) (T, error) { diff --git a/list/array_list_test.go b/list/array_list_test.go index 3bdbda28..0409c1ba 100644 --- a/list/array_list_test.go +++ b/list/array_list_test.go @@ -402,3 +402,51 @@ func TestArrayList_AsSlice(t *testing.T) { // // Output: // // [9 1 2 3] // } + +func TestArrayList_Set(t *testing.T) { + testCases := []struct { + name string + list *ArrayList[int] + index int + newVal int + wantSlice []int + wantErr error + }{ + { + name: "set 5 by index 1", + list: NewArrayListOf[int]([]int{0, 1, 2, 3, 4}), + index: 1, + newVal: 5, + wantSlice: []int{0, 5, 2, 3, 4}, + wantErr: nil, + }, + { + name: "index -1", + list: NewArrayListOf[int]([]int{0, 1, 2, 3, 4}), + index: -1, + newVal: 5, + wantSlice: []int{}, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 5, -1), + }, + { + name: "index 100", + list: NewArrayListOf[int]([]int{0, 1, 2, 3, 4}), + index: 100, + newVal: 5, + wantSlice: []int{}, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 5, 100), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.list.Set(tc.index, tc.newVal) + if err != nil { + assert.Equal(t, tc.wantErr, err) + return + } + assert.Equal(t, tc.wantSlice, tc.list.vals) + }) + } + +} From 08ad01a49cec9c74d408b4182ae43752448d1455 Mon Sep 17 00:00:00 2001 From: hhh111119 Date: Thu, 18 Aug 2022 00:56:15 +0800 Subject: [PATCH 16/32] =?UTF-8?q?LinkedList=20=E5=AE=9E=E7=8E=B0=20Get=20(?= =?UTF-8?q?#31)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * LinkedList 实现 Get - 实现 Get, 测试, benchmark - 简化 Add 函数 --- .CHANGELOG.md | 3 +- list/linked_list.go | 53 ++++++++++++++++----------- list/linked_list_test.go | 79 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 110 insertions(+), 25 deletions(-) diff --git a/.CHANGELOG.md b/.CHANGELOG.md index e432d4f8..aacd4db4 100644 --- a/.CHANGELOG.md +++ b/.CHANGELOG.md @@ -4,4 +4,5 @@ [ekit: implement Range()](https://github.com/gotomicro/ekit/pull/12) [ekit: implement Get()](https://github.com/gotomicro/ekit/pull/18) [ekit: implement Len()](https://github.com/gotomicro/ekit/pull/18) -[ekit: 实现了 LinkedList Add() NewLinkedListOf](https://github.com/gotomicro/ekit/gpull/26) \ No newline at end of file +[ekit: 实现了 LinkedList Add() NewLinkedListOf](https://github.com/gotomicro/ekit/gpull/26) +[ekit: 实现了 LinkedList Get](https://github.com/gotomicro/ekit/pull/31) \ No newline at end of file diff --git a/list/linked_list.go b/list/linked_list.go index 6d525658..16e882a3 100644 --- a/list/linked_list.go +++ b/list/linked_list.go @@ -22,9 +22,36 @@ type LinkedList[T any] struct { length int } -func (l *LinkedList[T]) Get(index int) (T, error) { - // TODO implement me - panic("implement me") +func (l *LinkedList[T]) getNode(index int) *node[T] { + if l.length == 0 { + return nil + } + cur := l.head + curIndex := 0 + + if l.fromTailToHead(index) { + cur = l.tail + index = l.length - index - 1 + for curIndex < index { + curIndex += 1 + cur = cur.prev + } + } else { + for curIndex < index { + curIndex += 1 + cur = cur.next + } + } + return cur +} + +func (l *LinkedList[T]) Get(index int) (t T, err error) { + if index < 0 || index >= l.length { + err = newErrIndexOutOfRange(l.length, index) + return + } + node := l.getNode(index) + return node.val, nil } func (l *LinkedList[T]) Append(t T) error { @@ -62,18 +89,7 @@ func (l *LinkedList[T]) Add(index int, t T) error { return nil } - cur := l.head - headToLeft := true - if l.fromTailToHead(index) { - cur = l.tail - headToLeft = false - index = l.length - index - 1 - } - curIndex := 0 - for curIndex < index { - curIndex += 1 - cur = cur.move(headToLeft) - } + cur := l.getNode(index) prev := cur.prev prev.insertAfter(newNode) newNode.insertAfter(cur) @@ -130,13 +146,6 @@ func (n *node[T]) insertAfter(newNode *node[T]) { newNode.prev = n } -func (n *node[T]) move(toNext bool) *node[T] { - if toNext { - return n.next - } - return n.prev -} - // NewLinkedListOf 将切片转换为链表, 数组的值是浅拷贝. func NewLinkedListOf[T any](ts []T) *LinkedList[T] { var head *node[T] = nil diff --git a/list/linked_list_test.go b/list/linked_list_test.go index 9ed90edf..6add868a 100644 --- a/list/linked_list_test.go +++ b/list/linked_list_test.go @@ -85,6 +85,14 @@ func TestLinkedList_Add(t *testing.T) { index: 4, wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 3, 4), }, + { + name: "add num to index 0", + list: NewLinkedListOf[int]([]int{}), + newVal: 100, + index: 0, + wantErr: nil, + wantLinkedList: NewLinkedListOf([]int{100}), + }, } for _, tc := range testCases { @@ -161,7 +169,60 @@ func TestLinkedList_Delete(t *testing.T) { } func TestLinkedList_Get(t *testing.T) { - fmt.Println("仿照 ArrayList 的测试写代码") + + tests := []struct { + name string + list *LinkedList[int] + index int + wantVal int + wantErr error + }{ + { + name: "get left", + list: NewLinkedListOf([]int{1, 2, 3, 4, 5}), + index: 0, + wantVal: 1, + }, + { + name: "get right", + list: NewLinkedListOf([]int{1, 2, 3, 4, 5}), + index: 4, + wantVal: 5, + }, + { + name: "get middle", + list: NewLinkedListOf([]int{1, 2, 3, 4, 5}), + index: 2, + wantVal: 3, + }, + { + name: "over left", + list: NewLinkedListOf([]int{1, 2, 3, 4, 5}), + index: -1, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 5, -1), + }, + { + name: "over right", + list: NewLinkedListOf([]int{1, 2, 3, 4, 5}), + index: 5, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 5, 5), + }, + { + name: "empty list", + list: NewLinkedListOf([]int{}), + index: 0, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 0, 0), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + get, err := tc.list.Get(tc.index) + assert.Equal(t, tc.wantErr, err) + if err == nil { + assert.Equal(t, tc.wantVal, get) + } + }) + } } func TestLinkedList_Len(t *testing.T) { @@ -207,6 +268,20 @@ func BenchmarkLinkedList_Add(b *testing.B) { } b.ResetTimer() for i := 0; i < b.N; i++ { - l.Add(testCase[i], testCase[i]) + _ = l.Add(testCase[i], testCase[i]) + } +} + +func BenchmarkLinkedList_Get(b *testing.B) { + l := NewLinkedListOf[int]([]int{1, 2, 3}) + for i := 1; i <= b.N; i++ { + err := l.Add(i, i) + if err != nil { + b.Fatal(err) + } + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = l.Get(i) } } From 2618650868f0626fd803455e9c73242eecfbe293 Mon Sep 17 00:00:00 2001 From: Gevin Date: Thu, 18 Aug 2022 22:04:22 +0800 Subject: [PATCH 17/32] =?UTF-8?q?=E5=AE=9E=E7=8E=B0ConcurrentList=20(#28)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- list/concurrent_list.go | 64 ++++++ list/concurrent_list_test.go | 390 +++++++++++++++++++++++++++++++++++ 2 files changed, 454 insertions(+) create mode 100644 list/concurrent_list_test.go diff --git a/list/concurrent_list.go b/list/concurrent_list.go index ae453502..263a77cb 100644 --- a/list/concurrent_list.go +++ b/list/concurrent_list.go @@ -22,3 +22,67 @@ type ConcurrentList[T any] struct { List[T] lock sync.RWMutex } + +func (c *ConcurrentList[T]) Get(index int) (T, error) { + c.lock.RLock() + defer c.lock.RUnlock() + return c.List.Get(index) +} + +func (c *ConcurrentList[T]) Append(t T) error { + c.lock.Lock() + defer c.lock.Unlock() + return c.List.Append(t) +} + +func (c *ConcurrentList[T]) Add(index int, t T) error { + c.lock.Lock() + defer c.lock.Unlock() + return c.List.Add(index, t) +} + +func (c *ConcurrentList[T]) Set(index int, t T) error { + c.lock.Lock() + defer c.lock.Unlock() + return c.List.Set(index, t) +} + +func (c *ConcurrentList[T]) Delete(index int) (T, error) { + c.lock.Lock() + defer c.lock.Unlock() + return c.List.Delete(index) +} + +func (c *ConcurrentList[T]) Len() int { + c.lock.RLock() + defer c.lock.RUnlock() + return c.List.Len() +} + +func (c *ConcurrentList[T]) Cap() int { + c.lock.RLock() + defer c.lock.RUnlock() + return c.List.Cap() +} + +func (c *ConcurrentList[T]) Range(fn func(index int, t T) error) error { + c.lock.RLock() + defer c.lock.RUnlock() + return c.List.Range(fn) +} + +func (c *ConcurrentList[T]) AsSlice() []T { + c.lock.RLock() + defer c.lock.RUnlock() + return c.List.AsSlice() +} + +func NewConcurrentListOf[T any](list List[T]) *ConcurrentList[T] { + return &ConcurrentList[T]{List: list} +} + +func NewConcurrentListOfArrayList[T any](ts []T) *ConcurrentList[T] { + var list List[T] + list = NewArrayListOf(ts) + return NewConcurrentListOf(list) +} diff --git a/list/concurrent_list_test.go b/list/concurrent_list_test.go new file mode 100644 index 00000000..591a88d8 --- /dev/null +++ b/list/concurrent_list_test.go @@ -0,0 +1,390 @@ +// 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 list + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConcurrentList_Add(t *testing.T) { + testCases := []struct { + name string + list *ConcurrentList[int] + index int + newVal int + wantSlice []int + wantErr error + }{ + // 仿照这个例子,继续添加测试 + // 你需要综合考虑下标的各种可能取值 + // 往两边增加,往中间加 + // 下标可能是负数,也可能超出你的长度 + { + name: "add num to index left", + list: NewConcurrentListOfArrayList[int]([]int{1, 2, 3}), + newVal: 100, + index: 0, + wantSlice: []int{100, 1, 2, 3}, + }, + { + name: "add num to index right", + list: NewConcurrentListOfArrayList[int]([]int{1, 2, 3}), + newVal: 100, + index: 3, + wantSlice: []int{1, 2, 3, 100}, + }, + { + name: "add num to index mid", + list: NewConcurrentListOfArrayList[int]([]int{1, 2, 3}), + newVal: 100, + index: 1, + wantSlice: []int{1, 100, 2, 3}, + }, + { + name: "add num to index -1", + list: NewConcurrentListOfArrayList[int]([]int{1, 2, 3}), + newVal: 100, + index: -1, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 3, -1), + }, + { + name: "add num to index OutOfRange", + list: NewConcurrentListOfArrayList[int]([]int{1, 2, 3}), + newVal: 100, + index: 4, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 3, 4), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.list.Add(tc.index, tc.newVal) + assert.Equal(t, tc.wantErr, err) + // 因为返回了 error,所以我们不用继续往下比较了 + if err != nil { + return + } + assert.Equal(t, tc.wantSlice, tc.list.AsSlice()) + }) + } +} + +// +// func TestConcurrentList_Append(t *testing.T) { +// // 这个比较简单,只需要增加元素,然后判断一下 Append 之后是否符合预期 +// } + +func TestConcurrentList_Cap(t *testing.T) { + testCases := []struct { + name string + expectCap int + list *ConcurrentList[int] + }{ + { + name: "与实际容量相等", + expectCap: 3, + list: NewConcurrentListOfArrayList[int]([]int{1, 2, 3}), + }, + { + name: "用户传入nil", + expectCap: 0, + list: NewConcurrentListOfArrayList[int]([]int{}), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + actual := testCase.list.Cap() + assert.Equal(t, testCase.expectCap, actual) + }) + } +} + +func TestConcurrentList_Append(t *testing.T) { + testCases := []struct { + name string + list *ConcurrentList[int] + newVal int + wantSlice []int + }{ + { + name: "append 234", + list: NewConcurrentListOfArrayList[int]([]int{123}), + newVal: 234, + wantSlice: []int{123, 234}, + }, + { + name: "nil append 123", + list: NewConcurrentListOfArrayList[int](nil), + newVal: 123, + wantSlice: []int{123}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.list.Append(tc.newVal) + if err != nil { + return + } + + assert.Equal(t, tc.wantSlice, tc.list.AsSlice()) + }) + } +} + +func TestConcurrentList_Delete(t *testing.T) { + testCases := []struct { + name string + list *ConcurrentList[int] + index int + wantSlice []int + wantVal int + wantErr error + }{ + // 仿照这个例子,继续添加测试 + // 你需要综合考虑下标的各种可能取值 + // 往两边增加,往中间加 + // 下标可能是负数,也可能超出你的长度 + + { + name: "index 0", + list: NewConcurrentListOfArrayList([]int{123, 100}), + index: 0, + wantSlice: []int{100}, + wantVal: 123, + }, + { + name: "index middle", + list: NewConcurrentListOfArrayList([]int{123, 124, 125}), + index: 1, + wantSlice: []int{123, 125}, + wantVal: 124, + }, + { + name: "index out of range", + list: NewConcurrentListOfArrayList([]int{123, 100}), + index: 12, + wantErr: newErrIndexOutOfRange(2, 12), + }, + { + name: "index less than 0", + list: NewConcurrentListOfArrayList([]int{123, 100}), + index: -1, + wantErr: newErrIndexOutOfRange(2, -1), + }, + { + name: "index last", + list: NewConcurrentListOfArrayList([]int{123, 100, 101, 102, 102, 102}), + index: 5, + wantSlice: []int{123, 100, 101, 102, 102}, + wantVal: 102, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + val, err := tc.list.Delete(tc.index) + assert.Equal(t, tc.wantErr, err) + // 因为返回了 error,所以我们不用继续往下比较了 + if err != nil { + return + } + assert.Equal(t, tc.wantSlice, tc.list.AsSlice()) + assert.Equal(t, tc.wantVal, val) + }) + } +} + +func TestConcurrentList_Len(t *testing.T) { + testCases := []struct { + name string + expectLen int + list *ConcurrentList[int] + }{ + { + name: "与实际元素数相等", + expectLen: 5, + list: NewConcurrentListOfArrayList([]int{1, 2, 3, 4, 5}), + }, + { + name: "用户传入nil", + expectLen: 0, + list: NewConcurrentListOfArrayList([]int{}), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + actual := testCase.list.Cap() + assert.Equal(t, testCase.expectLen, actual) + }) + } +} + +func TestConcurrentList_Get(t *testing.T) { + testCases := []struct { + name string + list *ConcurrentList[int] + index int + wantVal int + wantErr error + }{ + { + name: "index 0", + list: NewConcurrentListOfArrayList[int]([]int{123, 100}), + index: 0, + wantVal: 123, + }, + { + name: "index 2", + list: NewConcurrentListOfArrayList[int]([]int{123, 100}), + index: 2, + wantVal: 0, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 2, 2), + }, + { + name: "index -1", + list: NewConcurrentListOfArrayList[int]([]int{123, 100}), + index: -1, + wantVal: 0, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 2, -1), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + val, err := tc.list.Get(tc.index) + assert.Equal(t, tc.wantErr, err) + // 因为返回了 error,所以我们不用继续往下比较了 + if err != nil { + return + } + assert.Equal(t, tc.wantVal, val) + }) + } +} +func TestConcurrentList_Range(t *testing.T) { + // 设计两个测试用例,用求和来作为场景 + // 一个测试用例是计算全部元素的和 + // 一个测试用例是计算元素的和,如果遇到了第一个负数,那么就中断返回 + // 测试最终断言求的和是否符合预期 + testCases := []struct { + name string + list *ConcurrentList[int] + index int + wantVal int + wantErr error + }{ + { + name: "计算全部元素的和", + list: NewConcurrentListOfArrayList([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}), + wantVal: 55, + wantErr: nil, + }, + { + name: "测试中断", + list: NewConcurrentListOfArrayList([]int{1, 2, 3, 4, -5, 6, 7, 8, -9, 10}), + wantVal: 41, + wantErr: errors.New("index 4 is error"), + }, + { + name: "测试数组为nil", + list: NewConcurrentListOfArrayList([]int{}), + wantVal: 0, + wantErr: nil, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := 0 + err := tc.list.Range(func(index int, num int) error { + if num < 0 { + return fmt.Errorf("index %d is error", index) + } + result += num + return nil + }) + + assert.Equal(t, tc.wantErr, err) + if err != nil { + return + } + assert.Equal(t, tc.wantVal, result) + }) + } +} + +func TestConcurrentList_AsSlice(t *testing.T) { + vals := []int{1, 2, 3} + a := NewConcurrentListOfArrayList[int](vals) + slice := a.AsSlice() + // 内容相同 + assert.Equal(t, slice, vals) + aAddr := fmt.Sprintf("%p", vals) + sliceAddr := fmt.Sprintf("%p", slice) + // 但是地址不同,也就是意味着 slice 必须是一个新创建的 + assert.NotEqual(t, aAddr, sliceAddr) +} + +func TestConcurrentList_Set(t *testing.T) { + testCases := []struct { + name string + list *ConcurrentList[int] + index int + newVal int + wantSlice []int + wantErr error + }{ + { + name: "set 5 by index 1", + list: NewConcurrentListOfArrayList([]int{0, 1, 2, 3, 4}), + index: 1, + newVal: 5, + wantSlice: []int{0, 5, 2, 3, 4}, + wantErr: nil, + }, + { + name: "index -1", + list: NewConcurrentListOfArrayList([]int{0, 1, 2, 3, 4}), + index: -1, + newVal: 5, + wantSlice: []int{}, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 5, -1), + }, + { + name: "index 100", + list: NewConcurrentListOfArrayList([]int{0, 1, 2, 3, 4}), + index: 100, + newVal: 5, + wantSlice: []int{}, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 5, 100), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.list.Set(tc.index, tc.newVal) + if err != nil { + assert.Equal(t, tc.wantErr, err) + return + } + assert.Equal(t, tc.wantSlice, tc.list.AsSlice()) + }) + } + +} From 8e018944fb20a6bf8ab223b54892ee47a108ef51 Mon Sep 17 00:00:00 2001 From: mousseqin <23616001+mousseqin@users.noreply.github.com> Date: Thu, 18 Aug 2022 22:22:46 +0800 Subject: [PATCH 18/32] =?UTF-8?q?linked=5Flist=E4=B8=AD=E6=96=B0=E5=A2=9Ea?= =?UTF-8?q?ppend=E6=96=B9=E6=B3=95=20(#34)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .CHANGELOG.md | 3 ++- list/linked_list.go | 21 +++++++++++++++++++-- list/linked_list_test.go | 35 ++++++++++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/.CHANGELOG.md b/.CHANGELOG.md index aacd4db4..3055c9c6 100644 --- a/.CHANGELOG.md +++ b/.CHANGELOG.md @@ -5,4 +5,5 @@ [ekit: implement Get()](https://github.com/gotomicro/ekit/pull/18) [ekit: implement Len()](https://github.com/gotomicro/ekit/pull/18) [ekit: 实现了 LinkedList Add() NewLinkedListOf](https://github.com/gotomicro/ekit/gpull/26) -[ekit: 实现了 LinkedList Get](https://github.com/gotomicro/ekit/pull/31) \ No newline at end of file +[ekit: 实现了 LinkedList Get](https://github.com/gotomicro/ekit/pull/31) +[ekit: 实现了 LinkedList Append](https://github.com/gotomicro/ekit/pull/34) \ No newline at end of file diff --git a/list/linked_list.go b/list/linked_list.go index 16e882a3..8ba77250 100644 --- a/list/linked_list.go +++ b/list/linked_list.go @@ -54,9 +54,26 @@ func (l *LinkedList[T]) Get(index int) (t T, err error) { return node.val, nil } +// Append 往链表最后添加元素 func (l *LinkedList[T]) Append(t T) error { - // TODO implement me - panic("implement me") + // 创建一个新元素,通过传入参数进行数据域的赋值 + newNode := &node[T]{ + val: t, + } + nLen := l.length + defer func() { + l.length += 1 + }() + if nLen == 0 { + // 如果链表为空,则直接将新元素作为头尾节点 + l.head = newNode + l.tail = newNode + } else { + // 调用insertAfter + l.tail.insertAfter(newNode) + l.tail = newNode + } + return nil } // Add 在 LinkedList 下标为 index 的位置插入一个元素 diff --git a/list/linked_list_test.go b/list/linked_list_test.go index 6add868a..d506694f 100644 --- a/list/linked_list_test.go +++ b/list/linked_list_test.go @@ -110,7 +110,40 @@ func TestLinkedList_Add(t *testing.T) { } func TestLinkedList_Append(t *testing.T) { - fmt.Println("仿照 ArrayList 的测试写代码") + testCases := []struct { + name string + list *LinkedList[int] + newVal int + wantLinkedList *LinkedList[int] + }{ + { + name: "append uint to empty node", + list: NewLinkedListOf[int]([]int{}), + newVal: 77, + wantLinkedList: NewLinkedListOf[int]([]int{77}), + }, + { + name: "append uint to node", + list: NewLinkedListOf[int]([]int{0}), + newVal: 999, + wantLinkedList: NewLinkedListOf[int]([]int{0, 999}), + }, + { + name: "append int to node", + list: NewLinkedListOf[int]([]int{845, 123, 7898}), + newVal: -788, + wantLinkedList: NewLinkedListOf[int]([]int{845, 123, 7898, -788}), + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.list.Append(tc.newVal) + if err != nil { + return + } + assert.Equal(t, tc.wantLinkedList, tc.list) + }) + } } func TestNewLinkedListOf(t *testing.T) { From 2f5a0ab0f53bd0fa42d5d572bf240054dde0e7a9 Mon Sep 17 00:00:00 2001 From: Ming Deng Date: Thu, 18 Aug 2022 22:40:47 +0800 Subject: [PATCH 19/32] =?UTF-8?q?list:=20=E5=88=A0=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E7=94=A8=E4=BB=A3=E7=A0=81=20(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .CHANGELOG.md | 8 ++-- list/array_list_test.go | 39 ------------------- list/concurrent_list.go | 10 ----- list/concurrent_list_test.go | 75 +++++++++++++++--------------------- list/linked_list.go | 10 ++--- list/linked_list_test.go | 4 -- 6 files changed, 40 insertions(+), 106 deletions(-) diff --git a/.CHANGELOG.md b/.CHANGELOG.md index 3055c9c6..02ba1bc6 100644 --- a/.CHANGELOG.md +++ b/.CHANGELOG.md @@ -1,9 +1,9 @@ # 开发中 [ekit: add ToPtr function](https://github.com/gotomicro/ekit/pull/6) [sql: 支持 JsonColumn](https://github.com/gotomicro/ekit/pull/7) -[ekit: implement Range()](https://github.com/gotomicro/ekit/pull/12) -[ekit: implement Get()](https://github.com/gotomicro/ekit/pull/18) -[ekit: implement Len()](https://github.com/gotomicro/ekit/pull/18) -[ekit: 实现了 LinkedList Add() NewLinkedListOf](https://github.com/gotomicro/ekit/gpull/26) +[ekit: 支持 ArrayList Range()](https://github.com/gotomicro/ekit/pull/12) +[ekit: 支持 ArrayList Get()](https://github.com/gotomicro/ekit/pull/18) +[ekit: 支持 ArrayList Len()](https://github.com/gotomicro/ekit/pull/18) +[ekit: 实现了 LinkedList Add() NewLinkedListOf](https://github.com/gotomicro/ekit/pull/26) [ekit: 实现了 LinkedList Get](https://github.com/gotomicro/ekit/pull/31) [ekit: 实现了 LinkedList Append](https://github.com/gotomicro/ekit/pull/34) \ No newline at end of file diff --git a/list/array_list_test.go b/list/array_list_test.go index 0409c1ba..675953fc 100644 --- a/list/array_list_test.go +++ b/list/array_list_test.go @@ -31,10 +31,6 @@ func TestArrayList_Add(t *testing.T) { wantSlice []int wantErr error }{ - // 仿照这个例子,继续添加测试 - // 你需要综合考虑下标的各种可能取值 - // 往两边增加,往中间加 - // 下标可能是负数,也可能超出你的长度 { name: "add num to index left", list: NewArrayListOf[int]([]int{1, 2, 3}), @@ -85,11 +81,6 @@ func TestArrayList_Add(t *testing.T) { } } -// -// func TestArrayList_Append(t *testing.T) { -// // 这个比较简单,只需要增加元素,然后判断一下 Append 之后是否符合预期 -// } - func TestArrayList_Cap(t *testing.T) { testCases := []struct { name string @@ -179,11 +170,6 @@ func TestArrayList_Delete(t *testing.T) { wantVal int wantErr error }{ - // 仿照这个例子,继续添加测试 - // 你需要综合考虑下标的各种可能取值 - // 往两边增加,往中间加 - // 下标可能是负数,也可能超出你的长度 - { name: "index 0", list: &ArrayList[int]{ @@ -315,10 +301,6 @@ func TestArrayList_Get(t *testing.T) { } } func TestArrayList_Range(t *testing.T) { - // 设计两个测试用例,用求和来作为场景 - // 一个测试用例是计算全部元素的和 - // 一个测试用例是计算元素的和,如果遇到了第一个负数,那么就中断返回 - // 测试最终断言求的和是否符合预期 testCases := []struct { name string list *ArrayList[int] @@ -371,16 +353,6 @@ func TestArrayList_Range(t *testing.T) { } } -// -// func TestArrayList_Len(t *testing.T) { -// -// } -// -// func TestArrayList_Set(t *testing.T) { -// -// } -// - func TestArrayList_AsSlice(t *testing.T) { vals := []int{1, 2, 3} a := NewArrayListOf[int](vals) @@ -393,16 +365,6 @@ func TestArrayList_AsSlice(t *testing.T) { assert.NotEqual(t, aAddr, sliceAddr) } -// -// // 为其它所有的公开方法都加上例子 -// func ExampleArrayList_Add() { -// list := NewArrayListOf[int]([]int{1, 2, 3}) -// _ = list.Add(0, 9) -// fmt.Println(list.AsSlice()) -// // Output: -// // [9 1 2 3] -// } - func TestArrayList_Set(t *testing.T) { testCases := []struct { name string @@ -448,5 +410,4 @@ func TestArrayList_Set(t *testing.T) { assert.Equal(t, tc.wantSlice, tc.list.vals) }) } - } diff --git a/list/concurrent_list.go b/list/concurrent_list.go index 263a77cb..cf781304 100644 --- a/list/concurrent_list.go +++ b/list/concurrent_list.go @@ -76,13 +76,3 @@ func (c *ConcurrentList[T]) AsSlice() []T { defer c.lock.RUnlock() return c.List.AsSlice() } - -func NewConcurrentListOf[T any](list List[T]) *ConcurrentList[T] { - return &ConcurrentList[T]{List: list} -} - -func NewConcurrentListOfArrayList[T any](ts []T) *ConcurrentList[T] { - var list List[T] - list = NewArrayListOf(ts) - return NewConcurrentListOf(list) -} diff --git a/list/concurrent_list_test.go b/list/concurrent_list_test.go index 591a88d8..3a59e534 100644 --- a/list/concurrent_list_test.go +++ b/list/concurrent_list_test.go @@ -31,41 +31,37 @@ func TestConcurrentList_Add(t *testing.T) { wantSlice []int wantErr error }{ - // 仿照这个例子,继续添加测试 - // 你需要综合考虑下标的各种可能取值 - // 往两边增加,往中间加 - // 下标可能是负数,也可能超出你的长度 { name: "add num to index left", - list: NewConcurrentListOfArrayList[int]([]int{1, 2, 3}), + list: newConcurrentListOfSlice[int]([]int{1, 2, 3}), newVal: 100, index: 0, wantSlice: []int{100, 1, 2, 3}, }, { name: "add num to index right", - list: NewConcurrentListOfArrayList[int]([]int{1, 2, 3}), + list: newConcurrentListOfSlice[int]([]int{1, 2, 3}), newVal: 100, index: 3, wantSlice: []int{1, 2, 3, 100}, }, { name: "add num to index mid", - list: NewConcurrentListOfArrayList[int]([]int{1, 2, 3}), + list: newConcurrentListOfSlice[int]([]int{1, 2, 3}), newVal: 100, index: 1, wantSlice: []int{1, 100, 2, 3}, }, { name: "add num to index -1", - list: NewConcurrentListOfArrayList[int]([]int{1, 2, 3}), + list: newConcurrentListOfSlice[int]([]int{1, 2, 3}), newVal: 100, index: -1, wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 3, -1), }, { name: "add num to index OutOfRange", - list: NewConcurrentListOfArrayList[int]([]int{1, 2, 3}), + list: newConcurrentListOfSlice[int]([]int{1, 2, 3}), newVal: 100, index: 4, wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 3, 4), @@ -85,11 +81,6 @@ func TestConcurrentList_Add(t *testing.T) { } } -// -// func TestConcurrentList_Append(t *testing.T) { -// // 这个比较简单,只需要增加元素,然后判断一下 Append 之后是否符合预期 -// } - func TestConcurrentList_Cap(t *testing.T) { testCases := []struct { name string @@ -99,12 +90,12 @@ func TestConcurrentList_Cap(t *testing.T) { { name: "与实际容量相等", expectCap: 3, - list: NewConcurrentListOfArrayList[int]([]int{1, 2, 3}), + list: newConcurrentListOfSlice[int]([]int{1, 2, 3}), }, { name: "用户传入nil", expectCap: 0, - list: NewConcurrentListOfArrayList[int]([]int{}), + list: newConcurrentListOfSlice[int]([]int{}), }, } for _, testCase := range testCases { @@ -124,13 +115,13 @@ func TestConcurrentList_Append(t *testing.T) { }{ { name: "append 234", - list: NewConcurrentListOfArrayList[int]([]int{123}), + list: newConcurrentListOfSlice[int]([]int{123}), newVal: 234, wantSlice: []int{123, 234}, }, { name: "nil append 123", - list: NewConcurrentListOfArrayList[int](nil), + list: newConcurrentListOfSlice[int](nil), newVal: 123, wantSlice: []int{123}, }, @@ -157,40 +148,35 @@ func TestConcurrentList_Delete(t *testing.T) { wantVal int wantErr error }{ - // 仿照这个例子,继续添加测试 - // 你需要综合考虑下标的各种可能取值 - // 往两边增加,往中间加 - // 下标可能是负数,也可能超出你的长度 - { name: "index 0", - list: NewConcurrentListOfArrayList([]int{123, 100}), + list: newConcurrentListOfSlice([]int{123, 100}), index: 0, wantSlice: []int{100}, wantVal: 123, }, { name: "index middle", - list: NewConcurrentListOfArrayList([]int{123, 124, 125}), + list: newConcurrentListOfSlice([]int{123, 124, 125}), index: 1, wantSlice: []int{123, 125}, wantVal: 124, }, { name: "index out of range", - list: NewConcurrentListOfArrayList([]int{123, 100}), + list: newConcurrentListOfSlice([]int{123, 100}), index: 12, wantErr: newErrIndexOutOfRange(2, 12), }, { name: "index less than 0", - list: NewConcurrentListOfArrayList([]int{123, 100}), + list: newConcurrentListOfSlice([]int{123, 100}), index: -1, wantErr: newErrIndexOutOfRange(2, -1), }, { name: "index last", - list: NewConcurrentListOfArrayList([]int{123, 100, 101, 102, 102, 102}), + list: newConcurrentListOfSlice([]int{123, 100, 101, 102, 102, 102}), index: 5, wantSlice: []int{123, 100, 101, 102, 102}, wantVal: 102, @@ -220,12 +206,12 @@ func TestConcurrentList_Len(t *testing.T) { { name: "与实际元素数相等", expectLen: 5, - list: NewConcurrentListOfArrayList([]int{1, 2, 3, 4, 5}), + list: newConcurrentListOfSlice([]int{1, 2, 3, 4, 5}), }, { name: "用户传入nil", expectLen: 0, - list: NewConcurrentListOfArrayList([]int{}), + list: newConcurrentListOfSlice([]int{}), }, } for _, testCase := range testCases { @@ -246,20 +232,20 @@ func TestConcurrentList_Get(t *testing.T) { }{ { name: "index 0", - list: NewConcurrentListOfArrayList[int]([]int{123, 100}), + list: newConcurrentListOfSlice[int]([]int{123, 100}), index: 0, wantVal: 123, }, { name: "index 2", - list: NewConcurrentListOfArrayList[int]([]int{123, 100}), + list: newConcurrentListOfSlice[int]([]int{123, 100}), index: 2, wantVal: 0, wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 2, 2), }, { name: "index -1", - list: NewConcurrentListOfArrayList[int]([]int{123, 100}), + list: newConcurrentListOfSlice[int]([]int{123, 100}), index: -1, wantVal: 0, wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 2, -1), @@ -278,11 +264,8 @@ func TestConcurrentList_Get(t *testing.T) { }) } } + func TestConcurrentList_Range(t *testing.T) { - // 设计两个测试用例,用求和来作为场景 - // 一个测试用例是计算全部元素的和 - // 一个测试用例是计算元素的和,如果遇到了第一个负数,那么就中断返回 - // 测试最终断言求的和是否符合预期 testCases := []struct { name string list *ConcurrentList[int] @@ -292,19 +275,19 @@ func TestConcurrentList_Range(t *testing.T) { }{ { name: "计算全部元素的和", - list: NewConcurrentListOfArrayList([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}), + list: newConcurrentListOfSlice([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}), wantVal: 55, wantErr: nil, }, { name: "测试中断", - list: NewConcurrentListOfArrayList([]int{1, 2, 3, 4, -5, 6, 7, 8, -9, 10}), + list: newConcurrentListOfSlice([]int{1, 2, 3, 4, -5, 6, 7, 8, -9, 10}), wantVal: 41, wantErr: errors.New("index 4 is error"), }, { name: "测试数组为nil", - list: NewConcurrentListOfArrayList([]int{}), + list: newConcurrentListOfSlice([]int{}), wantVal: 0, wantErr: nil, }, @@ -331,7 +314,7 @@ func TestConcurrentList_Range(t *testing.T) { func TestConcurrentList_AsSlice(t *testing.T) { vals := []int{1, 2, 3} - a := NewConcurrentListOfArrayList[int](vals) + a := newConcurrentListOfSlice[int](vals) slice := a.AsSlice() // 内容相同 assert.Equal(t, slice, vals) @@ -352,7 +335,7 @@ func TestConcurrentList_Set(t *testing.T) { }{ { name: "set 5 by index 1", - list: NewConcurrentListOfArrayList([]int{0, 1, 2, 3, 4}), + list: newConcurrentListOfSlice([]int{0, 1, 2, 3, 4}), index: 1, newVal: 5, wantSlice: []int{0, 5, 2, 3, 4}, @@ -360,7 +343,7 @@ func TestConcurrentList_Set(t *testing.T) { }, { name: "index -1", - list: NewConcurrentListOfArrayList([]int{0, 1, 2, 3, 4}), + list: newConcurrentListOfSlice([]int{0, 1, 2, 3, 4}), index: -1, newVal: 5, wantSlice: []int{}, @@ -368,7 +351,7 @@ func TestConcurrentList_Set(t *testing.T) { }, { name: "index 100", - list: NewConcurrentListOfArrayList([]int{0, 1, 2, 3, 4}), + list: newConcurrentListOfSlice([]int{0, 1, 2, 3, 4}), index: 100, newVal: 5, wantSlice: []int{}, @@ -386,5 +369,9 @@ func TestConcurrentList_Set(t *testing.T) { assert.Equal(t, tc.wantSlice, tc.list.AsSlice()) }) } +} +func newConcurrentListOfSlice[T any](ts []T) *ConcurrentList[T] { + var list List[T] = NewArrayListOf(ts) + return &ConcurrentList[T]{List: list} } diff --git a/list/linked_list.go b/list/linked_list.go index 8ba77250..48b997d5 100644 --- a/list/linked_list.go +++ b/list/linked_list.go @@ -45,13 +45,13 @@ func (l *LinkedList[T]) getNode(index int) *node[T] { return cur } -func (l *LinkedList[T]) Get(index int) (t T, err error) { +func (l *LinkedList[T]) Get(index int) (T, error) { if index < 0 || index >= l.length { - err = newErrIndexOutOfRange(l.length, index) - return + var t T + return t, newErrIndexOutOfRange(l.length, index) } - node := l.getNode(index) - return node.val, nil + n := l.getNode(index) + return n.val, nil } // Append 往链表最后添加元素 diff --git a/list/linked_list_test.go b/list/linked_list_test.go index d506694f..27ede201 100644 --- a/list/linked_list_test.go +++ b/list/linked_list_test.go @@ -32,10 +32,6 @@ func TestLinkedList_Add(t *testing.T) { wantLinkedList *LinkedList[int] wantErr error }{ - // 仿照这个例子,继续添加测试 - // 你需要综合考虑下标的各种可能取值 - // 往两边增加,往中间加 - // 下标可能是负数,也可能超出你的长度 { name: "add num to index left", list: NewLinkedListOf[int]([]int{1, 2, 3}), From 0efba37a5ff6b433ee8d0a7a25cd818d205355cf Mon Sep 17 00:00:00 2001 From: mousseqin <23616001+mousseqin@users.noreply.github.com> Date: Sat, 20 Aug 2022 16:06:38 +0800 Subject: [PATCH 20/32] =?UTF-8?q?list:=20inked=5Flist=E5=AE=8C=E6=88=90del?= =?UTF-8?q?ete=E6=96=B9=E6=B3=95=E4=BB=A5=E5=8F=8A=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=94=A8=E4=BE=8B=20(#38)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .CHANGELOG.md | 3 +- list/linked_list.go | 41 ++++++++++++++++++++-- list/linked_list_test.go | 73 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 110 insertions(+), 7 deletions(-) diff --git a/.CHANGELOG.md b/.CHANGELOG.md index 02ba1bc6..a3b851d8 100644 --- a/.CHANGELOG.md +++ b/.CHANGELOG.md @@ -6,4 +6,5 @@ [ekit: 支持 ArrayList Len()](https://github.com/gotomicro/ekit/pull/18) [ekit: 实现了 LinkedList Add() NewLinkedListOf](https://github.com/gotomicro/ekit/pull/26) [ekit: 实现了 LinkedList Get](https://github.com/gotomicro/ekit/pull/31) -[ekit: 实现了 LinkedList Append](https://github.com/gotomicro/ekit/pull/34) \ No newline at end of file +[ekit: 实现了 LinkedList Append](https://github.com/gotomicro/ekit/pull/34) +[ekit: 实现了 LinkedList Delete](https://github.com/gotomicro/ekit/pull/38) \ No newline at end of file diff --git a/list/linked_list.go b/list/linked_list.go index 48b997d5..c4488888 100644 --- a/list/linked_list.go +++ b/list/linked_list.go @@ -122,9 +122,46 @@ func (l *LinkedList[T]) Set(index int, t T) error { panic("implement me") } +// Delete 删除指定位置的元素 func (l *LinkedList[T]) Delete(index int) (T, error) { - // TODO implement me - panic("implement me") + nLen := l.length + var delVal T // 需要删除的节点val + if index < 0 || index > nLen || nLen == 0 { + return delVal, newErrIndexOutOfRange(nLen, index) + } + defer func() { + l.length -= 1 + }() + + // 删除head + if index == 0 { + delVal = l.head.val + if nLen > 1 { + l.head.next.prev = nil + l.head = l.head.next + } else { + l.head = nil + l.tail = nil + } + return delVal, nil + } + // 删除tail + if index == nLen-1 { + delVal = l.tail.val + l.tail = l.tail.prev + l.tail.next = nil + return delVal, nil + } + + n := l.head + for i := 0; i < index-1; i++ { + n = n.next + } + delVal = n.next.val + n.next = n.next.next + n.next.prev = n + + return delVal, nil } func (l *LinkedList[T]) Len() int { diff --git a/list/linked_list_test.go b/list/linked_list_test.go index 27ede201..14d59430 100644 --- a/list/linked_list_test.go +++ b/list/linked_list_test.go @@ -105,6 +105,75 @@ func TestLinkedList_Add(t *testing.T) { } } +func TestLinkedList_Delete(t *testing.T) { + testCases := []struct { + name string + list *LinkedList[int] + wantLinkedList *LinkedList[int] + delVal int + index int + wantErr error + }{ + { + name: "delete num to index -1", + list: NewLinkedListOf[int]([]int{1, 2, 3}), + index: -1, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 3, -1), + }, + { + name: "delete beyond length index 99", + list: NewLinkedListOf[int]([]int{1, 2, 3}), + index: 99, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 3, 99), + }, + { + name: "delete empty node", + list: NewLinkedListOf[int]([]int{}), + index: 3, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 0, 3), + }, + { + name: "delete num to index 0", + list: NewLinkedListOf[int]([]int{1, 2, 3}), + index: 0, + delVal: 1, + wantLinkedList: NewLinkedListOf([]int{2, 3}), + }, + { + name: "delete num to index by tail", + list: NewLinkedListOf[int]([]int{1, 2, 3, 4, 5}), + index: 4, + delVal: 5, + wantLinkedList: NewLinkedListOf([]int{1, 2, 3, 4}), + }, + { + name: "delete num to index 1", + list: NewLinkedListOf[int]([]int{11, 22, 33, 44, 55}), + index: 1, + delVal: 22, + wantLinkedList: NewLinkedListOf([]int{11, 33, 44, 55}), + }, + { + name: "deleting an element with only one", + list: NewLinkedListOf[int]([]int{888}), + index: 0, + delVal: 888, + wantLinkedList: NewLinkedListOf([]int{}), + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + delVal, err := tc.list.Delete(tc.index) + if err != nil { + assert.Equal(t, tc.wantErr, err) + } else { + assert.Equal(t, tc.delVal, delVal) + assert.Equal(t, tc.wantLinkedList, tc.list) + } + }) + } +} + func TestLinkedList_Append(t *testing.T) { testCases := []struct { name string @@ -193,10 +262,6 @@ func TestLinkedList_Cap(t *testing.T) { fmt.Println("仿照 ArrayList 的测试写代码") } -func TestLinkedList_Delete(t *testing.T) { - fmt.Println("仿照 ArrayList 的测试写代码") -} - func TestLinkedList_Get(t *testing.T) { tests := []struct { From 2e22d1957f9b40fb6e4c644a901d4ccab84f5f54 Mon Sep 17 00:00:00 2001 From: ccchooko Date: Sat, 20 Aug 2022 17:13:20 +0800 Subject: [PATCH 21/32] =?UTF-8?q?LinkedList=E6=B7=BB=E5=8A=A0Append()?= =?UTF-8?q?=E6=96=B9=E6=B3=95=20(#37)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: ccchooko --- list/linked_list.go | 23 ++++++++--------------- list/linked_list_test.go | 31 ++++++++++++++++++------------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/list/linked_list.go b/list/linked_list.go index c4488888..3914f147 100644 --- a/list/linked_list.go +++ b/list/linked_list.go @@ -56,23 +56,16 @@ func (l *LinkedList[T]) Get(index int) (T, error) { // Append 往链表最后添加元素 func (l *LinkedList[T]) Append(t T) error { - // 创建一个新元素,通过传入参数进行数据域的赋值 - newNode := &node[T]{ - val: t, - } - nLen := l.length - defer func() { - l.length += 1 - }() - if nLen == 0 { - // 如果链表为空,则直接将新元素作为头尾节点 - l.head = newNode - l.tail = newNode + newLastNode := &node[T]{val: t} + if l.length == 0 { + l.head = newLastNode + l.tail = newLastNode } else { - // 调用insertAfter - l.tail.insertAfter(newNode) - l.tail = newNode + l.tail.next = newLastNode + newLastNode.prev = l.tail + l.tail = newLastNode } + l.length += 1 return nil } diff --git a/list/linked_list_test.go b/list/linked_list_test.go index 14d59430..b742086c 100644 --- a/list/linked_list_test.go +++ b/list/linked_list_test.go @@ -178,35 +178,40 @@ func TestLinkedList_Append(t *testing.T) { testCases := []struct { name string list *LinkedList[int] + index int newVal int wantLinkedList *LinkedList[int] + wantErr error }{ { - name: "append uint to empty node", - list: NewLinkedListOf[int]([]int{}), - newVal: 77, - wantLinkedList: NewLinkedListOf[int]([]int{77}), + name: "append val", + list: NewLinkedListOf[int]([]int{1, 2, 3}), + newVal: 100, + wantLinkedList: NewLinkedListOf[int]([]int{1, 2, 3, 100}), }, { - name: "append uint to node", - list: NewLinkedListOf[int]([]int{0}), - newVal: 999, - wantLinkedList: NewLinkedListOf[int]([]int{0, 999}), + name: "append val in list length of 1", + list: NewLinkedListOf[int]([]int{1}), + newVal: 100, + wantLinkedList: NewLinkedListOf[int]([]int{1, 100}), }, { - name: "append int to node", - list: NewLinkedListOf[int]([]int{845, 123, 7898}), - newVal: -788, - wantLinkedList: NewLinkedListOf[int]([]int{845, 123, 7898, -788}), + name: "append val to nil list", + list: NewLinkedListOf[int]([]int{}), + newVal: 100, + wantLinkedList: NewLinkedListOf[int]([]int{100}), }, } + for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { err := tc.list.Append(tc.newVal) + assert.Equal(t, tc.wantErr, err) + // 因为返回了 error,所以我们不用继续往下比较了 if err != nil { return } - assert.Equal(t, tc.wantLinkedList, tc.list) + assert.True(t, linkedListEqual(tc.list, tc.wantLinkedList)) }) } } From b76998ce245d5b688b0e40a8bfa1e9a17e600beb Mon Sep 17 00:00:00 2001 From: hhh111119 Date: Sun, 21 Aug 2022 17:24:49 +0800 Subject: [PATCH 22/32] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20TestPool=20Test=20?= =?UTF-8?q?=E4=B8=8D=E7=A8=B3=E5=AE=9A=E7=9A=84=E9=97=AE=E9=A2=98=20(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修复 TestPool bug, 会有偶发的不通过, 原因应该是, 执行第二次 get 之前, 发生了 gc. - 加了一个 次数, 记录函数执行了几次 - 根据次数, 来判断应该取到什么值 * 更新 changelog --- .CHANGELOG.md | 3 ++- pool/pool_test.go | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.CHANGELOG.md b/.CHANGELOG.md index a3b851d8..ca81d264 100644 --- a/.CHANGELOG.md +++ b/.CHANGELOG.md @@ -7,4 +7,5 @@ [ekit: 实现了 LinkedList Add() NewLinkedListOf](https://github.com/gotomicro/ekit/pull/26) [ekit: 实现了 LinkedList Get](https://github.com/gotomicro/ekit/pull/31) [ekit: 实现了 LinkedList Append](https://github.com/gotomicro/ekit/pull/34) -[ekit: 实现了 LinkedList Delete](https://github.com/gotomicro/ekit/pull/38) \ No newline at end of file +[ekit: 实现了 LinkedList Delete](https://github.com/gotomicro/ekit/pull/38) +[ekit: 修复 Pool TestPool](https://github.com/gotomicro/ekit/pull/40) \ No newline at end of file diff --git a/pool/pool_test.go b/pool/pool_test.go index 1f2cc8f9..73972312 100644 --- a/pool/pool_test.go +++ b/pool/pool_test.go @@ -22,7 +22,9 @@ import ( ) func TestPool(t *testing.T) { + cnt := 0 p := New[[]byte](func() []byte { + cnt += 1 res := make([]byte, 1, 12) res[0] = 'A' return res @@ -33,7 +35,12 @@ func TestPool(t *testing.T) { res = append(res, 'B') p.Put(res) res = p.Get() - assert.Equal(t, "AB", string(res)) + if cnt == 1 { + assert.Equal(t, "AB", string(res)) + } else { + assert.Equal(t, "A", string(res)) + } + } func ExampleNew() { From 001433a022a78ad83ab6def9962b0696dfd725e3 Mon Sep 17 00:00:00 2001 From: Deng Ming Date: Tue, 23 Aug 2022 21:45:49 +0800 Subject: [PATCH 23/32] =?UTF-8?q?bean/copier:=20Copier=20=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=92=8C=E5=8F=8D=E5=B0=84=E5=AE=9E=E7=8E=B0=E5=AE=9A?= =?UTF-8?q?=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bean/copier/copy.go | 27 +++++++++++ bean/copier/reflect_copier.go | 45 ++++++++++++++++++ bean/copier/reflect_copier_test.go | 74 ++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 bean/copier/copy.go create mode 100644 bean/copier/reflect_copier.go create mode 100644 bean/copier/reflect_copier_test.go diff --git a/bean/copier/copy.go b/bean/copier/copy.go new file mode 100644 index 00000000..a48a1336 --- /dev/null +++ b/bean/copier/copy.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 copier + +// Copier 复制数据 +// 1. 深拷贝亦或是浅拷贝,取决于具体的实现。每个实现都要声明清楚这一点; +// 2. Src 和 Dst 都必须是普通的结构体,支持组合 +// 3. 只复制公共字段 +// 这种设计设计,即使用 *Src 和 *Dst 可能加剧内存逃逸 +type Copier[Src any, Dst any] interface { + // CopyTo 将 src 中的数据复制到 dst 中 + CopyTo(src *Src, dst *Dst) error + // Copy 将创建一个 Dst 的实例,并且将 Src 中的数据复制过去 + Copy(src *Src) (*Dst, error) +} diff --git a/bean/copier/reflect_copier.go b/bean/copier/reflect_copier.go new file mode 100644 index 00000000..a90ebd6c --- /dev/null +++ b/bean/copier/reflect_copier.go @@ -0,0 +1,45 @@ +// 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 copier + +// ReflectCopier 基于反射的实现 +// ReflectCopier 是浅拷贝 +type ReflectCopier[Src any, Dst any] struct { + // fieldMap Src 中字段下标到 Dst 字段下标的映射 + // 其中 key 是 Src 中字段下标使用连字符 - 连接起来 + // 不在 fieldMap 中字段则意味着被忽略 + fieldMap map[string][]int +} + +func NewReflectCopier[Src any, Dst any]() *ReflectCopier[Src, Dst] { + panic("implement me") +} + +// CopyTo 执行复制 +// 执行复制的逻辑是: +// 1. 按照字段的映射关系进行匹配 +// 2. 如果 Src 和 Dst 中匹配的字段,其类型是基本类型(及其指针)或者内置类型(及其指针),并且类型一样,则直接用 Src 的值 +// 3. 如果 Src 和 Dst 中匹配的字段,其类型都是结构体,或者都是结构体指针,那么会深入复制 +// 4. 否则,返回类型不匹配的错误 +// TODO: 支持不同类型之间的转换 +func (r *ReflectCopier[Src, Dst]) CopyTo(src *Src, dst *Dst) error { + panic("implement me") +} + +func (r *ReflectCopier[Src, Dst]) Copy(src *Src) (*Dst, error) { + dst := new(Dst) + err := r.CopyTo(src, dst) + return dst, err +} diff --git a/bean/copier/reflect_copier_test.go b/bean/copier/reflect_copier_test.go new file mode 100644 index 00000000..fda6ec78 --- /dev/null +++ b/bean/copier/reflect_copier_test.go @@ -0,0 +1,74 @@ +// 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 copier + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestReflectCopier_Copy(t *testing.T) { + testCases := []struct { + name string + copyFunc func() (any, error) + wantDst any + wantErr error + }{ + // { + // name: "simple struct", + // copyFunc: func() (any, error) { + // copier := NewReflectCopier[SimpleSrc, SimpleDst]() + // return copier.Copy(&SimpleSrc{ + // Name: "大明", + // Age: ekit.ToPtr[int](18), + // Friends: []string{"Tom", "Jerry"}, + // }) + // }, + // wantDst: SimpleDst{ + // Name: "大明", + // Age: ekit.ToPtr[int](18), + // Friends: []string{"Tom", "Jerry"}, + // }, + // }, + // 你还需要测试 + // 1. Src 或者 Dst 类型非法,例如基本类型,内置类型或者接口 + // 2. 测试组合(结构体组合,指针组合,接口组合——接口组合可以直接不支持),深层组合,多重组合 + // 3. 复杂类型字段,如字段是结构体,字段是结构体指针,以及多级指针(不需要支持) + // 4. 类型不匹配 + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + res, err := tc.copyFunc() + assert.Equal(t, tc.wantErr, err) + if err != nil { + return + } + assert.Equal(t, tc.wantDst, res) + }) + } +} + +type SimpleSrc struct { + Name string + Age *int + Friends []string +} + +type SimpleDst struct { + Name string + Age *int + Friends []string +} From 1a0e9df03ae132073a7f0be21974197dceb8906b Mon Sep 17 00:00:00 2001 From: mousseqin <23616001+mousseqin@users.noreply.github.com> Date: Tue, 23 Aug 2022 21:47:30 +0800 Subject: [PATCH 24/32] =?UTF-8?q?list:=20=E5=AE=8C=E6=88=90=E4=BA=86Linked?= =?UTF-8?q?List=20Set=E6=96=B9=E6=B3=95=20(#42)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * git reset * list: 完成了LinkedList Set方法 --- list/linked_list.go | 9 +++-- list/linked_list_test.go | 74 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/list/linked_list.go b/list/linked_list.go index 3914f147..12d2c350 100644 --- a/list/linked_list.go +++ b/list/linked_list.go @@ -110,9 +110,14 @@ func (l *LinkedList[T]) fromTailToHead(index int) bool { return index > (l.length / 2) } +// Set 设置链表中index索引处的值为t func (l *LinkedList[T]) Set(index int, t T) error { - // TODO implement me - panic("implement me") + if index < 0 || index >= l.length { + return newErrIndexOutOfRange(l.length, index) + } + rv := l.getNode(index) + rv.val = t + return nil } // Delete 删除指定位置的元素 diff --git a/list/linked_list_test.go b/list/linked_list_test.go index b742086c..9b69198e 100644 --- a/list/linked_list_test.go +++ b/list/linked_list_test.go @@ -333,7 +333,79 @@ func TestLinkedList_Range(t *testing.T) { } func TestLinkedList_Set(t *testing.T) { - fmt.Println("仿照 ArrayList 的测试写代码") + testCases := []struct { + name string + list *LinkedList[int] + wantLinkedList *LinkedList[int] + index int + setVal int + wantErr error + }{ + { + name: "set num to index -1", + list: NewLinkedListOf[int]([]int{1, 2, 3}), + index: -1, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 3, -1), + }, + { + name: "set beyond length index 99", + list: NewLinkedListOf[int]([]int{1, 2, 3}), + index: 99, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 3, 99), + }, + { + name: "set empty node", + list: NewLinkedListOf[int]([]int{}), + index: 3, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 0, 3), + }, + { + name: "set num to index 3", + list: NewLinkedListOf[int]([]int{11, 22, 33, 44, 55}), + index: 2, + setVal: 999, + wantLinkedList: NewLinkedListOf([]int{11, 22, 999, 44, 55}), + }, + { + name: "set num to head", + list: NewLinkedListOf[int]([]int{11, 22, 33, 44, 55}), + index: 0, + setVal: -200, + wantLinkedList: NewLinkedListOf([]int{-200, 22, 33, 44, 55}), + }, + { + name: "set num to tail", + list: NewLinkedListOf[int]([]int{-11, 22, -33, 44, -55, 999, -888}), + index: 6, + setVal: 888, + wantLinkedList: NewLinkedListOf([]int{-11, 22, -33, 44, -55, 999, 888}), + }, + { + name: "index == len(*node)", + list: NewLinkedListOf[int]([]int{-11, 22, -33, 44, -55, 999, -888}), + index: 7, + setVal: 888, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 7, 7), + }, + { + name: "len(*node) == 0", + list: NewLinkedListOf[int]([]int{}), + index: 0, + setVal: 888, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 0, 0), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.list.Set(tc.index, tc.setVal) + if err != nil { + assert.Equal(t, tc.wantErr, err) + } else { + assert.Equal(t, tc.wantLinkedList, tc.list) + } + }) + } } func linkedListEqual[T comparable](l1 *LinkedList[T], l2 *LinkedList[T]) bool { From fadfdc64b22a0ae3f0630f2a85cc72ac19b1b481 Mon Sep 17 00:00:00 2001 From: Ming Deng Date: Tue, 23 Aug 2022 22:12:59 +0800 Subject: [PATCH 25/32] =?UTF-8?q?list:=20=E4=BF=AE=E5=A4=8D=20Delete=20?= =?UTF-8?q?=E6=96=B9=E6=B3=95=20(#44)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当 index == list.Len() 的时候会出现 panic --- list/linked_list.go | 5 +---- list/linked_list_test.go | 7 ++++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/list/linked_list.go b/list/linked_list.go index 12d2c350..a771fa7c 100644 --- a/list/linked_list.go +++ b/list/linked_list.go @@ -23,9 +23,6 @@ type LinkedList[T any] struct { } func (l *LinkedList[T]) getNode(index int) *node[T] { - if l.length == 0 { - return nil - } cur := l.head curIndex := 0 @@ -124,7 +121,7 @@ func (l *LinkedList[T]) Set(index int, t T) error { func (l *LinkedList[T]) Delete(index int) (T, error) { nLen := l.length var delVal T // 需要删除的节点val - if index < 0 || index > nLen || nLen == 0 { + if index < 0 || index >= nLen { return delVal, newErrIndexOutOfRange(nLen, index) } defer func() { diff --git a/list/linked_list_test.go b/list/linked_list_test.go index 9b69198e..d4c6c156 100644 --- a/list/linked_list_test.go +++ b/list/linked_list_test.go @@ -126,6 +126,12 @@ func TestLinkedList_Delete(t *testing.T) { index: 99, wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 3, 99), }, + { + name: "delete beyond length index 3", + list: NewLinkedListOf[int]([]int{1, 2, 3}), + index: 3, + wantErr: fmt.Errorf("ekit: 下标超出范围,长度 %d, 下标 %d", 3, 3), + }, { name: "delete empty node", list: NewLinkedListOf[int]([]int{}), @@ -268,7 +274,6 @@ func TestLinkedList_Cap(t *testing.T) { } func TestLinkedList_Get(t *testing.T) { - tests := []struct { name string list *LinkedList[int] From c012f04b154faee63fdd67facc32921e0f12fdd4 Mon Sep 17 00:00:00 2001 From: Mikasa <1342627550@qq.com> Date: Tue, 23 Aug 2022 23:33:10 +0800 Subject: [PATCH 26/32] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0LinkedList.Rang?= =?UTF-8?q?e=E6=96=B9=E6=B3=95=20(#46)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 实现LinkedList.Range方法 * 更新CHANGELOG. Signed-off-by: Mikasa <1342627550@qq.com> Signed-off-by: Mikasa <1342627550@qq.com> --- .CHANGELOG.md | 3 ++- list/linked_list.go | 11 ++++++++-- list/linked_list_test.go | 45 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/.CHANGELOG.md b/.CHANGELOG.md index ca81d264..f8460724 100644 --- a/.CHANGELOG.md +++ b/.CHANGELOG.md @@ -8,4 +8,5 @@ [ekit: 实现了 LinkedList Get](https://github.com/gotomicro/ekit/pull/31) [ekit: 实现了 LinkedList Append](https://github.com/gotomicro/ekit/pull/34) [ekit: 实现了 LinkedList Delete](https://github.com/gotomicro/ekit/pull/38) -[ekit: 修复 Pool TestPool](https://github.com/gotomicro/ekit/pull/40) \ No newline at end of file +[ekit: 修复 Pool TestPool](https://github.com/gotomicro/ekit/pull/40) +[ekit: 实现了 LinkedList Range](https://github.com/gotomicro/ekit/pull/46) diff --git a/list/linked_list.go b/list/linked_list.go index a771fa7c..7772e0ad 100644 --- a/list/linked_list.go +++ b/list/linked_list.go @@ -170,8 +170,15 @@ func (l *LinkedList[T]) Cap() int { } func (l *LinkedList[T]) Range(fn func(index int, t T) error) error { - // TODO implement me - panic("implement me") + head := l.head + for i := 0; i < l.length; i++ { + err := fn(i, head.val) + if err != nil { + return err + } + head = head.next + } + return nil } func (l *LinkedList[T]) AsSlice() []T { diff --git a/list/linked_list_test.go b/list/linked_list_test.go index d4c6c156..b93cd604 100644 --- a/list/linked_list_test.go +++ b/list/linked_list_test.go @@ -15,6 +15,7 @@ package list import ( + "errors" "fmt" "github.com/stretchr/testify/assert" @@ -334,7 +335,49 @@ func TestLinkedList_Len(t *testing.T) { } func TestLinkedList_Range(t *testing.T) { - fmt.Println("仿照 ArrayList 的测试写代码") + testCases := []struct { + name string + list *LinkedList[int] + wantVal int + wantErr error + }{ + { + name: "计算全部元素的和", + list: NewLinkedListOf([]int{1, 2, 3, 4, 5}), + wantVal: 15, + wantErr: nil, + }, + { + name: "测试中断", + list: NewLinkedListOf([]int{1, 2, 3, 4, -5, 6, 7, 8, -9, 10}), + wantErr: errors.New("index 4 is error"), + }, + { + name: "测试数组为nil", + list: NewLinkedListOf([]int{}), + wantVal: 0, + wantErr: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := 0 + err := tc.list.Range(func(index int, num int) error { + if num < 0 { + return fmt.Errorf("index %d is error", index) + } + result += num + return nil + }) + + assert.Equal(t, tc.wantErr, err) + if err != nil { + return + } + assert.Equal(t, tc.wantVal, result) + }) + } } func TestLinkedList_Set(t *testing.T) { From a25fd2c82109d5a75bc66b7ea7957b1147094097 Mon Sep 17 00:00:00 2001 From: Ming Deng Date: Wed, 24 Aug 2022 19:55:34 +0800 Subject: [PATCH 27/32] =?UTF-8?q?pool:=20=E5=AE=9A=E4=B9=89=20TaskPool=20(?= =?UTF-8?q?#48)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pool/{pool.go => sync_pool.go} | 0 pool/{pool_test.go => sync_pool_test.go} | 0 pool/task_pool.go | 99 ++++++++++++++++++++++++ 3 files changed, 99 insertions(+) rename pool/{pool.go => sync_pool.go} (100%) rename pool/{pool_test.go => sync_pool_test.go} (100%) create mode 100644 pool/task_pool.go diff --git a/pool/pool.go b/pool/sync_pool.go similarity index 100% rename from pool/pool.go rename to pool/sync_pool.go diff --git a/pool/pool_test.go b/pool/sync_pool_test.go similarity index 100% rename from pool/pool_test.go rename to pool/sync_pool_test.go diff --git a/pool/task_pool.go b/pool/task_pool.go new file mode 100644 index 00000000..94b7b86a --- /dev/null +++ b/pool/task_pool.go @@ -0,0 +1,99 @@ +// 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 pool + +import "context" + +// TaskPool 任务池 +type TaskPool interface { + // Submit 执行一个任务 + // 如果任务池提供了阻塞的功能,那么如果在 ctx 过期都没有提交成功,那么应该返回错误 + // 调用 Start 之后能否继续提交任务,则取决于具体的实现 + // 调用 Shutdown 或者 ShutdownNow 之后提交任务都会返回错误 + Submit(ctx context.Context, task func()) error + + // Start 开始调度任务执行。在调用 Start 之前,所有的任务都不会被调度执行。 + // Start 之后,能否继续调用 Submit 提交任务,取决于具体的实现 + Start() error + + // Shutdown 关闭任务池。如果此时尚未调用 Start 方法,那么将会立刻返回。 + // 任务池将会停止接收新的任务,但是会继续执行剩下的任务, + // 在所有任务执行完毕之后,用户可以从返回的 chan 中得到通知 + // 任务池在发出通知之后会关闭 chan struct{} + Shutdown() (<-chan struct{}, error) + + // ShutdownNow 立刻关闭线程池 + // 任务池能否中断当前正在执行的任务,取决于 TaskPool 的具体实现,以及 Task 的具体实现 + // 该方法会返回所有剩下的任务,剩下的任务是否包含正在执行的任务,也取决于具体的实现 + ShutdownNow() ([]Task, error) +} + +// Task 代表一个任务 +type Task interface { + // Run 执行任务 + // 如果 ctx 设置了超时时间,那么实现者需要自己决定是否进行超时控制 + Run(ctx context.Context) error +} + +// TaskFunc 一个可执行的任务 +type TaskFunc func(ctx context.Context) error + +// Run 执行任务 +// 超时控制取决于衍生出 TaskFunc 的方法 +func (t TaskFunc) Run(ctx context.Context) error { + return t(ctx) +} + +// BlockQueueTaskPool 并发阻塞的任务池 +type BlockQueueTaskPool struct { +} + +// NewBlockQueueTaskPool 创建一个新的 BlockQueueTaskPool +// concurrency 是并发数,即最多允许多少个 goroutine 执行任务 +// queueSize 是队列大小,即最多有多少个任务在等待调度 +func NewBlockQueueTaskPool(concurrency int, queueSize int) (*BlockQueueTaskPool, error) { + return &BlockQueueTaskPool{}, nil +} + +// Submit 提交一个任务 +// 如果此时队列已满,那么将会阻塞调用者。 +// 如果因为 ctx 的原因返回,那么将会返回 ctx.Err() +// 在调用 Start 前后都可以调用 Submit +func (b *BlockQueueTaskPool) Submit(ctx context.Context, task func()) error { + //TODO implement me + panic("implement me") +} + +// Start 开始调度任务执行 +// Start 之后,调用者可以继续使用 Submit 提交任务 +func (b *BlockQueueTaskPool) Start() error { + //TODO implement me + panic("implement me") +} + +// Shutdown 将会拒绝提交新的任务,但是会继续执行已提交任务 +// 当执行完毕后,会往返回的 chan 中丢入信号 +// Shutdown 会负责关闭返回的 chan +// Shutdown 无法中断正在执行的任务 +func (b *BlockQueueTaskPool) Shutdown() (<-chan struct{}, error) { + //TODO implement me + panic("implement me") +} + +// ShutdownNow 立刻关闭任务池,并且返回所有剩余未执行的任务(不包含正在执行的任务) +func (b *BlockQueueTaskPool) ShutdownNow() ([]Task, error) { + //TODO implement me + panic("implement me") +} From c4c2f583c60ae2b7e02fc120ac7a168079e2482e Mon Sep 17 00:00:00 2001 From: Ming Deng Date: Wed, 24 Aug 2022 23:34:37 +0800 Subject: [PATCH 28/32] =?UTF-8?q?pool:=20=E6=9B=B4=E6=94=B9=20TaskPool=20?= =?UTF-8?q?=E4=B8=BA=E4=BD=BF=E7=94=A8=20Task=20=E4=BD=9C=E4=B8=BA?= =?UTF-8?q?=E5=8F=82=E6=95=B0=20(#49)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pool/task_pool.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pool/task_pool.go b/pool/task_pool.go index 94b7b86a..39d2a782 100644 --- a/pool/task_pool.go +++ b/pool/task_pool.go @@ -22,7 +22,7 @@ type TaskPool interface { // 如果任务池提供了阻塞的功能,那么如果在 ctx 过期都没有提交成功,那么应该返回错误 // 调用 Start 之后能否继续提交任务,则取决于具体的实现 // 调用 Shutdown 或者 ShutdownNow 之后提交任务都会返回错误 - Submit(ctx context.Context, task func()) error + Submit(ctx context.Context, task Task) error // Start 开始调度任务执行。在调用 Start 之前,所有的任务都不会被调度执行。 // Start 之后,能否继续调用 Submit 提交任务,取决于具体的实现 @@ -72,14 +72,14 @@ func NewBlockQueueTaskPool(concurrency int, queueSize int) (*BlockQueueTaskPool, // 如果因为 ctx 的原因返回,那么将会返回 ctx.Err() // 在调用 Start 前后都可以调用 Submit func (b *BlockQueueTaskPool) Submit(ctx context.Context, task func()) error { - //TODO implement me + // TODO implement me panic("implement me") } // Start 开始调度任务执行 // Start 之后,调用者可以继续使用 Submit 提交任务 func (b *BlockQueueTaskPool) Start() error { - //TODO implement me + // TODO implement me panic("implement me") } @@ -88,12 +88,12 @@ func (b *BlockQueueTaskPool) Start() error { // Shutdown 会负责关闭返回的 chan // Shutdown 无法中断正在执行的任务 func (b *BlockQueueTaskPool) Shutdown() (<-chan struct{}, error) { - //TODO implement me + // TODO implement me panic("implement me") } // ShutdownNow 立刻关闭任务池,并且返回所有剩余未执行的任务(不包含正在执行的任务) func (b *BlockQueueTaskPool) ShutdownNow() ([]Task, error) { - //TODO implement me + // TODO implement me panic("implement me") } From 5324a7e3249e4fa6725d77bb473c77b853d34402 Mon Sep 17 00:00:00 2001 From: Deng Ming Date: Thu, 25 Aug 2022 11:16:47 +0800 Subject: [PATCH 29/32] =?UTF-8?q?list:=20=E5=AE=9E=E7=8E=B0=E4=BA=86=20Lin?= =?UTF-8?q?kedList=20Len,=20Cap=20=E5=92=8C=20NewLinkedList?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .CHANGELOG.md | 26 +++++++++++++++----------- list/linked_list.go | 13 ++++++++----- list/linked_list_test.go | 11 +++++++---- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/.CHANGELOG.md b/.CHANGELOG.md index f8460724..4fb3b884 100644 --- a/.CHANGELOG.md +++ b/.CHANGELOG.md @@ -1,12 +1,16 @@ # 开发中 -[ekit: add ToPtr function](https://github.com/gotomicro/ekit/pull/6) -[sql: 支持 JsonColumn](https://github.com/gotomicro/ekit/pull/7) -[ekit: 支持 ArrayList Range()](https://github.com/gotomicro/ekit/pull/12) -[ekit: 支持 ArrayList Get()](https://github.com/gotomicro/ekit/pull/18) -[ekit: 支持 ArrayList Len()](https://github.com/gotomicro/ekit/pull/18) -[ekit: 实现了 LinkedList Add() NewLinkedListOf](https://github.com/gotomicro/ekit/pull/26) -[ekit: 实现了 LinkedList Get](https://github.com/gotomicro/ekit/pull/31) -[ekit: 实现了 LinkedList Append](https://github.com/gotomicro/ekit/pull/34) -[ekit: 实现了 LinkedList Delete](https://github.com/gotomicro/ekit/pull/38) -[ekit: 修复 Pool TestPool](https://github.com/gotomicro/ekit/pull/40) -[ekit: 实现了 LinkedList Range](https://github.com/gotomicro/ekit/pull/46) +- [ekit: add ToPtr function](https://github.com/gotomicro/ekit/pull/6) +- [sql: 支持 JsonColumn](https://github.com/gotomicro/ekit/pull/7) +- list: 支持 ArrayList: + - [Range()](https://github.com/gotomicro/ekit/pull/12) + - [Get()](https://github.com/gotomicro/ekit/pull/18) + - [Len()](https://github.com/gotomicro/ekit/pull/18) +- list: 支持 LinkedList: + - [Add() 和 NewLinkedListOf()](https://github.com/gotomicro/ekit/pull/26) + - [Get](https://github.com/gotomicro/ekit/pull/31) + - [Append](https://github.com/gotomicro/ekit/pull/34) + - [Delete](https://github.com/gotomicro/ekit/pull/38) + - [Len,Cap,NewLinkedList](https://github.com/gotomicro/ekit/pull/51) + - [Range](https://github.com/gotomicro/ekit/pull/46) +- [pool: 修复 Pool TestPool 测试不稳定的问题](https://github.com/gotomicro/ekit/pull/40) + diff --git a/list/linked_list.go b/list/linked_list.go index 7772e0ad..78fdc96d 100644 --- a/list/linked_list.go +++ b/list/linked_list.go @@ -160,13 +160,11 @@ func (l *LinkedList[T]) Delete(index int) (T, error) { } func (l *LinkedList[T]) Len() int { - // TODO implement me - panic("implement me") + return l.length } func (l *LinkedList[T]) Cap() int { - // TODO implement me - panic("implement me") + return l.Len() } func (l *LinkedList[T]) Range(fn func(index int, t T) error) error { @@ -202,7 +200,12 @@ func (n *node[T]) insertAfter(newNode *node[T]) { newNode.prev = n } -// NewLinkedListOf 将切片转换为链表, 数组的值是浅拷贝. +// NewLinkedList 创建一个空链表 +func NewLinkedList[T any]() *LinkedList[T] { + return &LinkedList[T]{} +} + +// NewLinkedListOf 将切片转换为链表, 直接使用了切片元素的值,而没有进行复制 func NewLinkedListOf[T any](ts []T) *LinkedList[T] { var head *node[T] = nil var tail *node[T] = nil diff --git a/list/linked_list_test.go b/list/linked_list_test.go index b93cd604..5c042d95 100644 --- a/list/linked_list_test.go +++ b/list/linked_list_test.go @@ -24,7 +24,6 @@ import ( ) func TestLinkedList_Add(t *testing.T) { - testCases := []struct { name string list *LinkedList[int] @@ -271,7 +270,13 @@ func TestLinkedList_AsSlice(t *testing.T) { } func TestLinkedList_Cap(t *testing.T) { - fmt.Println("仿照 ArrayList 的测试写代码") + list := NewLinkedList[int]() + assert.Equal(t, 0, list.Cap()) + err := list.Append(12) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 1, list.Cap()) } func TestLinkedList_Get(t *testing.T) { @@ -458,7 +463,6 @@ func TestLinkedList_Set(t *testing.T) { func linkedListEqual[T comparable](l1 *LinkedList[T], l2 *LinkedList[T]) bool { if l1.length != l2.length { - fmt.Println(l1.length, l2.length) return false } @@ -470,7 +474,6 @@ func linkedListEqual[T comparable](l1 *LinkedList[T], l2 *LinkedList[T]) bool { l2Pos := l2.head for l1Pos != nil && l2Pos != nil { if l1Pos.val != l2Pos.val { - fmt.Println(l1Pos.val, l2Pos.val) return false } l1Pos = l1Pos.next From d41022f43c8f6727ea01f9d8d2fc143259793f30 Mon Sep 17 00:00:00 2001 From: Deng Ming Date: Thu, 25 Aug 2022 20:12:10 +0800 Subject: [PATCH 30/32] =?UTF-8?q?sqlx:=20=E5=8A=A0=E5=AF=86=E5=88=97?= =?UTF-8?q?=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sqlx/encrypt.go | 45 ++++++++++++++++++++++++++++++++++++++ {sql => sqlx}/json.go | 2 +- {sql => sqlx}/json_test.go | 2 +- 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 sqlx/encrypt.go rename {sql => sqlx}/json.go (99%) rename {sql => sqlx}/json_test.go (99%) diff --git a/sqlx/encrypt.go b/sqlx/encrypt.go new file mode 100644 index 00000000..f1b2fa6a --- /dev/null +++ b/sqlx/encrypt.go @@ -0,0 +1,45 @@ +// 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 sqlx + +import ( + "database/sql/driver" +) + +// EncryptColumn 代表一个加密的列 +// 一般来说加密可以选择依赖于数据库进行加密 +// EncryptColumn 并不打算使用极其难破解的加密算法 +// 而是选择使用 AES GCM 模式。 +// 如果你觉得安全性不够,那么你可以考虑自己实现类似的结构体. +type EncryptColumn[T any] struct { + Val T + // Valid 为 true 的时候,Val 才有意义 + Valid bool +} + +// Value 返回加密后的值 +// 如果 T 是基本类型,那么会对 T 进行直接加密 +// 否则,将 T 按照 JSON 序列化之后进行加密,返回加密后的数据 +func (e EncryptColumn[T]) Value() (driver.Value, error) { + //TODO implement me + panic("implement me") +} + +// Scan 方法会把写入的数据转化进行解密, +// 并将解密后的数据进行反序列化,构造 T +func (e *EncryptColumn[T]) Scan(src any) error { + //TODO implement me + panic("implement me") +} diff --git a/sql/json.go b/sqlx/json.go similarity index 99% rename from sql/json.go rename to sqlx/json.go index 0838e34e..dc628576 100644 --- a/sql/json.go +++ b/sqlx/json.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package sql +package sqlx import ( "database/sql" diff --git a/sql/json_test.go b/sqlx/json_test.go similarity index 99% rename from sql/json_test.go rename to sqlx/json_test.go index 6de12278..6aff5448 100644 --- a/sql/json_test.go +++ b/sqlx/json_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package sql +package sqlx import ( "database/sql" From 0a304570c6139539a7111d108103dc86f506c73a Mon Sep 17 00:00:00 2001 From: Deng Ming Date: Fri, 26 Aug 2022 12:08:31 +0800 Subject: [PATCH 31/32] =?UTF-8?q?ekit:=20=E5=BC=95=E5=85=A5=20golintci=20?= =?UTF-8?q?=E5=92=8C=20goimports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/linters/.golangci.yml | 58 ---------------------------- .github/pre-commit | 31 +++++++++++++++ .github/workflows/go-fmt.yml | 42 ++++++++++++++++++++ .github/workflows/golangci-lint.yml | 60 +++++++++++++++++++++++++++++ .golangci.yml | 4 ++ Makefile | 23 ++++++++++- bean/copier/reflect_copier.go | 2 +- bean/copier/reflect_copier_test.go | 3 +- list/linked_list_test.go | 1 + pool/sync_pool_test.go | 3 +- ptr_test.go | 3 +- script/goimports.sh | 15 ++++++++ script/setup.sh | 29 +++++++++----- sqlx/json_test.go | 3 +- 14 files changed, 204 insertions(+), 73 deletions(-) delete mode 100644 .github/linters/.golangci.yml create mode 100644 .github/pre-commit create mode 100644 .github/workflows/go-fmt.yml create mode 100644 .github/workflows/golangci-lint.yml create mode 100644 .golangci.yml create mode 100755 script/goimports.sh mode change 100644 => 100755 script/setup.sh diff --git a/.github/linters/.golangci.yml b/.github/linters/.golangci.yml deleted file mode 100644 index df364cb5..00000000 --- a/.github/linters/.golangci.yml +++ /dev/null @@ -1,58 +0,0 @@ -# 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. - -run: - timeout: 5m - skip-files: - - generated.* - - gen.* - -issues: - new: true - -linters: - enable: - - asciicheck - - bodyclose - - deadcode - - depguard - - gci - - gocritic - - gofmt - - gofumpt - - goimports - - goprintffuncname - - gosimple - - govet - - ineffassign - - misspell - - nilerr - - rowserrcheck - - staticcheck - - structcheck - - stylecheck - - typecheck - - unconvert - - unused - - unparam - - varcheck - - whitespace - - errcheck -# disable: - -linters-settings: - gci: - local-prefixes: github.com/gotomicro/ekit - goimports: - local-prefixes: github.com/gotomicro/ekit diff --git a/.github/pre-commit b/.github/pre-commit new file mode 100644 index 00000000..58da43c0 --- /dev/null +++ b/.github/pre-commit @@ -0,0 +1,31 @@ +#!/bin/sh +# 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. +# +# To use, store as .git/hooks/pre-commit inside your repository and make sure +# it has execute permissions. +# +# This script does not handle file names that contain spaces. + +# Pre-commit configuration + +RESULT=$(make check) +printf "执行检查中...\n" + +if [ -n "$RESULT" ]; then + echo >&2 "[ERROR]: 有文件发生变更,请将变更文件添加到本次提交中" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/.github/workflows/go-fmt.yml b/.github/workflows/go-fmt.yml new file mode 100644 index 00000000..09fe8c5f --- /dev/null +++ b/.github/workflows/go-fmt.yml @@ -0,0 +1,42 @@ +# 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. + +name: Format Go code + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: ">=1.18.0" + + - name: Install goimports + run: go install golang.org/x/tools/cmd/goimports@latest + + - name: Check + run: | + make check + if [ -n "$(git status --porcelain)" ]; then + echo >&2 "错误: 请在本地运行命令'make check'后再提交." + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 00000000..1431ee6d --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,60 @@ +# 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. + +name: golangci-lint +on: + push: + tags: + - v* + branches: + - master + - main + pull_request: +permissions: + contents: read + # Optional: allow read access to pull request. Use with `only-new-issues` option. + pull-requests: read +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v3 + with: + go-version: 1.18 + - uses: actions/checkout@v3 + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version + version: latest + + # Optional: working directory, useful for monorepos + # working-directory: somedir + + # Optional: golangci-lint command line arguments. + args: -c .golangci.yml + + # Optional: show only new issues if it's a pull request. The default value is `false`. + only-new-issues: true + + # Optional: if set to true then the all caching functionality will be complete disabled, + # takes precedence over all other caching options. + # skip-cache: true + + # Optional: if set to true then the action don't cache or restore ~/go/pkg. + # skip-pkg-cache: true + + # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. + # skip-build-cache: true \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..9f31ff72 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,4 @@ +run: + go: '1.18' + skip-dirs: + - .idea \ No newline at end of file diff --git a/Makefile b/Makefile index 8e3cfff7..9a2a2b3a 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,29 @@ +.PHONY: bench bench: @go test -bench=. -benchmem ./... +.PHONY: ut ut: @go test -race ./... +.PHONY: setup setup: - sh ./script/setup.sh \ No newline at end of file + @sh ./script/setup.sh + +.PHONY: fmt +fmt: + @sh ./script/goimports.sh + +.PHONY: lint +lint: + @golangci-lint run -c .golangci.yml + +.PHONY: tidy +tidy: + @go mod tidy -v + +.PHONY: check +check: + @$(MAKE) fmt + @$(MAKE) tidy + @$(MAKE) lint \ No newline at end of file diff --git a/bean/copier/reflect_copier.go b/bean/copier/reflect_copier.go index a90ebd6c..f86a0358 100644 --- a/bean/copier/reflect_copier.go +++ b/bean/copier/reflect_copier.go @@ -20,7 +20,7 @@ type ReflectCopier[Src any, Dst any] struct { // fieldMap Src 中字段下标到 Dst 字段下标的映射 // 其中 key 是 Src 中字段下标使用连字符 - 连接起来 // 不在 fieldMap 中字段则意味着被忽略 - fieldMap map[string][]int + //fieldMap map[string][]int } func NewReflectCopier[Src any, Dst any]() *ReflectCopier[Src, Dst] { diff --git a/bean/copier/reflect_copier_test.go b/bean/copier/reflect_copier_test.go index fda6ec78..9371c502 100644 --- a/bean/copier/reflect_copier_test.go +++ b/bean/copier/reflect_copier_test.go @@ -15,8 +15,9 @@ package copier import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestReflectCopier_Copy(t *testing.T) { diff --git a/list/linked_list_test.go b/list/linked_list_test.go index 5c042d95..aaca094c 100644 --- a/list/linked_list_test.go +++ b/list/linked_list_test.go @@ -17,6 +17,7 @@ package list import ( "errors" "fmt" + "github.com/stretchr/testify/assert" "math/rand" diff --git a/pool/sync_pool_test.go b/pool/sync_pool_test.go index 73972312..3a253094 100644 --- a/pool/sync_pool_test.go +++ b/pool/sync_pool_test.go @@ -16,9 +16,10 @@ package pool import ( "fmt" - "github.com/stretchr/testify/assert" "sync" "testing" + + "github.com/stretchr/testify/assert" ) func TestPool(t *testing.T) { diff --git a/ptr_test.go b/ptr_test.go index e848ccf8..4a992786 100644 --- a/ptr_test.go +++ b/ptr_test.go @@ -15,8 +15,9 @@ package ekit import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestToPtr(t *testing.T) { diff --git a/script/goimports.sh b/script/goimports.sh new file mode 100755 index 00000000..26c20726 --- /dev/null +++ b/script/goimports.sh @@ -0,0 +1,15 @@ +# 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. + +goimports -l -w $(find . -type f -name '*.go' -not -path "./.idea/*") \ No newline at end of file diff --git a/script/setup.sh b/script/setup.sh old mode 100644 new mode 100755 index 03701242..df30889d --- a/script/setup.sh +++ b/script/setup.sh @@ -12,18 +12,29 @@ # See the License for the specific language governing permissions and # limitations under the License. -SOURCE=.github/pre-push -TARGET=.git/hooks/pre-push +SOURCE_COMMIT=.github/pre-commit +TARGET_COMMIT=.git/hooks/pre-commit +SOURCE_PUSH=.github/pre-push +TARGET_PUSH=.git/hooks/pre-push + +# copy pre-commit file if not exist. +if [ ! -f $TARGET_COMMIT ]; then + echo "设置 git pre-commit hooks..." + cp $SOURCE_COMMIT $TARGET_COMMIT +fi # copy pre-push file if not exist. -if [ ! -f $TARGET ]; then - echo "设置 github hooks..." - cp $SOURCE $TARGET +if [ ! -f $TARGET_PUSH ]; then + echo "设置 git pre-push hooks..." + cp $SOURCE_PUSH $TARGET_PUSH fi -# add permission to TARGET file. -test -x $TARGET || chmod +x $TARGET +# add permission to TARGET_PUSH and TARGET_COMMIT file. +test -x $TARGET_PUSH || chmod +x $TARGET_PUSH +test -x $TARGET_COMMIT || chmod +x $TARGET_COMMIT echo "安装 golangci-lint..." -# install golangci-lint when golangci-lint not installed. -go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.42.1 \ No newline at end of file +go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + +echo "安装 goimports..." +go install golang.org/x/tools/cmd/goimports@latest \ No newline at end of file diff --git a/sqlx/json_test.go b/sqlx/json_test.go index 6aff5448..2fb6eaea 100644 --- a/sqlx/json_test.go +++ b/sqlx/json_test.go @@ -18,8 +18,9 @@ import ( "database/sql" "errors" "fmt" - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestJsonColumn_Value(t *testing.T) { From be2cd302607b18657f1c93a36fe325469ab4683d Mon Sep 17 00:00:00 2001 From: Deng Ming Date: Fri, 26 Aug 2022 12:21:38 +0800 Subject: [PATCH 32/32] =?UTF-8?q?ekit:=20=E5=BC=95=E5=85=A5=20golintci=20?= =?UTF-8?q?=E5=92=8C=20goimports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .CHANGELOG.md | 1 + .github/workflows/go-fmt.yml | 4 ++-- .github/workflows/golangci-lint.yml | 7 ++++--- .golangci.yml | 14 ++++++++++++++ Makefile | 3 +-- list/linked_list_test.go | 4 ---- 6 files changed, 22 insertions(+), 11 deletions(-) diff --git a/.CHANGELOG.md b/.CHANGELOG.md index 4fb3b884..c3e0d1b1 100644 --- a/.CHANGELOG.md +++ b/.CHANGELOG.md @@ -13,4 +13,5 @@ - [Len,Cap,NewLinkedList](https://github.com/gotomicro/ekit/pull/51) - [Range](https://github.com/gotomicro/ekit/pull/46) - [pool: 修复 Pool TestPool 测试不稳定的问题](https://github.com/gotomicro/ekit/pull/40) +- [ekit:引入 golangci-lint 和 goimports](https://github.com/gotomicro/ekit/pull/54) diff --git a/.github/workflows/go-fmt.yml b/.github/workflows/go-fmt.yml index 09fe8c5f..7003a135 100644 --- a/.github/workflows/go-fmt.yml +++ b/.github/workflows/go-fmt.yml @@ -16,9 +16,9 @@ name: Format Go code on: push: - branches: [ main ] + branches: [ main,dev ] pull_request: - branches: [ main ] + branches: [ main,dev ] jobs: build: diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 1431ee6d..8981b760 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -15,12 +15,13 @@ name: golangci-lint on: push: - tags: - - v* branches: - - master + - dev - main pull_request: + branches: + - dev + - main permissions: contents: read # Optional: allow read access to pull request. Use with `only-new-issues` option. diff --git a/.golangci.yml b/.golangci.yml index 9f31ff72..eb5a891f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,3 +1,17 @@ +# 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. + run: go: '1.18' skip-dirs: diff --git a/Makefile b/Makefile index 9a2a2b3a..3fe0863b 100644 --- a/Makefile +++ b/Makefile @@ -25,5 +25,4 @@ tidy: .PHONY: check check: @$(MAKE) fmt - @$(MAKE) tidy - @$(MAKE) lint \ No newline at end of file + @$(MAKE) tidy \ No newline at end of file diff --git a/list/linked_list_test.go b/list/linked_list_test.go index aaca094c..beac9b26 100644 --- a/list/linked_list_test.go +++ b/list/linked_list_test.go @@ -336,10 +336,6 @@ func TestLinkedList_Get(t *testing.T) { } } -func TestLinkedList_Len(t *testing.T) { - fmt.Println("仿照 ArrayList 的测试写代码") -} - func TestLinkedList_Range(t *testing.T) { testCases := []struct { name string