Compare commits

...

19 Commits

Author SHA1 Message Date
4e3a426c47 Merge branch 'master' into copilot/fix-4382 2025-11-21 17:38:07 +08:00
99d69857fa refactor(database/gdb): add quote for FieldsPrefix (#4485)
Code example:
``` go
	var res *BasicInfo
	err := g.DB().Model("basic_info").
		FieldsPrefix("basic_info", basicInfoColumns).
		Where("id", 35813305356386305).Scan(&res)
	if err != nil {
		panic(err)
	}
	g.Dump(res)
```

SQL generated before modification :
``` sql
SELECT basic_info.id,basic_info.full_name,basic_info.contact FROM `basic_info` WHERE (`id`=35813305356386305) AND `delete_time` IS NULL LIMIT 1
```

SQL generated after modification:
``` sql
SELECT `basic_info`.`id`,`basic_info`.`full_name`,`basic_info`.`contact` FROM `basic_info` WHERE (`id`=35813305356386305) AND `delete_time` IS NULL LIMIT 1
```

---------

Co-authored-by: hailaz <739476267@qq.com>
2025-11-21 17:27:09 +08:00
1b26013a66 fix: update copyright notice in multiple files to specify correct file reference (#4518)
修复注释
2025-11-21 14:12:56 +08:00
6c2155bd26 feat(container/glist): add generic list feature (#4483)
It is wrote with glist.List's and list.List's source codes and improve
to support T type.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: hailaz <739476267@qq.com>
2025-11-20 18:20:19 +08:00
9018a3d4ac feat(container/garray): enhance generic array implements (#4482)
Remove the t array of wrapper array. Now it's a real one. Other normal
array will base on it.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-19 18:11:04 +08:00
362d4202c4 fix(contrib/drivers/pgsql): Fixed the problem of overlapping fields in the same table name in pgsql multiple schema mode (#4375)
Co-authored-by: hailaz <739476267@qq.com>
2025-11-19 18:03:52 +08:00
a85b221d32 fix(contrib/config/apollo):where gcfg config apollo failed to retrieve configurations for multiple namespaces, where watch apollo change resulted in missing configurations. (#4509)
Fixed an issue where `gcfg config apollo` failed to retrieve
configurations for multiple namespaces; fixed an issue where `watch
apollo change` resulted in missing configurations.

---------

Co-authored-by: DAWN <xiongchao@cdfsunrise.com>
Co-authored-by: hailaz <739476267@qq.com>
2025-11-19 16:18:55 +08:00
cb8594eb80 refactor(contrib/clickhouse): optimization clickhouse (#4499)
1. close stmt
2.  fix assert  *gtime.Time
2025-11-19 16:03:07 +08:00
ac88e640d1 fix(net/goai): swagger $ref replace (#4512)
修复swagger泛型导致的 []/三个特殊字符不支持

---------

Co-authored-by: hailaz <739476267@qq.com>
2025-11-19 16:00:39 +08:00
2d307c5dd1 feat(contrib/drivers/pgsql): add array type numeric[] and decimal[] converting to Go []float64 support #4457 (#4511)
Co-authored-by: hailaz <739476267@qq.com>
2025-11-19 14:35:30 +08:00
54453c8e8f fix(ci): add cache cleaning step to prevent 'no space left on device' errors (#4513)
修复ci runner免费的磁盘空间不足导致无法完成单元测试的问题
1. 移动example单测到ci sub中
2. 使用go clean -cache清理避免短期内再次出现空间不足的问题
2025-11-19 12:54:51 +08:00
Ray
072b962b81 fix(‎encoding/gjson): fix gjson data race (#4510) 2025-11-17 15:21:38 +08:00
a80f58b7f6 fix: update gf cli to v2.9.5 (#4507)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action

Co-authored-by: hailaz <hailaz@users.noreply.github.com>
2025-11-10 21:45:57 +08:00
ded6773042 Merge branch 'master' into copilot/fix-4382 2025-09-29 17:33:24 +08:00
99a7fe5dd1 Merge branch 'master' into copilot/fix-4382 2025-09-25 10:26:44 +08:00
1a8977157d Apply gci import order changes 2025-09-11 07:58:46 +00:00
bae001b83b Enhance MySQL key length compatibility and add comprehensive test for issue #4382
Co-authored-by: houseme <4829346+houseme@users.noreply.github.com>
2025-09-11 07:48:58 +00:00
507211ecdd Fix MySQL key length issue by adding ROW_FORMAT=DYNAMIC for utf8mb4 tables
Co-authored-by: houseme <4829346+houseme@users.noreply.github.com>
2025-09-11 07:45:14 +00:00
32a002fc51 Initial plan 2025-09-11 07:34:16 +00:00
86 changed files with 3977 additions and 1765 deletions

View File

@ -20,6 +20,13 @@ on:
- feature/**
- enhance/**
- fix/**
workflow_dispatch:
inputs:
debug:
type: boolean
description: 'Enable tmate Debug'
required: false
default: false
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
@ -207,6 +214,13 @@ jobs:
- name: Checkout Repository
uses: actions/checkout@v5
- name: Setup tmate Session
uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug }}
with:
detached: true
limit-access-to-actor: false
- name: Start Apollo Containers
run: docker compose -f ".github/workflows/apollo/docker-compose.yml" up -d --build

View File

@ -2,12 +2,6 @@
coverage=$1
# update code of submodules
git clone https://github.com/gogf/examples
# update go.mod in examples directory to replace github.com/gogf/gf packages with local directory
bash .github/workflows/scripts/replace_examples_gomod.sh
# find all path that contains go.mod.
for file in `find . -name go.mod`; do
dirpath=$(dirname $file)
@ -24,22 +18,18 @@ for file in `find . -name go.mod`; do
continue 1
fi
# Check if it's a contrib directory or examples directory
if [[ $dirpath =~ "/contrib/" ]] || [[ $dirpath =~ "/examples/" ]]; then
# examples directory was moved to sub ci procedure.
if [[ $dirpath =~ "/examples/" ]]; then
continue 1
fi
# Check if it's a contrib directory
if [[ $dirpath =~ "/contrib/" ]]; then
# Check if go version meets the requirement
if ! go version | grep -qE "go${LATEST_GO_VERSION}"; then
echo "ignore path $dirpath as go version is not ${LATEST_GO_VERSION}: $(go version)"
continue 1
fi
# If it's examples directory, only build without tests
if [[ $dirpath =~ "/examples/" ]]; then
echo "the examples directory only needs to be built, not unit tests and coverage tests."
cd $dirpath
go mod tidy
go build ./...
cd -
continue 1
fi
fi
if [[ $file =~ "/testdata/" ]]; then
@ -47,6 +37,11 @@ for file in `find . -name go.mod`; do
continue 1
fi
if [[ $dirpath = "." ]]; then
# No space left on device error sometimes occurs in CI pipelines, so clean the cache before tests.
go clean -cache
fi
cd $dirpath
go mod tidy
go build ./...

View File

@ -2,6 +2,12 @@
coverage=$1
# update code of submodules
git clone https://github.com/gogf/examples
# update go.mod in examples directory to replace github.com/gogf/gf packages with local directory
bash .github/workflows/scripts/replace_examples_gomod.sh
# Function to compare version numbers
version_compare() {
local ver1=$1
@ -35,7 +41,19 @@ for file in `find . -name go.mod`; do
dirpath=$(dirname $file)
echo "Processing: $dirpath"
# Only process kubecm directory, skip others
# Only process examples and kubecm directories
# Process examples directory (only build, no tests)
if [[ $dirpath =~ "/examples/" ]]; then
echo " the examples directory only needs to be built, not unit tests."
cd $dirpath
go mod tidy
go build ./...
cd -
continue 1
fi
# Process kubecm directory
if [ "kubecm" != $(basename $dirpath) ]; then
echo " Skipping: not kubecm directory"
continue

View File

@ -3,11 +3,13 @@
Thanks for taking the time to join our community and start contributing!
## With issues
- Use the search tool before opening a new issue.
- Please provide source code and commit sha if you found a bug.
- Review existing issues and provide feedback or react to them.
## With pull requests
- Open your pull request against `master`
- Your pull request should have no more than two commits, if not you should squash them.
- It should pass all tests in the available continuous integrations systems such as GitHub CI.

View File

@ -23,17 +23,16 @@
A powerful framework for faster, easier, and more efficient project development.
## Documentation
# Documentation
- GoFrame Official Site: [https://goframe.org](https://goframe.org)
- GoFrame Official Site(en): [https://goframe.org/en](https://goframe.org/en)
- GoFrame Mirror Site(中文): [https://goframe.org.cn](https://goframe.org.cn)
- GoFrame Mirror Site(github pages): [https://pages.goframe.org](https://pages.goframe.org)
- Official Site: [https://goframe.org](https://goframe.org)
- Official Site(en): [https://goframe.org/en](https://goframe.org/en)
- 国内镜像: [https://goframe.org.cn](https://goframe.org.cn)
- Mirror Site: [Github Pages](https://pages.goframe.org)
- Mirror Site: [Offline Docs](https://github.com/gogf/goframe.org-pdf?tab=readme-ov-file#%E6%9C%80%E6%96%B0%E7%89%88%E6%9C%AC)
- GoDoc API: [https://pkg.go.dev/github.com/gogf/gf/v2](https://pkg.go.dev/github.com/gogf/gf/v2)
# Contributors
## Contributors
💖 [Thanks to all the contributors who made GoFrame possible](https://github.com/gogf/gf/graphs/contributors) 💖
@ -41,6 +40,6 @@ A powerful framework for faster, easier, and more efficient project development.
<img src="https://goframe.org/img/contributors.svg?version=v2.9.5" alt="goframe contributors"/>
</a>
# License
## License
`GoFrame` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.

View File

@ -46,6 +46,20 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.9.5 h1:WTbuQvbtOSddi2GtiobM/FCRUr5god7yzlEQP7WGOM8=
github.com/gogf/gf/contrib/drivers/clickhouse/v2 v2.9.5/go.mod h1:/lnvHd9+8VmjDLdgIczzRTJA1tZYY5AA8bdcaexi/ao=
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.9.5 h1:hgkgzbi6j8tDf+UpjG01fLqnAchD3913RZ37ruY9TqE=
github.com/gogf/gf/contrib/drivers/mssql/v2 v2.9.5/go.mod h1:Sy0DQNJ/xEL4snJyWqcflmYGr2jE8Tn9mynxqbe2Dds=
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.5 h1:0+ZBYhi4sqwxXwL+hIBpp06a7G4m5nmjskQ3NNb8qYc=
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.5/go.mod h1:vyB7J/uJcLCrHD5lfFBzxhEEMkePIRzfhd33EcsuLa0=
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.9.5 h1:eCPD7oteF/gurCU5ZfAhFBU7E1vI85cnoHKRdckn9Vc=
github.com/gogf/gf/contrib/drivers/oracle/v2 v2.9.5/go.mod h1:e5sxdxw3OrAFB+4VdYt3Wbm0G7LP5wofNRKj3JHIots=
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.9.5 h1:FCgvTLBuhPX7HE6YyR/dbNASoiKv72jpVS5SqoYYJTw=
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.9.5/go.mod h1:/Lz1qzhYN3ogt5aMFoIfjp82SbeuzjpXCqQhFu4Z3MI=
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.5 h1:MooWqn5qLMfB105PlBvd2Z7J3KdVBsjYxULtb42u308=
github.com/gogf/gf/contrib/drivers/sqlite/v2 v2.9.5/go.mod h1:pr68Q85xhRnJ5PhyGte/LiFJ/m+998RPjW+Jn+w+xYo=
github.com/gogf/gf/v2 v2.9.5 h1:1scfOdHbMP854oQaiLejl+eL+c4xfuvtWmmZiDJxbKs=
github.com/gogf/gf/v2 v2.9.5/go.mod h1:VUb5eyJKpvW77O/dXsbbLNO/Kjrg0UycIiq0lRiBjjo=
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f h1:7xfXR/BhG3JDqO1s45n65Oyx9t4E/UqDOXep6jXdLCM=
github.com/gogf/selfupdate v0.0.0-20231215043001-5c48c528462f/go.mod h1:HnYoio6S7VaFJdryKcD/r9HgX+4QzYfr00XiXUo/xz0=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=

View File

@ -4,7 +4,7 @@ go 1.23.0
toolchain go1.24.6
require github.com/gogf/gf/v2 v2.9.4
require github.com/gogf/gf/v2 v2.9.5
require (
go.opentelemetry.io/otel v1.38.0 // indirect

View File

@ -24,8 +24,8 @@ github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtg
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
@ -38,8 +38,8 @@ github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjK
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
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/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
@ -52,8 +52,8 @@ go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=

View File

@ -7,28 +7,18 @@
package garray
import (
"bytes"
"fmt"
"math"
"sort"
"sync"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/deepcopy"
"github.com/gogf/gf/v2/internal/empty"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/rwmutex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/grand"
)
// Array is a golang array with rich features.
// It contains a concurrent-safe/unsafe switch, which should be set
// when its initialization and cannot be changed then.
type Array struct {
mu rwmutex.RWMutex
array []any
*TArray[any]
once sync.Once
}
// New creates and returns an empty array.
@ -48,8 +38,7 @@ func NewArray(safe ...bool) *Array {
// which is false in default.
func NewArraySize(size int, cap int, safe ...bool) *Array {
return &Array{
mu: rwmutex.Create(safe...),
array: make([]any, size, cap),
TArray: NewTArraySize[any](size, cap, safe...),
}
}
@ -85,8 +74,7 @@ func NewFromCopy(array []any, safe ...bool) *Array {
// which is false in default.
func NewArrayFrom(array []any, safe ...bool) *Array {
return &Array{
mu: rwmutex.Create(safe...),
array: array,
TArray: NewTArrayFrom(array, safe...),
}
}
@ -96,265 +84,149 @@ func NewArrayFrom(array []any, safe ...bool) *Array {
func NewArrayFromCopy(array []any, safe ...bool) *Array {
newArray := make([]any, len(array))
copy(newArray, array)
return &Array{
mu: rwmutex.Create(safe...),
array: newArray,
}
return NewArrayFrom(newArray, safe...)
}
// lazyInit lazily initializes the array.
func (a *Array) lazyInit() {
a.once.Do(func() {
if a.TArray == nil {
a.TArray = NewTArray[any](false)
}
})
}
// At returns the value by the specified index.
// If the given `index` is out of range of the array, it returns `nil`.
func (a *Array) At(index int) (value any) {
value, _ = a.Get(index)
return
a.lazyInit()
return a.TArray.At(index)
}
// Get returns the value by the specified index.
// If the given `index` is out of range of the array, the `found` is false.
func (a *Array) Get(index int) (value any, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
if index < 0 || index >= len(a.array) {
return nil, false
}
return a.array[index], true
a.lazyInit()
return a.TArray.Get(index)
}
// Set sets value to specified index.
func (a *Array) Set(index int, value any) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
a.array[index] = value
return nil
a.lazyInit()
return a.TArray.Set(index, value)
}
// SetArray sets the underlying slice array with the given `array`.
func (a *Array) SetArray(array []any) *Array {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
a.lazyInit()
a.TArray.SetArray(array)
return a
}
// Replace replaces the array items by given `array` from the beginning of array.
func (a *Array) Replace(array []any) *Array {
a.mu.Lock()
defer a.mu.Unlock()
max := len(array)
if max > len(a.array) {
max = len(a.array)
}
for i := 0; i < max; i++ {
a.array[i] = array[i]
}
a.lazyInit()
a.TArray.Replace(array)
return a
}
// Sum returns the sum of values in an array.
func (a *Array) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += gconv.Int(v)
}
return
a.lazyInit()
return a.TArray.Sum()
}
// SortFunc sorts the array by custom function `less`.
func (a *Array) SortFunc(less func(v1, v2 any) bool) *Array {
a.mu.Lock()
defer a.mu.Unlock()
sort.Slice(a.array, func(i, j int) bool {
return less(a.array[i], a.array[j])
})
a.lazyInit()
a.TArray.SortFunc(less)
return a
}
// InsertBefore inserts the `values` to the front of `index`.
func (a *Array) InsertBefore(index int, values ...any) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
rear := append([]any{}, a.array[index:]...)
a.array = append(a.array[0:index], values...)
a.array = append(a.array, rear...)
return nil
a.lazyInit()
return a.TArray.InsertBefore(index, values...)
}
// InsertAfter inserts the `values` to the back of `index`.
func (a *Array) InsertAfter(index int, values ...any) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
rear := append([]any{}, a.array[index+1:]...)
a.array = append(a.array[0:index+1], values...)
a.array = append(a.array, rear...)
return nil
a.lazyInit()
return a.TArray.InsertAfter(index, values...)
}
// Remove removes an item by index.
// If the given `index` is out of range of the array, the `found` is false.
func (a *Array) Remove(index int) (value any, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
return a.doRemoveWithoutLock(index)
}
// doRemoveWithoutLock removes an item by index without lock.
func (a *Array) doRemoveWithoutLock(index int) (value any, found bool) {
if index < 0 || index >= len(a.array) {
return nil, false
}
// Determine array boundaries when deleting to improve deletion efficiency.
if index == 0 {
value := a.array[0]
a.array = a.array[1:]
return value, true
} else if index == len(a.array)-1 {
value := a.array[index]
a.array = a.array[:index]
return value, true
}
// If it is a non-boundary delete,
// it will involve the creation of an array,
// then the deletion is less efficient.
value = a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
return value, true
a.lazyInit()
return a.TArray.Remove(index)
}
// RemoveValue removes an item by value.
// It returns true if value is found in the array, or else false if not found.
func (a *Array) RemoveValue(value any) bool {
a.mu.Lock()
defer a.mu.Unlock()
if i := a.doSearchWithoutLock(value); i != -1 {
a.doRemoveWithoutLock(i)
return true
}
return false
a.lazyInit()
return a.TArray.RemoveValue(value)
}
// RemoveValues removes multiple items by `values`.
func (a *Array) RemoveValues(values ...any) {
a.mu.Lock()
defer a.mu.Unlock()
for _, value := range values {
if i := a.doSearchWithoutLock(value); i != -1 {
a.doRemoveWithoutLock(i)
}
}
a.lazyInit()
a.TArray.RemoveValues(values...)
}
// PushLeft pushes one or multiple items to the beginning of array.
func (a *Array) PushLeft(value ...any) *Array {
a.mu.Lock()
a.array = append(value, a.array...)
a.mu.Unlock()
a.lazyInit()
a.TArray.PushLeft(value...)
return a
}
// PushRight pushes one or multiple items to the end of array.
// It equals to Append.
func (a *Array) PushRight(value ...any) *Array {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
a.lazyInit()
a.TArray.PushRight(value...)
return a
}
// PopRand randomly pops and return an item out of array.
// Note that if the array is empty, the `found` is false.
func (a *Array) PopRand() (value any, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
return a.doRemoveWithoutLock(grand.Intn(len(a.array)))
a.lazyInit()
return a.TArray.PopRand()
}
// PopRands randomly pops and returns `size` items out of array.
func (a *Array) PopRands(size int) []any {
a.mu.Lock()
defer a.mu.Unlock()
if size <= 0 || len(a.array) == 0 {
return nil
}
if size >= len(a.array) {
size = len(a.array)
}
array := make([]any, size)
for i := 0; i < size; i++ {
array[i], _ = a.doRemoveWithoutLock(grand.Intn(len(a.array)))
}
return array
a.lazyInit()
return a.TArray.PopRands(size)
}
// PopLeft pops and returns an item from the beginning of array.
// Note that if the array is empty, the `found` is false.
func (a *Array) PopLeft() (value any, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.array) == 0 {
return nil, false
}
value = a.array[0]
a.array = a.array[1:]
return value, true
a.lazyInit()
return a.TArray.PopLeft()
}
// PopRight pops and returns an item from the end of array.
// Note that if the array is empty, the `found` is false.
func (a *Array) PopRight() (value any, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
if index < 0 {
return nil, false
}
value = a.array[index]
a.array = a.array[:index]
return value, true
a.lazyInit()
return a.TArray.PopRight()
}
// PopLefts pops and returns `size` items from the beginning of array.
func (a *Array) PopLefts(size int) []any {
a.mu.Lock()
defer a.mu.Unlock()
if size <= 0 || len(a.array) == 0 {
return nil
}
if size >= len(a.array) {
array := a.array
a.array = a.array[:0]
return array
}
value := a.array[0:size]
a.array = a.array[size:]
return value
a.lazyInit()
return a.TArray.PopLefts(size)
}
// PopRights pops and returns `size` items from the end of array.
func (a *Array) PopRights(size int) []any {
a.mu.Lock()
defer a.mu.Unlock()
if size <= 0 || len(a.array) == 0 {
return nil
}
index := len(a.array) - size
if index <= 0 {
array := a.array
a.array = a.array[:0]
return array
}
value := a.array[index:]
a.array = a.array[:index]
return value
a.lazyInit()
return a.TArray.PopRights(size)
}
// Range picks and returns items by range, like array[start:end].
@ -365,26 +237,8 @@ func (a *Array) PopRights(size int) []any {
// If `end` is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *Array) Range(start int, end ...int) []any {
a.mu.RLock()
defer a.mu.RUnlock()
offsetEnd := len(a.array)
if len(end) > 0 && end[0] < offsetEnd {
offsetEnd = end[0]
}
if start > offsetEnd {
return nil
}
if start < 0 {
start = 0
}
array := ([]any)(nil)
if a.mu.IsSafe() {
array = make([]any, offsetEnd-start)
copy(array, a.array[start:offsetEnd])
} else {
array = a.array[start:offsetEnd]
}
return array
a.lazyInit()
return a.TArray.Range(start, end...)
}
// SubSlice returns a slice of elements from the array as specified
@ -401,69 +255,29 @@ func (a *Array) Range(start int, end ...int) []any {
//
// Any possibility crossing the left border of array, it will fail.
func (a *Array) SubSlice(offset int, length ...int) []any {
a.mu.RLock()
defer a.mu.RUnlock()
size := len(a.array)
if len(length) > 0 {
size = length[0]
}
if offset > len(a.array) {
return nil
}
if offset < 0 {
offset = len(a.array) + offset
if offset < 0 {
return nil
}
}
if size < 0 {
offset += size
size = -size
if offset < 0 {
return nil
}
}
end := offset + size
if end > len(a.array) {
end = len(a.array)
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]any, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:end]
}
a.lazyInit()
return a.TArray.SubSlice(offset, length...)
}
// Append is alias of PushRight, please See PushRight.
func (a *Array) Append(value ...any) *Array {
a.PushRight(value...)
a.lazyInit()
a.TArray.Append(value...)
return a
}
// Len returns the length of array.
func (a *Array) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
a.lazyInit()
return a.TArray.Len()
}
// Slice returns the underlying data of array.
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
// or else a pointer to the underlying data.
func (a *Array) Slice() []any {
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array := make([]any, len(a.array))
copy(array, a.array)
return array
} else {
return a.array
}
a.lazyInit()
return a.TArray.Slice()
}
// Interfaces returns current array as []any.
@ -473,89 +287,49 @@ func (a *Array) Interfaces() []any {
// Clone returns a new array, which is a copy of current array.
func (a *Array) Clone() (newArray *Array) {
a.mu.RLock()
array := make([]any, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewArrayFrom(array, a.mu.IsSafe())
a.lazyInit()
return &Array{TArray: a.TArray.Clone()}
}
// Clear deletes all items of current array.
func (a *Array) Clear() *Array {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]any, 0)
}
a.mu.Unlock()
a.lazyInit()
a.TArray.Clear()
return a
}
// Contains checks whether a value exists in the array.
func (a *Array) Contains(value any) bool {
return a.Search(value) != -1
a.lazyInit()
return a.TArray.Contains(value)
}
// Search searches array by `value`, returns the index of `value`,
// or returns -1 if not exists.
func (a *Array) Search(value any) int {
a.mu.RLock()
defer a.mu.RUnlock()
return a.doSearchWithoutLock(value)
}
func (a *Array) doSearchWithoutLock(value any) int {
if len(a.array) == 0 {
return -1
}
result := -1
for index, v := range a.array {
if v == value {
result = index
break
}
}
return result
a.lazyInit()
return a.TArray.Search(value)
}
// Unique uniques the array, clear repeated items.
// Example: [1,1,2,3,2] -> [1,2,3]
func (a *Array) Unique() *Array {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.array) == 0 {
return a
}
var (
ok bool
temp any
uniqueSet = make(map[any]struct{})
uniqueArray = make([]any, 0, len(a.array))
)
for i := 0; i < len(a.array); i++ {
temp = a.array[i]
if _, ok = uniqueSet[temp]; ok {
continue
}
uniqueSet[temp] = struct{}{}
uniqueArray = append(uniqueArray, temp)
}
a.array = uniqueArray
a.lazyInit()
a.TArray.Unique()
return a
}
// LockFunc locks writing by callback function `f`.
func (a *Array) LockFunc(f func(array []any)) *Array {
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
a.lazyInit()
a.TArray.LockFunc(f)
return a
}
// RLockFunc locks reading by callback function `f`.
func (a *Array) RLockFunc(f func(array []any)) *Array {
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
a.lazyInit()
a.TArray.RLockFunc(f)
return a
}
@ -564,48 +338,23 @@ func (a *Array) RLockFunc(f func(array []any)) *Array {
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *Array) Merge(array any) *Array {
a.lazyInit()
return a.Append(gconv.Interfaces(array)...)
}
// Fill fills an array with num entries of the value `value`,
// keys starting at the `startIndex` parameter.
func (a *Array) Fill(startIndex int, num int, value any) error {
a.mu.Lock()
defer a.mu.Unlock()
if startIndex < 0 || startIndex > len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", startIndex, len(a.array))
}
for i := startIndex; i < startIndex+num; i++ {
if i > len(a.array)-1 {
a.array = append(a.array, value)
} else {
a.array[i] = value
}
}
return nil
a.lazyInit()
return a.TArray.Fill(startIndex, num, value)
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by `size`.
// The last chunk may contain less than size elements.
func (a *Array) Chunk(size int) [][]any {
if size < 1 {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n [][]any
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, a.array[i*size:end])
i++
}
return n
a.lazyInit()
return a.TArray.Chunk(size)
}
// Pad pads array to the specified length with `value`.
@ -613,98 +362,47 @@ func (a *Array) Chunk(size int) [][]any {
// If the absolute value of `size` is less than or equal to the length of the array
// then no padding takes place.
func (a *Array) Pad(size int, val any) *Array {
a.mu.Lock()
defer a.mu.Unlock()
if size == 0 || (size > 0 && size < len(a.array)) || (size < 0 && size > -len(a.array)) {
return a
}
n := size
if size < 0 {
n = -size
}
n -= len(a.array)
tmp := make([]any, n)
for i := 0; i < n; i++ {
tmp[i] = val
}
if size > 0 {
a.array = append(a.array, tmp...)
} else {
a.array = append(tmp, a.array...)
}
a.lazyInit()
a.TArray.Pad(size, val)
return a
}
// Rand randomly returns one item from array(no deleting).
func (a *Array) Rand() (value any, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return nil, false
}
return a.array[grand.Intn(len(a.array))], true
a.lazyInit()
return a.TArray.Rand()
}
// Rands randomly returns `size` items from array(no deleting).
func (a *Array) Rands(size int) []any {
a.mu.RLock()
defer a.mu.RUnlock()
if size <= 0 || len(a.array) == 0 {
return nil
}
array := make([]any, size)
for i := 0; i < size; i++ {
array[i] = a.array[grand.Intn(len(a.array))]
}
return array
a.lazyInit()
return a.TArray.Rands(size)
}
// Shuffle randomly shuffles the array.
func (a *Array) Shuffle() *Array {
a.mu.Lock()
defer a.mu.Unlock()
for i, v := range grand.Perm(len(a.array)) {
a.array[i], a.array[v] = a.array[v], a.array[i]
}
a.lazyInit()
a.TArray.Shuffle()
return a
}
// Reverse makes array with elements in reverse order.
func (a *Array) Reverse() *Array {
a.mu.Lock()
defer a.mu.Unlock()
for i, j := 0, len(a.array)-1; i < j; i, j = i+1, j-1 {
a.array[i], a.array[j] = a.array[j], a.array[i]
}
a.lazyInit()
a.TArray.Reverse()
return a
}
// Join joins array elements with a string `glue`.
func (a *Array) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return ""
}
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array)-1 {
buffer.WriteString(glue)
}
}
return buffer.String()
a.lazyInit()
return a.TArray.Join(glue)
}
// CountValues counts the number of occurrences of all values in the array.
func (a *Array) CountValues() map[any]int {
m := make(map[any]int)
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
m[v]++
}
return m
a.lazyInit()
return a.TArray.CountValues()
}
// Iterator is alias of IteratorAsc.
@ -715,25 +413,15 @@ func (a *Array) Iterator(f func(k int, v any) bool) {
// IteratorAsc iterates the array readonly in ascending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *Array) IteratorAsc(f func(k int, v any) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
for k, v := range a.array {
if !f(k, v) {
break
}
}
a.lazyInit()
a.TArray.IteratorAsc(f)
}
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *Array) IteratorDesc(f func(k int, v any) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
for i := len(a.array) - 1; i >= 0; i-- {
if !f(i, a.array[i]) {
break
}
}
a.lazyInit()
a.TArray.IteratorDesc(f)
}
// String returns current array as a string, which implements like json.Marshal does.
@ -741,118 +429,64 @@ func (a *Array) String() string {
if a == nil {
return ""
}
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
buffer.WriteByte('[')
s := ""
for k, v := range a.array {
s = gconv.String(v)
if gstr.IsNumeric(s) {
buffer.WriteString(s)
} else {
buffer.WriteString(`"` + gstr.QuoteMeta(s, `"\`) + `"`)
}
if k != len(a.array)-1 {
buffer.WriteByte(',')
}
}
buffer.WriteByte(']')
return buffer.String()
a.lazyInit()
return a.TArray.String()
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
// Note that do not use pointer as its receiver here.
func (a Array) MarshalJSON() ([]byte, error) {
a.mu.RLock()
defer a.mu.RUnlock()
return json.Marshal(a.array)
a.lazyInit()
return a.TArray.MarshalJSON()
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (a *Array) UnmarshalJSON(b []byte) error {
if a.array == nil {
a.array = make([]any, 0)
}
a.mu.Lock()
defer a.mu.Unlock()
if err := json.UnmarshalUseNumber(b, &a.array); err != nil {
return err
}
return nil
a.lazyInit()
return a.TArray.UnmarshalJSON(b)
}
// UnmarshalValue is an interface implement which sets any type of value for array.
func (a *Array) UnmarshalValue(value any) error {
a.mu.Lock()
defer a.mu.Unlock()
switch value.(type) {
case string, []byte:
return json.UnmarshalUseNumber(gconv.Bytes(value), &a.array)
default:
a.array = gconv.SliceAny(value)
}
return nil
a.lazyInit()
return a.TArray.UnmarshalValue(value)
}
// Filter iterates array and filters elements using custom callback function.
// It removes the element from array if callback function `filter` returns true,
// it or else does nothing and continues iterating.
func (a *Array) Filter(filter func(index int, value any) bool) *Array {
a.mu.Lock()
defer a.mu.Unlock()
for i := 0; i < len(a.array); {
if filter(i, a.array[i]) {
a.array = append(a.array[:i], a.array[i+1:]...)
} else {
i++
}
}
a.lazyInit()
a.TArray.Filter(filter)
return a
}
// FilterNil removes all nil value of the array.
func (a *Array) FilterNil() *Array {
a.mu.Lock()
defer a.mu.Unlock()
for i := 0; i < len(a.array); {
if empty.IsNil(a.array[i]) {
a.array = append(a.array[:i], a.array[i+1:]...)
} else {
i++
}
}
a.lazyInit()
a.TArray.FilterNil()
return a
}
// FilterEmpty removes all empty value of the array.
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
func (a *Array) FilterEmpty() *Array {
a.mu.Lock()
defer a.mu.Unlock()
for i := 0; i < len(a.array); {
if empty.IsEmpty(a.array[i]) {
a.array = append(a.array[:i], a.array[i+1:]...)
} else {
i++
}
}
a.lazyInit()
a.TArray.FilterEmpty()
return a
}
// Walk applies a user supplied function `f` to every item of array.
func (a *Array) Walk(f func(value any) any) *Array {
a.mu.Lock()
defer a.mu.Unlock()
for i, v := range a.array {
a.array[i] = f(v)
}
a.lazyInit()
a.TArray.Walk(f)
return a
}
// IsEmpty checks whether the array is empty.
func (a *Array) IsEmpty() bool {
return a.Len() == 0
a.lazyInit()
return a.TArray.IsEmpty()
}
// DeepCopy implements interface for deep copy of current type.
@ -860,11 +494,8 @@ func (a *Array) DeepCopy() any {
if a == nil {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
newSlice := make([]any, len(a.array))
for i, v := range a.array {
newSlice[i] = deepcopy.Copy(v)
a.lazyInit()
return &Array{
TArray: a.TArray.DeepCopy().(*TArray[any]),
}
return NewArrayFrom(newSlice, a.mu.IsSafe())
}

View File

@ -7,25 +7,19 @@
package garray
import (
"bytes"
"fmt"
"math"
"sort"
"sync"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/rwmutex"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/grand"
)
// IntArray is a golang int array with rich features.
// It contains a concurrent-safe/unsafe switch, which should be set
// when its initialization and cannot be changed then.
type IntArray struct {
mu rwmutex.RWMutex
array []int
*TArray[int]
once sync.Once
}
// NewIntArray creates and returns an empty array.
@ -40,8 +34,7 @@ func NewIntArray(safe ...bool) *IntArray {
// which is false in default.
func NewIntArraySize(size int, cap int, safe ...bool) *IntArray {
return &IntArray{
mu: rwmutex.Create(safe...),
array: make([]int, size, cap),
TArray: NewTArraySize[int](size, cap, safe...),
}
}
@ -65,8 +58,7 @@ func NewIntArrayRange(start, end, step int, safe ...bool) *IntArray {
// which is false in default.
func NewIntArrayFrom(array []int, safe ...bool) *IntArray {
return &IntArray{
mu: rwmutex.Create(safe...),
array: array,
TArray: NewTArrayFrom(array, safe...),
}
}
@ -76,78 +68,66 @@ func NewIntArrayFrom(array []int, safe ...bool) *IntArray {
func NewIntArrayFromCopy(array []int, safe ...bool) *IntArray {
newArray := make([]int, len(array))
copy(newArray, array)
return &IntArray{
mu: rwmutex.Create(safe...),
array: newArray,
}
return NewIntArrayFrom(newArray, safe...)
}
// lazyInit lazily initializes the array.
func (a *IntArray) lazyInit() {
a.once.Do(func() {
if a.TArray == nil {
a.TArray = NewTArray[int](false)
}
})
}
// At returns the value by the specified index.
// If the given `index` is out of range of the array, it returns `0`.
func (a *IntArray) At(index int) (value int) {
value, _ = a.Get(index)
return
a.lazyInit()
return a.TArray.At(index)
}
// Get returns the value by the specified index.
// If the given `index` is out of range of the array, the `found` is false.
func (a *IntArray) Get(index int) (value int, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
if index < 0 || index >= len(a.array) {
return 0, false
}
return a.array[index], true
a.lazyInit()
return a.TArray.Get(index)
}
// Set sets value to specified index.
func (a *IntArray) Set(index int, value int) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
a.array[index] = value
return nil
a.lazyInit()
return a.TArray.Set(index, value)
}
// SetArray sets the underlying slice array with the given `array`.
func (a *IntArray) SetArray(array []int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
a.lazyInit()
a.TArray.SetArray(array)
return a
}
// Replace replaces the array items by given `array` from the beginning of array.
func (a *IntArray) Replace(array []int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
max := len(array)
if max > len(a.array) {
max = len(a.array)
}
for i := 0; i < max; i++ {
a.array[i] = array[i]
}
a.lazyInit()
a.TArray.Replace(array)
return a
}
// Sum returns the sum of values in an array.
func (a *IntArray) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += v
}
return
a.lazyInit()
return a.TArray.Sum()
}
// Sort sorts the array in increasing order.
// The parameter `reverse` controls whether sort in increasing order(default) or decreasing order.
func (a *IntArray) Sort(reverse ...bool) *IntArray {
a.lazyInit()
a.mu.Lock()
defer a.mu.Unlock()
if len(reverse) > 0 && reverse[0] {
sort.Slice(a.array, func(i, j int) bool {
return a.array[i] >= a.array[j]
@ -160,210 +140,101 @@ func (a *IntArray) Sort(reverse ...bool) *IntArray {
// SortFunc sorts the array by custom function `less`.
func (a *IntArray) SortFunc(less func(v1, v2 int) bool) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Slice(a.array, func(i, j int) bool {
return less(a.array[i], a.array[j])
})
a.lazyInit()
a.TArray.SortFunc(less)
return a
}
// InsertBefore inserts the `values` to the front of `index`.
func (a *IntArray) InsertBefore(index int, values ...int) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
"index %d out of array range %d",
index, len(a.array),
)
}
rear := append([]int{}, a.array[index:]...)
a.array = append(a.array[0:index], values...)
a.array = append(a.array, rear...)
return nil
a.lazyInit()
return a.TArray.InsertBefore(index, values...)
}
// InsertAfter inserts the `value` to the back of `index`.
func (a *IntArray) InsertAfter(index int, values ...int) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
"index %d out of array range %d",
index, len(a.array),
)
}
rear := append([]int{}, a.array[index+1:]...)
a.array = append(a.array[0:index+1], values...)
a.array = append(a.array, rear...)
return nil
a.lazyInit()
return a.TArray.InsertAfter(index, values...)
}
// Remove removes an item by index.
// If the given `index` is out of range of the array, the `found` is false.
func (a *IntArray) Remove(index int) (value int, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
return a.doRemoveWithoutLock(index)
}
// doRemoveWithoutLock removes an item by index without lock.
func (a *IntArray) doRemoveWithoutLock(index int) (value int, found bool) {
if index < 0 || index >= len(a.array) {
return 0, false
}
// Determine array boundaries when deleting to improve deletion efficiency.
if index == 0 {
value := a.array[0]
a.array = a.array[1:]
return value, true
} else if index == len(a.array)-1 {
value := a.array[index]
a.array = a.array[:index]
return value, true
}
// If it is a non-boundary delete,
// it will involve the creation of an array,
// then the deletion is less efficient.
value = a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
return value, true
a.lazyInit()
return a.TArray.Remove(index)
}
// RemoveValue removes an item by value.
// It returns true if value is found in the array, or else false if not found.
func (a *IntArray) RemoveValue(value int) bool {
a.mu.Lock()
defer a.mu.Unlock()
if i := a.doSearchWithoutLock(value); i != -1 {
a.doRemoveWithoutLock(i)
return true
}
return false
a.lazyInit()
return a.TArray.RemoveValue(value)
}
// RemoveValues removes multiple items by `values`.
func (a *IntArray) RemoveValues(values ...int) {
a.mu.Lock()
defer a.mu.Unlock()
for _, value := range values {
if i := a.doSearchWithoutLock(value); i != -1 {
a.doRemoveWithoutLock(i)
}
}
a.lazyInit()
a.TArray.RemoveValues(values...)
}
// PushLeft pushes one or multiple items to the beginning of array.
func (a *IntArray) PushLeft(value ...int) *IntArray {
a.mu.Lock()
a.array = append(value, a.array...)
a.mu.Unlock()
a.lazyInit()
a.TArray.PushLeft(value...)
return a
}
// PushRight pushes one or multiple items to the end of array.
// It equals to Append.
func (a *IntArray) PushRight(value ...int) *IntArray {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
a.lazyInit()
a.TArray.PushRight(value...)
return a
}
// PopLeft pops and returns an item from the beginning of array.
// Note that if the array is empty, the `found` is false.
func (a *IntArray) PopLeft() (value int, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.array) == 0 {
return 0, false
}
value = a.array[0]
a.array = a.array[1:]
return value, true
a.lazyInit()
return a.TArray.PopLeft()
}
// PopRight pops and returns an item from the end of array.
// Note that if the array is empty, the `found` is false.
func (a *IntArray) PopRight() (value int, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
if index < 0 {
return 0, false
}
value = a.array[index]
a.array = a.array[:index]
return value, true
a.lazyInit()
return a.TArray.PopRight()
}
// PopRand randomly pops and return an item out of array.
// Note that if the array is empty, the `found` is false.
func (a *IntArray) PopRand() (value int, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
return a.doRemoveWithoutLock(grand.Intn(len(a.array)))
a.lazyInit()
return a.TArray.PopRand()
}
// PopRands randomly pops and returns `size` items out of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *IntArray) PopRands(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
if size <= 0 || len(a.array) == 0 {
return nil
}
if size >= len(a.array) {
size = len(a.array)
}
array := make([]int, size)
for i := 0; i < size; i++ {
array[i], _ = a.doRemoveWithoutLock(grand.Intn(len(a.array)))
}
return array
a.lazyInit()
return a.TArray.PopRands(size)
}
// PopLefts pops and returns `size` items from the beginning of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *IntArray) PopLefts(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
if size <= 0 || len(a.array) == 0 {
return nil
}
if size >= len(a.array) {
array := a.array
a.array = a.array[:0]
return array
}
value := a.array[0:size]
a.array = a.array[size:]
return value
a.lazyInit()
return a.TArray.PopLefts(size)
}
// PopRights pops and returns `size` items from the end of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *IntArray) PopRights(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
if size <= 0 || len(a.array) == 0 {
return nil
}
index := len(a.array) - size
if index <= 0 {
array := a.array
a.array = a.array[:0]
return array
}
value := a.array[index:]
a.array = a.array[:index]
return value
a.lazyInit()
return a.TArray.PopRights(size)
}
// Range picks and returns items by range, like array[start:end].
@ -374,26 +245,8 @@ func (a *IntArray) PopRights(size int) []int {
// If `end` is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *IntArray) Range(start int, end ...int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
offsetEnd := len(a.array)
if len(end) > 0 && end[0] < offsetEnd {
offsetEnd = end[0]
}
if start > offsetEnd {
return nil
}
if start < 0 {
start = 0
}
array := ([]int)(nil)
if a.mu.IsSafe() {
array = make([]int, offsetEnd-start)
copy(array, a.array[start:offsetEnd])
} else {
array = a.array[start:offsetEnd]
}
return array
a.lazyInit()
return a.TArray.Range(start, end...)
}
// SubSlice returns a slice of elements from the array as specified
@ -410,170 +263,84 @@ func (a *IntArray) Range(start int, end ...int) []int {
//
// Any possibility crossing the left border of array, it will fail.
func (a *IntArray) SubSlice(offset int, length ...int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
size := len(a.array)
if len(length) > 0 {
size = length[0]
}
if offset > len(a.array) {
return nil
}
if offset < 0 {
offset = len(a.array) + offset
if offset < 0 {
return nil
}
}
if size < 0 {
offset += size
size = -size
if offset < 0 {
return nil
}
}
end := offset + size
if end > len(a.array) {
end = len(a.array)
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]int, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:end]
}
a.lazyInit()
return a.TArray.SubSlice(offset, length...)
}
// Append is alias of PushRight,please See PushRight.
func (a *IntArray) Append(value ...int) *IntArray {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
a.lazyInit()
a.TArray.Append(value...)
return a
}
// Len returns the length of array.
func (a *IntArray) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
a.lazyInit()
return a.TArray.Len()
}
// Slice returns the underlying data of array.
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
// or else a pointer to the underlying data.
func (a *IntArray) Slice() []int {
array := ([]int)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]int, len(a.array))
copy(array, a.array)
} else {
array = a.array
}
return array
a.lazyInit()
return a.TArray.Slice()
}
// Interfaces returns current array as []any.
func (a *IntArray) Interfaces() []any {
a.mu.RLock()
defer a.mu.RUnlock()
array := make([]any, len(a.array))
for k, v := range a.array {
array[k] = v
}
return array
a.lazyInit()
return a.TArray.Interfaces()
}
// Clone returns a new array, which is a copy of current array.
func (a *IntArray) Clone() (newArray *IntArray) {
a.mu.RLock()
array := make([]int, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewIntArrayFrom(array, a.mu.IsSafe())
a.lazyInit()
return &IntArray{
TArray: a.TArray.Clone(),
}
}
// Clear deletes all items of current array.
func (a *IntArray) Clear() *IntArray {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]int, 0)
}
a.mu.Unlock()
a.lazyInit()
a.TArray.Clear()
return a
}
// Contains checks whether a value exists in the array.
func (a *IntArray) Contains(value int) bool {
return a.Search(value) != -1
a.lazyInit()
return a.TArray.Contains(value)
}
// Search searches array by `value`, returns the index of `value`,
// or returns -1 if not exists.
func (a *IntArray) Search(value int) int {
a.mu.RLock()
defer a.mu.RUnlock()
return a.doSearchWithoutLock(value)
}
func (a *IntArray) doSearchWithoutLock(value int) int {
if len(a.array) == 0 {
return -1
}
result := -1
for index, v := range a.array {
if v == value {
result = index
break
}
}
return result
a.lazyInit()
return a.TArray.Search(value)
}
// Unique uniques the array, clear repeated items.
// Example: [1,1,2,3,2] -> [1,2,3]
func (a *IntArray) Unique() *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.array) == 0 {
return a
}
var (
ok bool
temp int
uniqueSet = make(map[int]struct{})
uniqueArray = make([]int, 0, len(a.array))
)
for i := 0; i < len(a.array); i++ {
temp = a.array[i]
if _, ok = uniqueSet[temp]; ok {
continue
}
uniqueSet[temp] = struct{}{}
uniqueArray = append(uniqueArray, temp)
}
a.array = uniqueArray
a.lazyInit()
a.TArray.Unique()
return a
}
// LockFunc locks writing by callback function `f`.
func (a *IntArray) LockFunc(f func(array []int)) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
a.lazyInit()
a.TArray.LockFunc(f)
return a
}
// RLockFunc locks reading by callback function `f`.
func (a *IntArray) RLockFunc(f func(array []int)) *IntArray {
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
a.lazyInit()
a.TArray.RLockFunc(f)
return a
}
@ -588,46 +355,16 @@ func (a *IntArray) Merge(array any) *IntArray {
// Fill fills an array with num entries of the value `value`,
// keys starting at the `startIndex` parameter.
func (a *IntArray) Fill(startIndex int, num int, value int) error {
a.mu.Lock()
defer a.mu.Unlock()
if startIndex < 0 || startIndex > len(a.array) {
return gerror.NewCodef(
gcode.CodeInvalidParameter,
"index %d out of array range %d",
startIndex, len(a.array),
)
}
for i := startIndex; i < startIndex+num; i++ {
if i > len(a.array)-1 {
a.array = append(a.array, value)
} else {
a.array[i] = value
}
}
return nil
a.lazyInit()
return a.TArray.Fill(startIndex, num, value)
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by `size`.
// The last chunk may contain less than size elements.
func (a *IntArray) Chunk(size int) [][]int {
if size < 1 {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n [][]int
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, a.array[i*size:end])
i++
}
return n
a.lazyInit()
return a.TArray.Chunk(size)
}
// Pad pads array to the specified length with `value`.
@ -635,98 +372,47 @@ func (a *IntArray) Chunk(size int) [][]int {
// If the absolute value of `size` is less than or equal to the length of the array
// then no padding takes place.
func (a *IntArray) Pad(size int, value int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
if size == 0 || (size > 0 && size < len(a.array)) || (size < 0 && size > -len(a.array)) {
return a
}
n := size
if size < 0 {
n = -size
}
n -= len(a.array)
tmp := make([]int, n)
for i := 0; i < n; i++ {
tmp[i] = value
}
if size > 0 {
a.array = append(a.array, tmp...)
} else {
a.array = append(tmp, a.array...)
}
a.lazyInit()
a.TArray.Pad(size, value)
return a
}
// Rand randomly returns one item from array(no deleting).
func (a *IntArray) Rand() (value int, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return 0, false
}
return a.array[grand.Intn(len(a.array))], true
a.lazyInit()
return a.TArray.Rand()
}
// Rands randomly returns `size` items from array(no deleting).
func (a *IntArray) Rands(size int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
if size <= 0 || len(a.array) == 0 {
return nil
}
array := make([]int, size)
for i := 0; i < size; i++ {
array[i] = a.array[grand.Intn(len(a.array))]
}
return array
a.lazyInit()
return a.TArray.Rands(size)
}
// Shuffle randomly shuffles the array.
func (a *IntArray) Shuffle() *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
for i, v := range grand.Perm(len(a.array)) {
a.array[i], a.array[v] = a.array[v], a.array[i]
}
a.lazyInit()
a.TArray.Shuffle()
return a
}
// Reverse makes array with elements in reverse order.
func (a *IntArray) Reverse() *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
for i, j := 0, len(a.array)-1; i < j; i, j = i+1, j-1 {
a.array[i], a.array[j] = a.array[j], a.array[i]
}
a.lazyInit()
a.TArray.Reverse()
return a
}
// Join joins array elements with a string `glue`.
func (a *IntArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return ""
}
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array)-1 {
buffer.WriteString(glue)
}
}
return buffer.String()
a.lazyInit()
return a.TArray.Join(glue)
}
// CountValues counts the number of occurrences of all values in the array.
func (a *IntArray) CountValues() map[int]int {
m := make(map[int]int)
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
m[v]++
}
return m
a.lazyInit()
return a.TArray.CountValues()
}
// Iterator is alias of IteratorAsc.
@ -737,25 +423,15 @@ func (a *IntArray) Iterator(f func(k int, v int) bool) {
// IteratorAsc iterates the array readonly in ascending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *IntArray) IteratorAsc(f func(k int, v int) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
for k, v := range a.array {
if !f(k, v) {
break
}
}
a.lazyInit()
a.TArray.IteratorAsc(f)
}
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *IntArray) IteratorDesc(f func(k int, v int) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
for i := len(a.array) - 1; i >= 0; i-- {
if !f(i, a.array[i]) {
break
}
}
a.lazyInit()
a.TArray.IteratorDesc(f)
}
// String returns current array as a string, which implements like json.Marshal does.
@ -769,80 +445,49 @@ func (a *IntArray) String() string {
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
// Note that do not use pointer as its receiver here.
func (a IntArray) MarshalJSON() ([]byte, error) {
a.mu.RLock()
defer a.mu.RUnlock()
return json.Marshal(a.array)
a.lazyInit()
return a.TArray.MarshalJSON()
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (a *IntArray) UnmarshalJSON(b []byte) error {
if a.array == nil {
a.array = make([]int, 0)
}
a.mu.Lock()
defer a.mu.Unlock()
if err := json.UnmarshalUseNumber(b, &a.array); err != nil {
return err
}
return nil
a.lazyInit()
return a.TArray.UnmarshalJSON(b)
}
// UnmarshalValue is an interface implement which sets any type of value for array.
func (a *IntArray) UnmarshalValue(value any) error {
a.mu.Lock()
defer a.mu.Unlock()
switch value.(type) {
case string, []byte:
return json.UnmarshalUseNumber(gconv.Bytes(value), &a.array)
default:
a.array = gconv.SliceInt(value)
}
return nil
a.lazyInit()
return a.TArray.UnmarshalValue(value)
}
// Filter iterates array and filters elements using custom callback function.
// It removes the element from array if callback function `filter` returns true,
// it or else does nothing and continues iterating.
func (a *IntArray) Filter(filter func(index int, value int) bool) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
for i := 0; i < len(a.array); {
if filter(i, a.array[i]) {
a.array = append(a.array[:i], a.array[i+1:]...)
} else {
i++
}
}
a.lazyInit()
a.TArray.Filter(filter)
return a
}
// FilterEmpty removes all zero value of the array.
func (a *IntArray) FilterEmpty() *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
for i := 0; i < len(a.array); {
if a.array[i] == 0 {
a.array = append(a.array[:i], a.array[i+1:]...)
} else {
i++
}
}
a.lazyInit()
a.TArray.FilterEmpty()
return a
}
// Walk applies a user supplied function `f` to every item of array.
func (a *IntArray) Walk(f func(value int) int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
for i, v := range a.array {
a.array[i] = f(v)
}
a.lazyInit()
a.TArray.Walk(f)
return a
}
// IsEmpty checks whether the array is empty.
func (a *IntArray) IsEmpty() bool {
return a.Len() == 0
a.lazyInit()
return a.TArray.IsEmpty()
}
// DeepCopy implements interface for deep copy of current type.
@ -850,9 +495,8 @@ func (a *IntArray) DeepCopy() any {
if a == nil {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
newSlice := make([]int, len(a.array))
copy(newSlice, a.array)
return NewIntArrayFrom(newSlice, a.mu.IsSafe())
a.lazyInit()
return &IntArray{
TArray: a.TArray.DeepCopy().(*TArray[int]),
}
}

View File

@ -8,25 +8,20 @@ package garray
import (
"bytes"
"math"
"sort"
"strings"
"sync"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/rwmutex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/grand"
)
// StrArray is a golang string array with rich features.
// It contains a concurrent-safe/unsafe switch, which should be set
// when its initialization and cannot be changed then.
type StrArray struct {
mu rwmutex.RWMutex
array []string
*TArray[string]
once sync.Once
}
// NewStrArray creates and returns an empty array.
@ -41,8 +36,7 @@ func NewStrArray(safe ...bool) *StrArray {
// which is false in default.
func NewStrArraySize(size int, cap int, safe ...bool) *StrArray {
return &StrArray{
mu: rwmutex.Create(safe...),
array: make([]string, size, cap),
TArray: NewTArraySize[string](size, cap, safe...),
}
}
@ -51,8 +45,7 @@ func NewStrArraySize(size int, cap int, safe ...bool) *StrArray {
// which is false in default.
func NewStrArrayFrom(array []string, safe ...bool) *StrArray {
return &StrArray{
mu: rwmutex.Create(safe...),
array: array,
TArray: NewTArrayFrom(array, safe...),
}
}
@ -62,77 +55,64 @@ func NewStrArrayFrom(array []string, safe ...bool) *StrArray {
func NewStrArrayFromCopy(array []string, safe ...bool) *StrArray {
newArray := make([]string, len(array))
copy(newArray, array)
return &StrArray{
mu: rwmutex.Create(safe...),
array: newArray,
}
return NewStrArrayFrom(newArray, safe...)
}
// lazyInit lazily initializes the array.
func (a *StrArray) lazyInit() {
a.once.Do(func() {
if a.TArray == nil {
a.TArray = NewTArray[string](false)
}
})
}
// At returns the value by the specified index.
// If the given `index` is out of range of the array, it returns an empty string.
func (a *StrArray) At(index int) (value string) {
value, _ = a.Get(index)
return
a.lazyInit()
return a.TArray.At(index)
}
// Get returns the value by the specified index.
// If the given `index` is out of range of the array, the `found` is false.
func (a *StrArray) Get(index int) (value string, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
if index < 0 || index >= len(a.array) {
return "", false
}
return a.array[index], true
a.lazyInit()
return a.TArray.Get(index)
}
// Set sets value to specified index.
func (a *StrArray) Set(index int, value string) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
a.array[index] = value
return nil
a.lazyInit()
return a.TArray.Set(index, value)
}
// SetArray sets the underlying slice array with the given `array`.
func (a *StrArray) SetArray(array []string) *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
a.lazyInit()
a.TArray.SetArray(array)
return a
}
// Replace replaces the array items by given `array` from the beginning of array.
func (a *StrArray) Replace(array []string) *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
max := len(array)
if max > len(a.array) {
max = len(a.array)
}
for i := 0; i < max; i++ {
a.array[i] = array[i]
}
a.lazyInit()
a.TArray.Replace(array)
return a
}
// Sum returns the sum of values in an array.
func (a *StrArray) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += gconv.Int(v)
}
return
a.lazyInit()
return a.TArray.Sum()
}
// Sort sorts the array in increasing order.
// The parameter `reverse` controls whether sort
// in increasing order(default) or decreasing order
func (a *StrArray) Sort(reverse ...bool) *StrArray {
a.lazyInit()
a.mu.Lock()
defer a.mu.Unlock()
if len(reverse) > 0 && reverse[0] {
@ -147,200 +127,101 @@ func (a *StrArray) Sort(reverse ...bool) *StrArray {
// SortFunc sorts the array by custom function `less`.
func (a *StrArray) SortFunc(less func(v1, v2 string) bool) *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Slice(a.array, func(i, j int) bool {
return less(a.array[i], a.array[j])
})
a.lazyInit()
a.TArray.SortFunc(less)
return a
}
// InsertBefore inserts the `values` to the front of `index`.
func (a *StrArray) InsertBefore(index int, values ...string) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
rear := append([]string{}, a.array[index:]...)
a.array = append(a.array[0:index], values...)
a.array = append(a.array, rear...)
return nil
a.lazyInit()
return a.TArray.InsertBefore(index, values...)
}
// InsertAfter inserts the `values` to the back of `index`.
func (a *StrArray) InsertAfter(index int, values ...string) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
rear := append([]string{}, a.array[index+1:]...)
a.array = append(a.array[0:index+1], values...)
a.array = append(a.array, rear...)
return nil
a.lazyInit()
return a.TArray.InsertAfter(index, values...)
}
// Remove removes an item by index.
// If the given `index` is out of range of the array, the `found` is false.
func (a *StrArray) Remove(index int) (value string, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
return a.doRemoveWithoutLock(index)
}
// doRemoveWithoutLock removes an item by index without lock.
func (a *StrArray) doRemoveWithoutLock(index int) (value string, found bool) {
if index < 0 || index >= len(a.array) {
return "", false
}
// Determine array boundaries when deleting to improve deletion efficiency.
if index == 0 {
value := a.array[0]
a.array = a.array[1:]
return value, true
} else if index == len(a.array)-1 {
value := a.array[index]
a.array = a.array[:index]
return value, true
}
// If it is a non-boundary delete,
// it will involve the creation of an array,
// then the deletion is less efficient.
value = a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
return value, true
a.lazyInit()
return a.TArray.Remove(index)
}
// RemoveValue removes an item by value.
// It returns true if value is found in the array, or else false if not found.
func (a *StrArray) RemoveValue(value string) bool {
if i := a.Search(value); i != -1 {
_, found := a.Remove(i)
return found
}
return false
a.lazyInit()
return a.TArray.RemoveValue(value)
}
// RemoveValues removes multiple items by `values`.
func (a *StrArray) RemoveValues(values ...string) {
a.mu.Lock()
defer a.mu.Unlock()
for _, value := range values {
if i := a.doSearchWithoutLock(value); i != -1 {
a.doRemoveWithoutLock(i)
}
}
a.lazyInit()
a.TArray.RemoveValues(values...)
}
// PushLeft pushes one or multiple items to the beginning of array.
func (a *StrArray) PushLeft(value ...string) *StrArray {
a.mu.Lock()
a.array = append(value, a.array...)
a.mu.Unlock()
a.lazyInit()
a.TArray.PushLeft(value...)
return a
}
// PushRight pushes one or multiple items to the end of array.
// It equals to Append.
func (a *StrArray) PushRight(value ...string) *StrArray {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
a.lazyInit()
a.TArray.PushRight(value...)
return a
}
// PopLeft pops and returns an item from the beginning of array.
// Note that if the array is empty, the `found` is false.
func (a *StrArray) PopLeft() (value string, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.array) == 0 {
return "", false
}
value = a.array[0]
a.array = a.array[1:]
return value, true
a.lazyInit()
return a.TArray.PopLeft()
}
// PopRight pops and returns an item from the end of array.
// Note that if the array is empty, the `found` is false.
func (a *StrArray) PopRight() (value string, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
if index < 0 {
return "", false
}
value = a.array[index]
a.array = a.array[:index]
return value, true
a.lazyInit()
return a.TArray.PopRight()
}
// PopRand randomly pops and return an item out of array.
// Note that if the array is empty, the `found` is false.
func (a *StrArray) PopRand() (value string, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
return a.doRemoveWithoutLock(grand.Intn(len(a.array)))
a.lazyInit()
return a.TArray.PopRand()
}
// PopRands randomly pops and returns `size` items out of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *StrArray) PopRands(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
if size <= 0 || len(a.array) == 0 {
return nil
}
if size >= len(a.array) {
size = len(a.array)
}
array := make([]string, size)
for i := 0; i < size; i++ {
array[i], _ = a.doRemoveWithoutLock(grand.Intn(len(a.array)))
}
return array
a.lazyInit()
return a.TArray.PopRands(size)
}
// PopLefts pops and returns `size` items from the beginning of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *StrArray) PopLefts(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
if size <= 0 || len(a.array) == 0 {
return nil
}
if size >= len(a.array) {
array := a.array
a.array = a.array[:0]
return array
}
value := a.array[0:size]
a.array = a.array[size:]
return value
a.lazyInit()
return a.TArray.PopLefts(size)
}
// PopRights pops and returns `size` items from the end of array.
// If the given `size` is greater than size of the array, it returns all elements of the array.
// Note that if given `size` <= 0 or the array is empty, it returns nil.
func (a *StrArray) PopRights(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
if size <= 0 || len(a.array) == 0 {
return nil
}
index := len(a.array) - size
if index <= 0 {
array := a.array
a.array = a.array[:0]
return array
}
value := a.array[index:]
a.array = a.array[:index]
return value
a.lazyInit()
return a.TArray.PopRights(size)
}
// Range picks and returns items by range, like array[start:end].
@ -351,26 +232,8 @@ func (a *StrArray) PopRights(size int) []string {
// If `end` is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *StrArray) Range(start int, end ...int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
offsetEnd := len(a.array)
if len(end) > 0 && end[0] < offsetEnd {
offsetEnd = end[0]
}
if start > offsetEnd {
return nil
}
if start < 0 {
start = 0
}
array := ([]string)(nil)
if a.mu.IsSafe() {
array = make([]string, offsetEnd-start)
copy(array, a.array[start:offsetEnd])
} else {
array = a.array[start:offsetEnd]
}
return array
a.lazyInit()
return a.TArray.Range(start, end...)
}
// SubSlice returns a slice of elements from the array as specified
@ -387,111 +250,63 @@ func (a *StrArray) Range(start int, end ...int) []string {
//
// Any possibility crossing the left border of array, it will fail.
func (a *StrArray) SubSlice(offset int, length ...int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
size := len(a.array)
if len(length) > 0 {
size = length[0]
}
if offset > len(a.array) {
return nil
}
if offset < 0 {
offset = len(a.array) + offset
if offset < 0 {
return nil
}
}
if size < 0 {
offset += size
size = -size
if offset < 0 {
return nil
}
}
end := offset + size
if end > len(a.array) {
end = len(a.array)
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]string, size)
copy(s, a.array[offset:])
return s
}
return a.array[offset:end]
a.lazyInit()
return a.TArray.SubSlice(offset, length...)
}
// Append is alias of PushRight,please See PushRight.
func (a *StrArray) Append(value ...string) *StrArray {
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
a.lazyInit()
a.TArray.Append(value...)
return a
}
// Len returns the length of array.
func (a *StrArray) Len() int {
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
a.lazyInit()
return a.TArray.Len()
}
// Slice returns the underlying data of array.
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
// or else a pointer to the underlying data.
func (a *StrArray) Slice() []string {
array := ([]string)(nil)
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array = make([]string, len(a.array))
copy(array, a.array)
} else {
array = a.array
}
return array
a.lazyInit()
return a.TArray.Slice()
}
// Interfaces returns current array as []any.
func (a *StrArray) Interfaces() []any {
a.mu.RLock()
defer a.mu.RUnlock()
array := make([]any, len(a.array))
for k, v := range a.array {
array[k] = v
}
return array
a.lazyInit()
return a.TArray.Interfaces()
}
// Clone returns a new array, which is a copy of current array.
func (a *StrArray) Clone() (newArray *StrArray) {
a.mu.RLock()
array := make([]string, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewStrArrayFrom(array, a.mu.IsSafe())
a.lazyInit()
return &StrArray{
TArray: a.TArray.Clone(),
}
}
// Clear deletes all items of current array.
func (a *StrArray) Clear() *StrArray {
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]string, 0)
}
a.mu.Unlock()
a.lazyInit()
a.TArray.Clear()
return a
}
// Contains checks whether a value exists in the array.
func (a *StrArray) Contains(value string) bool {
return a.Search(value) != -1
a.lazyInit()
return a.TArray.Contains(value)
}
// ContainsI checks whether a value exists in the array with case-insensitively.
// Note that it internally iterates the whole array to do the comparison with case-insensitively.
func (a *StrArray) ContainsI(value string) bool {
a.lazyInit()
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
@ -508,64 +323,29 @@ func (a *StrArray) ContainsI(value string) bool {
// Search searches array by `value`, returns the index of `value`,
// or returns -1 if not exists.
func (a *StrArray) Search(value string) int {
a.mu.RLock()
defer a.mu.RUnlock()
return a.doSearchWithoutLock(value)
}
func (a *StrArray) doSearchWithoutLock(value string) int {
if len(a.array) == 0 {
return -1
}
result := -1
for index, v := range a.array {
if strings.Compare(v, value) == 0 {
result = index
break
}
}
return result
a.lazyInit()
return a.TArray.Search(value)
}
// Unique uniques the array, clear repeated items.
// Example: [1,1,2,3,2] -> [1,2,3]
func (a *StrArray) Unique() *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.array) == 0 {
return a
}
var (
ok bool
temp string
uniqueSet = make(map[string]struct{})
uniqueArray = make([]string, 0, len(a.array))
)
for i := 0; i < len(a.array); i++ {
temp = a.array[i]
if _, ok = uniqueSet[temp]; ok {
continue
}
uniqueSet[temp] = struct{}{}
uniqueArray = append(uniqueArray, temp)
}
a.array = uniqueArray
a.lazyInit()
a.TArray.Unique()
return a
}
// LockFunc locks writing by callback function `f`.
func (a *StrArray) LockFunc(f func(array []string)) *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
a.lazyInit()
a.TArray.LockFunc(f)
return a
}
// RLockFunc locks reading by callback function `f`.
func (a *StrArray) RLockFunc(f func(array []string)) *StrArray {
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
a.lazyInit()
a.TArray.RLockFunc(f)
return a
}
@ -580,42 +360,16 @@ func (a *StrArray) Merge(array any) *StrArray {
// Fill fills an array with num entries of the value `value`,
// keys starting at the `startIndex` parameter.
func (a *StrArray) Fill(startIndex int, num int, value string) error {
a.mu.Lock()
defer a.mu.Unlock()
if startIndex < 0 || startIndex > len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", startIndex, len(a.array))
}
for i := startIndex; i < startIndex+num; i++ {
if i > len(a.array)-1 {
a.array = append(a.array, value)
} else {
a.array[i] = value
}
}
return nil
a.lazyInit()
return a.TArray.Fill(startIndex, num, value)
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by `size`.
// The last chunk may contain less than size elements.
func (a *StrArray) Chunk(size int) [][]string {
if size < 1 {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n [][]string
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, a.array[i*size:end])
i++
}
return n
a.lazyInit()
return a.TArray.Chunk(size)
}
// Pad pads array to the specified length with `value`.
@ -623,98 +377,47 @@ func (a *StrArray) Chunk(size int) [][]string {
// If the absolute value of `size` is less than or equal to the length of the array
// then no padding takes place.
func (a *StrArray) Pad(size int, value string) *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
if size == 0 || (size > 0 && size < len(a.array)) || (size < 0 && size > -len(a.array)) {
return a
}
n := size
if size < 0 {
n = -size
}
n -= len(a.array)
tmp := make([]string, n)
for i := 0; i < n; i++ {
tmp[i] = value
}
if size > 0 {
a.array = append(a.array, tmp...)
} else {
a.array = append(tmp, a.array...)
}
a.lazyInit()
a.TArray.Pad(size, value)
return a
}
// Rand randomly returns one item from array(no deleting).
func (a *StrArray) Rand() (value string, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return "", false
}
return a.array[grand.Intn(len(a.array))], true
a.lazyInit()
return a.TArray.Rand()
}
// Rands randomly returns `size` items from array(no deleting).
func (a *StrArray) Rands(size int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
if size <= 0 || len(a.array) == 0 {
return nil
}
array := make([]string, size)
for i := 0; i < size; i++ {
array[i] = a.array[grand.Intn(len(a.array))]
}
return array
a.lazyInit()
return a.TArray.Rands(size)
}
// Shuffle randomly shuffles the array.
func (a *StrArray) Shuffle() *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
for i, v := range grand.Perm(len(a.array)) {
a.array[i], a.array[v] = a.array[v], a.array[i]
}
a.lazyInit()
a.TArray.Shuffle()
return a
}
// Reverse makes array with elements in reverse order.
func (a *StrArray) Reverse() *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
for i, j := 0, len(a.array)-1; i < j; i, j = i+1, j-1 {
a.array[i], a.array[j] = a.array[j], a.array[i]
}
a.lazyInit()
a.TArray.Reverse()
return a
}
// Join joins array elements with a string `glue`.
func (a *StrArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return ""
}
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(v)
if k != len(a.array)-1 {
buffer.WriteString(glue)
}
}
return buffer.String()
a.lazyInit()
return a.TArray.Join(glue)
}
// CountValues counts the number of occurrences of all values in the array.
func (a *StrArray) CountValues() map[string]int {
m := make(map[string]int)
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
m[v]++
}
return m
a.lazyInit()
return a.TArray.CountValues()
}
// Iterator is alias of IteratorAsc.
@ -725,25 +428,15 @@ func (a *StrArray) Iterator(f func(k int, v string) bool) {
// IteratorAsc iterates the array readonly in ascending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *StrArray) IteratorAsc(f func(k int, v string) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
for k, v := range a.array {
if !f(k, v) {
break
}
}
a.lazyInit()
a.TArray.IteratorAsc(f)
}
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *StrArray) IteratorDesc(f func(k int, v string) bool) {
a.mu.RLock()
defer a.mu.RUnlock()
for i := len(a.array) - 1; i >= 0; i-- {
if !f(i, a.array[i]) {
break
}
}
a.lazyInit()
a.TArray.IteratorDesc(f)
}
// String returns current array as a string, which implements like json.Marshal does.
@ -751,6 +444,9 @@ func (a *StrArray) String() string {
if a == nil {
return ""
}
a.lazyInit()
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
@ -768,80 +464,49 @@ func (a *StrArray) String() string {
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
// Note that do not use pointer as its receiver here.
func (a StrArray) MarshalJSON() ([]byte, error) {
a.mu.RLock()
defer a.mu.RUnlock()
return json.Marshal(a.array)
a.lazyInit()
return a.TArray.MarshalJSON()
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (a *StrArray) UnmarshalJSON(b []byte) error {
if a.array == nil {
a.array = make([]string, 0)
}
a.mu.Lock()
defer a.mu.Unlock()
if err := json.UnmarshalUseNumber(b, &a.array); err != nil {
return err
}
return nil
a.lazyInit()
return a.TArray.UnmarshalJSON(b)
}
// UnmarshalValue is an interface implement which sets any type of value for array.
func (a *StrArray) UnmarshalValue(value any) error {
a.mu.Lock()
defer a.mu.Unlock()
switch value.(type) {
case string, []byte:
return json.UnmarshalUseNumber(gconv.Bytes(value), &a.array)
default:
a.array = gconv.SliceStr(value)
}
return nil
a.lazyInit()
return a.TArray.UnmarshalValue(value)
}
// Filter iterates array and filters elements using custom callback function.
// It removes the element from array if callback function `filter` returns true,
// it or else does nothing and continues iterating.
func (a *StrArray) Filter(filter func(index int, value string) bool) *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
for i := 0; i < len(a.array); {
if filter(i, a.array[i]) {
a.array = append(a.array[:i], a.array[i+1:]...)
} else {
i++
}
}
a.lazyInit()
a.TArray.Filter(filter)
return a
}
// FilterEmpty removes all empty string value of the array.
func (a *StrArray) FilterEmpty() *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
for i := 0; i < len(a.array); {
if a.array[i] == "" {
a.array = append(a.array[:i], a.array[i+1:]...)
} else {
i++
}
}
a.lazyInit()
a.TArray.FilterEmpty()
return a
}
// Walk applies a user supplied function `f` to every item of array.
func (a *StrArray) Walk(f func(value string) string) *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
for i, v := range a.array {
a.array[i] = f(v)
}
a.lazyInit()
a.TArray.Walk(f)
return a
}
// IsEmpty checks whether the array is empty.
func (a *StrArray) IsEmpty() bool {
return a.Len() == 0
a.lazyInit()
return a.TArray.IsEmpty()
}
// DeepCopy implements interface for deep copy of current type.
@ -849,9 +514,8 @@ func (a *StrArray) DeepCopy() any {
if a == nil {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
newSlice := make([]string, len(a.array))
copy(newSlice, a.array)
return NewStrArrayFrom(newSlice, a.mu.IsSafe())
a.lazyInit()
return &StrArray{
TArray: a.TArray.DeepCopy().(*TArray[string]),
}
}

View File

@ -7,35 +7,44 @@
package garray
import (
"bytes"
"math"
"sort"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/deepcopy"
"github.com/gogf/gf/v2/internal/empty"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/rwmutex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/grand"
)
// TArray is a golang array with rich features.
// It contains a concurrent-safe/unsafe switch, which should be set
// when its initialization and cannot be changed then.
// TArray is a wrapper of Array. It is designed to make using Array more convenient.
type TArray[T comparable] struct {
Array
mu rwmutex.RWMutex
array []T
}
// NewTArray creates and returns an empty array.
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewTArray[T comparable](safe ...bool) *TArray[T] {
return &TArray[T]{
Array: *NewArray(safe...),
}
return NewTArraySize[T](0, 0, safe...)
}
// NewTArraySize create and returns an array with given size and cap.
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewTArraySize[T comparable](size int, cap int, safe ...bool) *TArray[T] {
arr := NewArraySize(size, cap, safe...)
ret := &TArray[T]{
Array: *arr,
return &TArray[T]{
mu: rwmutex.Create(safe...),
array: make([]T, size, cap),
}
return ret
}
// NewTArrayFrom creates and returns an array with given slice `array`.
@ -43,7 +52,8 @@ func NewTArraySize[T comparable](size int, cap int, safe ...bool) *TArray[T] {
// which is false in default.
func NewTArrayFrom[T comparable](array []T, safe ...bool) *TArray[T] {
return &TArray[T]{
Array: *NewArrayFrom(tToAnySlice(array), safe...),
mu: rwmutex.Create(safe...),
array: array,
}
}
@ -51,152 +61,271 @@ func NewTArrayFrom[T comparable](array []T, safe ...bool) *TArray[T] {
// The parameter `safe` is used to specify whether using array in concurrent-safety,
// which is false in default.
func NewTArrayFromCopy[T comparable](array []T, safe ...bool) *TArray[T] {
newArray := make([]T, len(array))
copy(newArray, array)
return &TArray[T]{
Array: *NewArrayFromCopy(tToAnySlice(array), safe...),
mu: rwmutex.Create(safe...),
array: newArray,
}
}
// At returns the value by the specified index.
// If the given `index` is out of range of the array, it returns `nil`.
func (a *TArray[T]) At(index int) (value T) {
value, _ = a.Array.At(index).(T)
value, _ = a.Get(index)
return
}
// Get returns the value by the specified index.
// If the given `index` is out of range of the array, the `found` is false.
func (a *TArray[T]) Get(index int) (value T, found bool) {
val, found := a.Array.Get(index)
if !found {
a.mu.RLock()
defer a.mu.RUnlock()
if index < 0 || index >= len(a.array) {
found = false
return
}
value, _ = val.(T)
return
return a.array[index], true
}
// Set sets value to specified index.
func (a *TArray[T]) Set(index int, value T) error {
return a.Array.Set(index, value)
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
a.array[index] = value
return nil
}
// SetArray sets the underlying slice array with the given `array`.
func (a *TArray[T]) SetArray(array []T) *TArray[T] {
a.Array.SetArray(tToAnySlice(array))
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
return a
}
// Replace replaces the array items by given `array` from the beginning of array.
func (a *TArray[T]) Replace(array []T) *TArray[T] {
a.Array.Replace(tToAnySlice(array))
a.mu.Lock()
defer a.mu.Unlock()
max := len(array)
if max > len(a.array) {
max = len(a.array)
}
for i := 0; i < max; i++ {
a.array[i] = array[i]
}
return a
}
// Sum returns the sum of values in an array.
func (a *TArray[T]) Sum() int {
return a.Array.Sum()
func (a *TArray[T]) Sum() (sum int) {
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
sum += gconv.Int(v)
}
return
}
// SortFunc sorts the array by custom function `less`.
func (a *TArray[T]) SortFunc(less func(v1, v2 T) bool) *TArray[T] {
a.Array.SortFunc(func(v1, v2 any) bool {
v1t, _ := v1.(T)
v2t, _ := v2.(T)
return less(v1t, v2t)
a.mu.Lock()
defer a.mu.Unlock()
sort.Slice(a.array, func(i, j int) bool {
return less(a.array[i], a.array[j])
})
return a
}
// InsertBefore inserts the `values` to the front of `index`.
func (a *TArray[T]) InsertBefore(index int, values ...T) error {
return a.Array.InsertBefore(index, tToAnySlice(values)...)
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
rear := append([]T{}, a.array[index:]...)
a.array = append(a.array[0:index], values...)
a.array = append(a.array, rear...)
return nil
}
// InsertAfter inserts the `values` to the back of `index`.
func (a *TArray[T]) InsertAfter(index int, values ...T) error {
return a.Array.InsertAfter(index, tToAnySlice(values)...)
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", index, len(a.array))
}
rear := append([]T{}, a.array[index+1:]...)
a.array = append(a.array[0:index+1], values...)
a.array = append(a.array, rear...)
return nil
}
// Remove removes an item by index.
// If the given `index` is out of range of the array, the `found` is false.
func (a *TArray[T]) Remove(index int) (value T, found bool) {
val, found := a.Array.Remove(index)
if !found {
a.mu.Lock()
defer a.mu.Unlock()
return a.doRemoveWithoutLock(index)
}
// doRemoveWithoutLock removes an item by index without lock.
func (a *TArray[T]) doRemoveWithoutLock(index int) (value T, found bool) {
if index < 0 || index >= len(a.array) {
found = false
return
}
value, _ = val.(T)
return
// Determine array boundaries when deleting to improve deletion efficiency.
if index == 0 {
value := a.array[0]
a.array = a.array[1:]
return value, true
} else if index == len(a.array)-1 {
value := a.array[index]
a.array = a.array[:index]
return value, true
}
// If it is a non-boundary delete,
// it will involve the creation of an array,
// then the deletion is less efficient.
value = a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
return value, true
}
// RemoveValue removes an item by value.
// It returns true if value is found in the array, or else false if not found.
func (a *TArray[T]) RemoveValue(value T) bool {
return a.Array.RemoveValue(value)
a.mu.Lock()
defer a.mu.Unlock()
if i := a.doSearchWithoutLock(value); i != -1 {
a.doRemoveWithoutLock(i)
return true
}
return false
}
// RemoveValues removes multiple items by `values`.
func (a *TArray[T]) RemoveValues(values ...T) {
a.Array.RemoveValues(tToAnySlice(values)...)
a.mu.Lock()
defer a.mu.Unlock()
for _, value := range values {
if i := a.doSearchWithoutLock(value); i != -1 {
a.doRemoveWithoutLock(i)
}
}
}
// PushLeft pushes one or multiple items to the beginning of array.
func (a *TArray[T]) PushLeft(value ...T) *TArray[T] {
a.Array.PushLeft(tToAnySlice(value)...)
a.mu.Lock()
a.array = append(value, a.array...)
a.mu.Unlock()
return a
}
// PushRight pushes one or multiple items to the end of array.
// It equals to Append.
func (a *TArray[T]) PushRight(value ...T) *TArray[T] {
a.Array.PushRight(tToAnySlice(value)...)
a.mu.Lock()
a.array = append(a.array, value...)
a.mu.Unlock()
return a
}
// PopRand randomly pops and return an item out of array.
// Note that if the array is empty, the `found` is false.
func (a *TArray[T]) PopRand() (value T, found bool) {
val, found := a.Array.PopRand()
if !found {
return
}
value, _ = val.(T)
return
a.mu.Lock()
defer a.mu.Unlock()
return a.doRemoveWithoutLock(grand.Intn(len(a.array)))
}
// PopRands randomly pops and returns `size` items out of array.
func (a *TArray[T]) PopRands(size int) []T {
return anyToTSlice[T](a.Array.PopRands(size))
a.mu.Lock()
defer a.mu.Unlock()
if size <= 0 || len(a.array) == 0 {
return nil
}
if size >= len(a.array) {
size = len(a.array)
}
array := make([]T, size)
for i := 0; i < size; i++ {
array[i], _ = a.doRemoveWithoutLock(grand.Intn(len(a.array)))
}
return array
}
// PopLeft pops and returns an item from the beginning of array.
// Note that if the array is empty, the `found` is false.
func (a *TArray[T]) PopLeft() (value T, found bool) {
val, found := a.Array.PopLeft()
if !found {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.array) == 0 {
found = false
return
}
value, _ = val.(T)
return
value = a.array[0]
a.array = a.array[1:]
return value, true
}
// PopRight pops and returns an item from the end of array.
// Note that if the array is empty, the `found` is false.
func (a *TArray[T]) PopRight() (value T, found bool) {
val, found := a.Array.PopRight()
if !found {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
if index < 0 {
found = false
return
}
value, _ = val.(T)
return
value = a.array[index]
a.array = a.array[:index]
return value, true
}
// PopLefts pops and returns `size` items from the beginning of array.
func (a *TArray[T]) PopLefts(size int) []T {
return anyToTSlice[T](a.Array.PopLefts(size))
a.mu.Lock()
defer a.mu.Unlock()
if size <= 0 || len(a.array) == 0 {
return nil
}
if size >= len(a.array) {
array := a.array
a.array = a.array[:0]
return array
}
value := a.array[0:size]
a.array = a.array[size:]
return value
}
// PopRights pops and returns `size` items from the end of array.
func (a *TArray[T]) PopRights(size int) []T {
return anyToTSlice[T](a.Array.PopRights(size))
a.mu.Lock()
defer a.mu.Unlock()
if size <= 0 || len(a.array) == 0 {
return nil
}
index := len(a.array) - size
if index <= 0 {
array := a.array
a.array = a.array[:0]
return array
}
value := a.array[index:]
a.array = a.array[:index]
return value
}
// Range picks and returns items by range, like array[start:end].
@ -207,7 +336,26 @@ func (a *TArray[T]) PopRights(size int) []T {
// If `end` is omitted, then the sequence will have everything from start up
// until the end of the array.
func (a *TArray[T]) Range(start int, end ...int) []T {
return anyToTSlice[T](a.Array.Range(start, end...))
a.mu.RLock()
defer a.mu.RUnlock()
offsetEnd := len(a.array)
if len(end) > 0 && end[0] < offsetEnd {
offsetEnd = end[0]
}
if start > offsetEnd {
return nil
}
if start < 0 {
start = 0
}
array := ([]T)(nil)
if a.mu.IsSafe() {
array = make([]T, offsetEnd-start)
copy(array, a.array[start:offsetEnd])
} else {
array = a.array[start:offsetEnd]
}
return array
}
// SubSlice returns a slice of elements from the array as specified
@ -224,80 +372,161 @@ func (a *TArray[T]) Range(start int, end ...int) []T {
//
// Any possibility crossing the left border of array, it will fail.
func (a *TArray[T]) SubSlice(offset int, length ...int) []T {
return anyToTSlice[T](a.Array.SubSlice(offset, length...))
a.mu.RLock()
defer a.mu.RUnlock()
size := len(a.array)
if len(length) > 0 {
size = length[0]
}
if offset > len(a.array) {
return nil
}
if offset < 0 {
offset = len(a.array) + offset
if offset < 0 {
return nil
}
}
if size < 0 {
offset += size
size = -size
if offset < 0 {
return nil
}
}
end := offset + size
if end > len(a.array) {
end = len(a.array)
size = len(a.array) - offset
}
if a.mu.IsSafe() {
s := make([]T, size)
copy(s, a.array[offset:])
return s
} else {
return a.array[offset:end]
}
}
// Append is alias of PushRight, please See PushRight.
func (a *TArray[T]) Append(value ...T) *TArray[T] {
a.Array.Append(tToAnySlice(value)...)
a.PushRight(value...)
return a
}
// Len returns the length of array.
func (a *TArray[T]) Len() int {
return a.Array.Len()
a.mu.RLock()
length := len(a.array)
a.mu.RUnlock()
return length
}
// Slice returns the underlying data of array.
// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data,
// or else a pointer to the underlying data.
func (a *TArray[T]) Slice() []T {
return anyToTSlice[T](a.Array.Slice())
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
array := make([]T, len(a.array))
copy(array, a.array)
return array
} else {
return a.array
}
}
// Interfaces returns current array as []any.
func (a *TArray[T]) Interfaces() []any {
return a.Array.Interfaces()
return tToAnySlice(a.Slice())
}
// Clone returns a new array, which is a copy of current array.
func (a *TArray[T]) Clone() *TArray[T] {
return &TArray[T]{
Array: *a.Array.Clone(),
}
func (a *TArray[T]) Clone() (newArray *TArray[T]) {
a.mu.RLock()
array := make([]T, len(a.array))
copy(array, a.array)
a.mu.RUnlock()
return NewTArrayFrom(array, a.mu.IsSafe())
}
// Clear deletes all items of current array.
func (a *TArray[T]) Clear() *TArray[T] {
a.Array.Clear()
a.mu.Lock()
if len(a.array) > 0 {
a.array = make([]T, 0)
}
a.mu.Unlock()
return a
}
// Contains checks whether a value exists in the array.
func (a *TArray[T]) Contains(value T) bool {
return a.Array.Contains(value)
return a.Search(value) != -1
}
// Search searches array by `value`, returns the index of `value`,
// or returns -1 if not exists.
func (a *TArray[T]) Search(value T) int {
return a.Array.Search(value)
a.mu.RLock()
defer a.mu.RUnlock()
return a.doSearchWithoutLock(value)
}
func (a *TArray[T]) doSearchWithoutLock(value T) int {
if len(a.array) == 0 {
return -1
}
result := -1
for index, v := range a.array {
if v == value {
result = index
break
}
}
return result
}
// Unique uniques the array, clear repeated items.
// Example: [1,1,2,3,2] -> [1,2,3]
func (a *TArray[T]) Unique() *TArray[T] {
a.Array.Unique()
a.mu.Lock()
defer a.mu.Unlock()
if len(a.array) == 0 {
return a
}
var (
ok bool
temp T
uniqueSet = make(map[T]struct{})
uniqueArray = make([]T, 0, len(a.array))
)
for i := 0; i < len(a.array); i++ {
temp = a.array[i]
if _, ok = uniqueSet[temp]; ok {
continue
}
uniqueSet[temp] = struct{}{}
uniqueArray = append(uniqueArray, temp)
}
a.array = uniqueArray
return a
}
// LockFunc locks writing by callback function `f`.
func (a *TArray[T]) LockFunc(f func(array []T)) *TArray[T] {
a.Array.LockFunc(func(array []any) {
vals := anyToTSlice[T](array)
f(vals)
for k, v := range vals {
array[k] = v
}
})
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
// RLockFunc locks reading by callback function `f`.
func (a *TArray[T]) RLockFunc(f func(array []T)) *TArray[T] {
a.Array.RLockFunc(func(array []any) {
f(anyToTSlice[T](array))
})
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
@ -306,40 +535,63 @@ func (a *TArray[T]) RLockFunc(f func(array []T)) *TArray[T] {
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *TArray[T]) Merge(array any) *TArray[T] {
var vals []T
switch v := array.(type) {
case *Array:
return a.Merge(v.Slice())
case *StrArray:
return a.Merge(v.Slice())
case *IntArray:
return a.Merge(v.Slice())
case *SortedTArray[T]:
vals = v.Slice()
case *TArray[T]:
a.Array.Merge(&v.Array)
vals = v.Slice()
case []T:
a.Array.Merge(v)
case TArray[T]:
a.Array.Merge(&v.Array)
vals = v
default:
var vals []T
if err := gconv.Scan(v, &vals); err != nil {
interfaces := gconv.Interfaces(v)
if err := gconv.Scan(interfaces, &vals); err != nil {
panic(err)
}
a.Append(vals...)
}
return a
return a.Append(vals...)
}
// Fill fills an array with num entries of the value `value`,
// keys starting at the `startIndex` parameter.
func (a *TArray[T]) Fill(startIndex int, num int, value T) error {
return a.Array.Fill(startIndex, num, value)
a.mu.Lock()
defer a.mu.Unlock()
if startIndex < 0 || startIndex > len(a.array) {
return gerror.NewCodef(gcode.CodeInvalidParameter, "index %d out of array range %d", startIndex, len(a.array))
}
for i := startIndex; i < startIndex+num; i++ {
if i > len(a.array)-1 {
a.array = append(a.array, value)
} else {
a.array[i] = value
}
}
return nil
}
// Chunk splits an array into multiple arrays,
// the size of each array is determined by `size`.
// The last chunk may contain less than size elements.
func (a *TArray[T]) Chunk(size int) (values [][]T) {
return anyToTSlices[T](a.Array.Chunk(size))
func (a *TArray[T]) Chunk(size int) [][]T {
if size < 1 {
return nil
}
a.mu.RLock()
defer a.mu.RUnlock()
length := len(a.array)
chunks := int(math.Ceil(float64(length) / float64(size)))
var n [][]T
for i, end := 0, 0; chunks > 0; chunks-- {
end = (i + 1) * size
if end > length {
end = length
}
n = append(n, a.array[i*size:end])
i++
}
return n
}
// Pad pads array to the specified length with `value`.
@ -347,76 +599,128 @@ func (a *TArray[T]) Chunk(size int) (values [][]T) {
// If the absolute value of `size` is less than or equal to the length of the array
// then no padding takes place.
func (a *TArray[T]) Pad(size int, val T) *TArray[T] {
a.Array.Pad(size, val)
a.mu.Lock()
defer a.mu.Unlock()
if size == 0 || (size > 0 && size < len(a.array)) || (size < 0 && size > -len(a.array)) {
return a
}
n := size
if size < 0 {
n = -size
}
n -= len(a.array)
tmp := make([]T, n)
for i := 0; i < n; i++ {
tmp[i] = val
}
if size > 0 {
a.array = append(a.array, tmp...)
} else {
a.array = append(tmp, a.array...)
}
return a
}
// Rand randomly returns one item from array(no deleting).
func (a *TArray[T]) Rand() (value T, found bool) {
val, found := a.Array.Rand()
if !found {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
found = false
return
}
value, _ = val.(T)
return
return a.array[grand.Intn(len(a.array))], true
}
// Rands randomly returns `size` items from array(no deleting).
func (a *TArray[T]) Rands(size int) []T {
return anyToTSlice[T](a.Array.Rands(size))
a.mu.RLock()
defer a.mu.RUnlock()
if size <= 0 || len(a.array) == 0 {
return nil
}
array := make([]T, size)
for i := 0; i < size; i++ {
array[i] = a.array[grand.Intn(len(a.array))]
}
return array
}
// Shuffle randomly shuffles the array.
func (a *TArray[T]) Shuffle() *TArray[T] {
a.Array.Shuffle()
a.mu.Lock()
defer a.mu.Unlock()
for i, v := range grand.Perm(len(a.array)) {
a.array[i], a.array[v] = a.array[v], a.array[i]
}
return a
}
// Reverse makes array with elements in reverse order.
func (a *TArray[T]) Reverse() *TArray[T] {
a.Array.Reverse()
a.mu.Lock()
defer a.mu.Unlock()
for i, j := 0, len(a.array)-1; i < j; i, j = i+1, j-1 {
a.array[i], a.array[j] = a.array[j], a.array[i]
}
return a
}
// Join joins array elements with a string `glue`.
func (a *TArray[T]) Join(glue string) string {
return a.Array.Join(glue)
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return ""
}
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array)-1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}
// CountValues counts the number of occurrences of all values in the array.
func (a *TArray[T]) CountValues() (valueCnt map[T]int) {
valueCnt = map[T]int{}
for k, v := range a.Array.CountValues() {
k0, _ := k.(T)
valueCnt[k0] = v
func (a *TArray[T]) CountValues() map[T]int {
m := make(map[T]int)
a.mu.RLock()
defer a.mu.RUnlock()
for _, v := range a.array {
m[v]++
}
return
return m
}
// Iterator is alias of IteratorAsc.
func (a *TArray[T]) Iterator(f func(k int, v T) bool) {
a.Array.Iterator(func(k int, v any) bool {
v0, _ := v.(T)
return f(k, v0)
})
a.IteratorAsc(f)
}
// IteratorAsc iterates the array readonly in ascending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *TArray[T]) IteratorAsc(f func(k int, v T) bool) {
a.Array.IteratorAsc(func(k int, v any) bool {
v0, _ := v.(T)
return f(k, v0)
})
a.mu.RLock()
defer a.mu.RUnlock()
for k, v := range a.array {
if !f(k, v) {
break
}
}
}
// IteratorDesc iterates the array readonly in descending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (a *TArray[T]) IteratorDesc(f func(k int, v T) bool) {
a.Array.IteratorDesc(func(k int, v any) bool {
v0, _ := v.(T)
return f(k, v0)
})
a.mu.RLock()
defer a.mu.RUnlock()
for i := len(a.array) - 1; i >= 0; i-- {
if !f(i, a.array[i]) {
break
}
}
}
// String returns current array as a string, which implements like json.Marshal does.
@ -424,61 +728,120 @@ func (a *TArray[T]) String() string {
if a == nil {
return ""
}
return a.Array.String()
a.mu.RLock()
defer a.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
buffer.WriteByte('[')
s := ""
for k, v := range a.array {
s = gconv.String(v)
if gstr.IsNumeric(s) {
buffer.WriteString(s)
} else {
buffer.WriteString(`"` + gstr.QuoteMeta(s, `"\`) + `"`)
}
if k != len(a.array)-1 {
buffer.WriteByte(',')
}
}
buffer.WriteByte(']')
return buffer.String()
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
// Note that do not use pointer as its receiver here.
func (a TArray[T]) MarshalJSON() ([]byte, error) {
return a.Array.MarshalJSON()
a.mu.RLock()
defer a.mu.RUnlock()
return json.Marshal(a.array)
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (a *TArray[T]) UnmarshalJSON(b []byte) error {
return a.Array.UnmarshalJSON(b)
if a.array == nil {
a.array = make([]T, 0)
}
a.mu.Lock()
defer a.mu.Unlock()
if err := json.UnmarshalUseNumber(b, &a.array); err != nil {
return err
}
return nil
}
// UnmarshalValue is an interface implement which sets any type of value for array.
func (a *TArray[T]) UnmarshalValue(value any) error {
return a.Array.UnmarshalValue(value)
a.mu.Lock()
defer a.mu.Unlock()
switch value.(type) {
case string, []byte:
return json.UnmarshalUseNumber(gconv.Bytes(value), &a.array)
default:
if err := gconv.Scan(gconv.SliceAny(value), &a.array); err != nil {
return err
}
}
return nil
}
// Filter iterates array and filters elements using custom callback function.
// It removes the element from array if callback function `filter` returns true,
// it or else does nothing and continues iterating.
func (a *TArray[T]) Filter(filter func(index int, value T) bool) *TArray[T] {
a.Array.Filter(func(index int, value any) bool {
val, _ := value.(T)
return filter(index, val)
})
a.mu.Lock()
defer a.mu.Unlock()
for i := 0; i < len(a.array); {
if filter(i, a.array[i]) {
a.array = append(a.array[:i], a.array[i+1:]...)
} else {
i++
}
}
return a
}
// FilterNil removes all nil value of the array.
func (a *TArray[T]) FilterNil() *TArray[T] {
a.Array.FilterNil()
a.mu.Lock()
defer a.mu.Unlock()
for i := 0; i < len(a.array); {
if empty.IsNil(a.array[i]) {
a.array = append(a.array[:i], a.array[i+1:]...)
} else {
i++
}
}
return a
}
// FilterEmpty removes all empty value of the array.
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
func (a *TArray[T]) FilterEmpty() *TArray[T] {
a.Array.FilterEmpty()
a.mu.Lock()
defer a.mu.Unlock()
for i := 0; i < len(a.array); {
if empty.IsEmpty(a.array[i]) {
a.array = append(a.array[:i], a.array[i+1:]...)
} else {
i++
}
}
return a
}
// Walk applies a user supplied function `f` to every item of array.
func (a *TArray[T]) Walk(f func(value T) T) *TArray[T] {
a.Array.Walk(func(value any) any {
val, _ := value.(T)
return f(val)
})
a.mu.Lock()
defer a.mu.Unlock()
for i, v := range a.array {
a.array[i] = f(v)
}
return a
}
// IsEmpty checks whether the array is empty.
func (a *TArray[T]) IsEmpty() bool {
return a.Array.IsEmpty()
return a.Len() == 0
}
// DeepCopy implements interface for deep copy of current type.
@ -486,8 +849,11 @@ func (a *TArray[T]) DeepCopy() any {
if a == nil {
return nil
}
arr := a.Array.DeepCopy().(*Array)
return &TArray[T]{
Array: *arr,
a.mu.RLock()
defer a.mu.RUnlock()
newSlice := make([]T, len(a.array))
for i, v := range a.array {
newSlice[i] = deepcopy.Copy(v).(T)
}
return NewTArrayFrom(newSlice, a.mu.IsSafe())
}

View File

@ -9,6 +9,7 @@ package garray
import (
"fmt"
"sort"
"sync"
"github.com/gogf/gf/v2/util/gconv"
)
@ -20,13 +21,16 @@ import (
// when its initialization and cannot be changed then.
type SortedArray struct {
*SortedTArray[any]
once sync.Once
}
// lazyInit lazily initializes the array.
func (a *SortedArray) lazyInit() {
if a.SortedTArray == nil {
a.SortedTArray = NewSortedTArraySize[any](0, nil, false)
}
a.once.Do(func() {
if a.SortedTArray == nil {
a.SortedTArray = NewSortedTArraySize[any](0, nil, false)
}
})
}
// NewSortedArray creates and returns an empty sorted array.

View File

@ -8,6 +8,7 @@ package garray
import (
"fmt"
"sync"
"github.com/gogf/gf/v2/util/gconv"
)
@ -19,14 +20,17 @@ import (
// when its initialization and cannot be changed then.
type SortedIntArray struct {
*SortedTArray[int]
once sync.Once
}
// lazyInit lazily initializes the array.
func (a *SortedIntArray) lazyInit() {
if a.SortedTArray == nil {
a.SortedTArray = NewSortedTArraySize(0, defaultComparatorInt, false)
a.SetSorter(quickSortInt)
}
a.once.Do(func() {
if a.SortedTArray == nil {
a.SortedTArray = NewSortedTArraySize(0, defaultComparatorInt, false)
a.SetSorter(quickSortInt)
}
})
}
// NewSortedIntArray creates and returns an empty sorted array.

View File

@ -9,6 +9,7 @@ package garray
import (
"bytes"
"strings"
"sync"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
@ -21,14 +22,17 @@ import (
// when its initialization and cannot be changed then.
type SortedStrArray struct {
*SortedTArray[string]
once sync.Once
}
// lazyInit lazily initializes the array.
func (a *SortedStrArray) lazyInit() {
if a.SortedTArray == nil {
a.SortedTArray = NewSortedTArraySize(0, defaultComparatorStr, false)
a.SetSorter(quickSortStr)
}
a.once.Do(func() {
if a.SortedTArray == nil {
a.SortedTArray = NewSortedTArraySize(0, defaultComparatorStr, false)
a.SetSorter(quickSortStr)
}
})
}
// NewSortedStrArray creates and returns an empty sorted array.

View File

@ -63,14 +63,17 @@ func Test_TArray_Basic(t *testing.T) {
v, ok = array.Remove(0) // 1, 2, 3
t.Assert(v, 100)
t.Assert(ok, true)
t.Assert(array.Slice(), []int{1, 2, 3})
v, ok = array.Remove(-1)
t.Assert(v, 0)
t.Assert(ok, false)
t.Assert(array.Slice(), []int{1, 2, 3})
v, ok = array.Remove(100000)
t.Assert(v, 0)
t.Assert(ok, false)
t.Assert(array.Slice(), []int{1, 2, 3})
v, ok = array2.Remove(3) // 0 1 2
t.Assert(v, 3)
@ -81,14 +84,16 @@ func Test_TArray_Basic(t *testing.T) {
t.Assert(ok, true)
t.Assert(array.Contains(100), false)
array.Append(4) // 1, 2, 3 ,4
array.Append(4) // 2, 2, 3, 4
t.Assert(array.Slice(), []int{2, 2, 3, 4})
t.Assert(array.Len(), 4)
array.InsertBefore(0, 100) // 100, 1, 2, 3, 4
array.InsertAfter(0, 200) // 100, 200, 1, 2, 3, 4
t.Assert(array.Slice(), []int{100, 200, 1, 2, 3, 4})
array.InsertBefore(0, 100) // 100, 2, 2, 3, 4
t.Assert(array.Slice(), []int{100, 2, 2, 3, 4})
array.InsertAfter(0, 200) // 100, 200, 2, 2, 3, 4
t.Assert(array.Slice(), []int{100, 200, 2, 2, 3, 4})
array.InsertBefore(5, 300)
array.InsertAfter(6, 400)
t.Assert(array.Slice(), []int{100, 200, 1, 2, 3, 300, 4, 400})
t.Assert(array.Slice(), []int{100, 200, 2, 2, 3, 300, 4, 400})
t.Assert(array.Clear().Len(), 0)
err = array.InsertBefore(99, 9900)
t.AssertNE(err, nil)
@ -588,15 +593,15 @@ func TestTArray_RLockFunc(t *testing.T) {
ch1 := make(chan int64, 3)
ch2 := make(chan int64, 1)
// go1
go a1.RLockFunc(func(n1 []any) { // 读锁
time.Sleep(2 * time.Second) // 暂停1秒
go a1.RLockFunc(func(n1 []any) { // read lock
time.Sleep(2 * time.Second) // sleep 2 s
n1[2] = "g"
ch2 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
})
// go2
go func() {
time.Sleep(100 * time.Millisecond) // 故意暂停0.01秒,等go1执行锁后再开始执行.
time.Sleep(100 * time.Millisecond) // wait go1 do line lock for 0.01s. Then do.
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
a1.Len()
ch1 <- gconv.Int64(time.Now().UnixNano() / 1000 / 1000)
@ -604,11 +609,11 @@ func TestTArray_RLockFunc(t *testing.T) {
t1 := <-ch1
t2 := <-ch1
<-ch2 // 等待go1完成
<-ch2 // wait for go1 done.
// 防止ci抖动,以豪秒为单位
t.AssertLT(t2-t1, 20) // go1加的读锁所go2读的时候并没有阻塞。
t.Assert(a1.Contains("g"), false)
// Prevent CI jitter, in milliseconds.
t.AssertLT(t2-t1, 20) // Go1 acquired a read lock, so when Go2 reads, it is not blocked.
t.Assert(a1.Contains("g"), true)
})
}

721
container/glist/glist_t.go Normal file
View File

@ -0,0 +1,721 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
//
package glist
import (
"bytes"
"container/list"
"github.com/gogf/gf/v2/internal/deepcopy"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/internal/rwmutex"
"github.com/gogf/gf/v2/util/gconv"
)
// TElement is an element of a linked list.
type TElement[T any] struct {
// Next and previous pointers in the doubly-linked list of elements.
// To simplify the implementation, internally a list l is implemented
// as a ring, such that &l.root is both the next element of the last
// list element (l.Back()) and the previous element of the first list
// element (l.Front()).
next, prev *TElement[T]
// The list to which this element belongs.
list *TList[T]
// The value stored with this element.
Value T
}
// Next returns the next list element or nil.
func (e *TElement[T]) Next() *TElement[T] {
if p := e.next; e.list != nil && p != &e.list.root {
return p
}
return nil
}
// Prev returns the previous list element or nil.
func (e *TElement[T]) Prev() *TElement[T] {
if p := e.prev; e.list != nil && p != &e.list.root {
return p
}
return nil
}
// TList is a doubly linked list containing a concurrent-safe/unsafe switch.
// The switch should be set when its initialization and cannot be changed then.
type TList[T any] struct {
mu rwmutex.RWMutex
root TElement[T] // sentinel list element, only &root, root.prev, and root.next are used
len int // current list length excluding (this) sentinel element
}
// NewT creates and returns a new empty doubly linked list.
func NewT[T any](safe ...bool) *TList[T] {
l := &TList[T]{
mu: rwmutex.Create(safe...),
}
return l.init()
}
// NewTFrom creates and returns a list from a copy of given slice `array`.
// The parameter `safe` is used to specify whether using list in concurrent-safety,
// which is false in default.
func NewTFrom[T any](array []T, safe ...bool) *TList[T] {
l := NewT[T](safe...)
for _, v := range array {
l.insertValue(v, l.root.prev)
}
return l
}
// PushFront inserts a new element `e` with value `v` at the front of list `l` and returns `e`.
func (l *TList[T]) PushFront(v T) (e *TElement[T]) {
l.mu.Lock()
l.lazyInit()
e = l.insertValue(v, &l.root)
l.mu.Unlock()
return
}
// PushBack inserts a new element `e` with value `v` at the back of list `l` and returns `e`.
func (l *TList[T]) PushBack(v T) (e *TElement[T]) {
l.mu.Lock()
l.lazyInit()
e = l.insertValue(v, l.root.prev)
l.mu.Unlock()
return
}
// PushFronts inserts multiple new elements with values `values` at the front of list `l`.
func (l *TList[T]) PushFronts(values []T) {
l.mu.Lock()
l.lazyInit()
for _, v := range values {
l.insertValue(v, &l.root)
}
l.mu.Unlock()
}
// PushBacks inserts multiple new elements with values `values` at the back of list `l`.
func (l *TList[T]) PushBacks(values []T) {
l.mu.Lock()
l.lazyInit()
for _, v := range values {
l.insertValue(v, l.root.prev)
}
l.mu.Unlock()
}
// PopBack removes the element from back of `l` and returns the value of the element.
func (l *TList[T]) PopBack() (value T) {
l.mu.Lock()
defer l.mu.Unlock()
l.lazyInit()
if l.len == 0 {
return
}
return l.remove(l.root.prev)
}
// PopFront removes the element from front of `l` and returns the value of the element.
func (l *TList[T]) PopFront() (value T) {
l.mu.Lock()
defer l.mu.Unlock()
l.lazyInit()
if l.len == 0 {
return
}
return l.remove(l.root.next)
}
// PopBacks removes `max` elements from back of `l`
// and returns values of the removed elements as slice.
func (l *TList[T]) PopBacks(max int) (values []T) {
l.mu.Lock()
defer l.mu.Unlock()
l.lazyInit()
length := l.len
if length > 0 {
if max > 0 && max < length {
length = max
}
values = make([]T, length)
for i := 0; i < length; i++ {
values[i] = l.remove(l.root.prev)
}
}
return
}
// PopFronts removes `max` elements from front of `l`
// and returns values of the removed elements as slice.
func (l *TList[T]) PopFronts(max int) (values []T) {
l.mu.Lock()
defer l.mu.Unlock()
l.lazyInit()
length := l.len
if length > 0 {
if max > 0 && max < length {
length = max
}
values = make([]T, length)
for i := 0; i < length; i++ {
values[i] = l.remove(l.root.next)
}
}
return
}
// PopBackAll removes all elements from back of `l`
// and returns values of the removed elements as slice.
func (l *TList[T]) PopBackAll() []T {
return l.PopBacks(-1)
}
// PopFrontAll removes all elements from front of `l`
// and returns values of the removed elements as slice.
func (l *TList[T]) PopFrontAll() []T {
return l.PopFronts(-1)
}
// FrontAll copies and returns values of all elements from front of `l` as slice.
func (l *TList[T]) FrontAll() (values []T) {
l.mu.RLock()
defer l.mu.RUnlock()
l.lazyInit()
length := l.len
if length > 0 {
values = make([]T, length)
for i, e := 0, l.front(); i < length; i, e = i+1, e.Next() {
values[i] = e.Value
}
}
return
}
// BackAll copies and returns values of all elements from back of `l` as slice.
func (l *TList[T]) BackAll() (values []T) {
l.mu.RLock()
defer l.mu.RUnlock()
l.lazyInit()
length := l.len
if length > 0 {
values = make([]T, length)
for i, e := 0, l.back(); i < length; i, e = i+1, e.Prev() {
values[i] = e.Value
}
}
return
}
// FrontValue returns value of the first element of `l` or zero value of T if the list is empty.
func (l *TList[T]) FrontValue() (value T) {
l.mu.RLock()
defer l.mu.RUnlock()
l.lazyInit()
if e := l.front(); e != nil {
value = e.Value
}
return
}
// BackValue returns value of the last element of `l` or zero value of T if the list is empty.
func (l *TList[T]) BackValue() (value T) {
l.mu.RLock()
defer l.mu.RUnlock()
l.lazyInit()
if e := l.back(); e != nil {
value = e.Value
}
return
}
// Front returns the first element of list `l` or nil if the list is empty.
func (l *TList[T]) Front() (e *TElement[T]) {
l.mu.RLock()
defer l.mu.RUnlock()
l.lazyInit()
e = l.front()
return
}
// Back returns the last element of list `l` or nil if the list is empty.
func (l *TList[T]) Back() (e *TElement[T]) {
l.mu.RLock()
defer l.mu.RUnlock()
l.lazyInit()
e = l.back()
return
}
// Len returns the number of elements of list `l`.
// The complexity is O(1).
func (l *TList[T]) Len() (length int) {
l.mu.RLock()
defer l.mu.RUnlock()
l.lazyInit()
length = l.len
return
}
// Size is alias of Len.
func (l *TList[T]) Size() int {
return l.Len()
}
// MoveBefore moves element `e` to its new position before `p`.
// If `e` or `p` is not an element of `l`, or `e` == `p`, the list is not modified.
// The element and `p` must not be nil.
func (l *TList[T]) MoveBefore(e, p *TElement[T]) {
l.mu.Lock()
defer l.mu.Unlock()
l.lazyInit()
if e.list != l || e == p || p.list != l {
return
}
l.move(e, p.prev)
}
// MoveAfter moves element `e` to its new position after `p`.
// If `e` or `p` is not an element of `l`, or `e` == `p`, the list is not modified.
// The element and `p` must not be nil.
func (l *TList[T]) MoveAfter(e, p *TElement[T]) {
l.mu.Lock()
defer l.mu.Unlock()
l.lazyInit()
if e.list != l || e == p || p.list != l {
return
}
l.move(e, p)
}
// MoveToFront moves element `e` to the front of list `l`.
// If `e` is not an element of `l`, the list is not modified.
// The element must not be nil.
func (l *TList[T]) MoveToFront(e *TElement[T]) {
l.mu.Lock()
defer l.mu.Unlock()
l.lazyInit()
if e.list != l || l.root.next == e {
return
}
// see comment in List.Remove about initialization of l
l.move(e, &l.root)
}
// MoveToBack moves element `e` to the back of list `l`.
// If `e` is not an element of `l`, the list is not modified.
// The element must not be nil.
func (l *TList[T]) MoveToBack(e *TElement[T]) {
l.mu.Lock()
defer l.mu.Unlock()
l.lazyInit()
if e.list != l || l.root.prev == e {
return
}
// see comment in List.Remove about initialization of l
l.move(e, l.root.prev)
}
// PushBackList inserts a copy of an other list at the back of list `l`.
// The lists `l` and `other` may be the same, but they must not be nil.
func (l *TList[T]) PushBackList(other *TList[T]) {
if l != other {
other.mu.RLock()
defer other.mu.RUnlock()
}
l.mu.Lock()
defer l.mu.Unlock()
l.lazyInit()
for i, e := other.len, other.front(); i > 0; i, e = i-1, e.Next() {
l.insertValue(e.Value, l.root.prev)
}
}
// PushFrontList inserts a copy of an other list at the front of list `l`.
// The lists `l` and `other` may be the same, but they must not be nil.
func (l *TList[T]) PushFrontList(other *TList[T]) {
if l != other {
other.mu.RLock()
defer other.mu.RUnlock()
}
l.mu.Lock()
defer l.mu.Unlock()
l.lazyInit()
for i, e := other.len, other.back(); i > 0; i, e = i-1, e.Prev() {
l.insertValue(e.Value, &l.root)
}
}
// InsertAfter inserts a new element `e` with value `v` immediately after `p` and returns `e`.
// If `p` is not an element of `l`, the list is not modified.
// The `p` must not be nil.
func (l *TList[T]) InsertAfter(p *TElement[T], v T) (e *TElement[T]) {
l.mu.Lock()
defer l.mu.Unlock()
l.lazyInit()
if p.list != l {
return nil
}
e = l.insertValue(v, p)
return
}
// InsertBefore inserts a new element `e` with value `v` immediately before `p` and returns `e`.
// If `p` is not an element of `l`, the list is not modified.
// The `p` must not be nil.
func (l *TList[T]) InsertBefore(p *TElement[T], v T) (e *TElement[T]) {
l.mu.Lock()
defer l.mu.Unlock()
l.lazyInit()
if p.list != l {
return nil
}
e = l.insertValue(v, p.prev)
return
}
// Remove removes `e` from `l` if `e` is an element of list `l`.
// It returns the element value e.Value.
// The element must not be nil.
func (l *TList[T]) Remove(e *TElement[T]) (value T) {
l.mu.Lock()
defer l.mu.Unlock()
l.lazyInit()
return l.remove(e)
}
// Removes removes multiple elements `es` from `l` if `es` are elements of list `l`.
func (l *TList[T]) Removes(es []*TElement[T]) {
l.mu.Lock()
defer l.mu.Unlock()
l.lazyInit()
for _, e := range es {
l.remove(e)
}
}
// RemoveAll removes all elements from list `l`.
func (l *TList[T]) RemoveAll() {
l.mu.Lock()
l.init()
l.mu.Unlock()
}
// Clear is alias of RemoveAll.
func (l *TList[T]) Clear() {
l.RemoveAll()
}
// ToList converts TList[T] to list.List
func (l *TList[T]) ToList() *list.List {
l.mu.RLock()
defer l.mu.RUnlock()
return l.toList()
}
// toList converts TList[T] to list.List
func (l *TList[T]) toList() *list.List {
l.lazyInit()
nl := list.New()
for e := l.front(); e != nil; e = e.Next() {
nl.PushBack(e.Value)
}
return nl
}
// AppendList append list.List to the end
func (l *TList[T]) AppendList(nl *list.List) {
l.mu.Lock()
defer l.mu.Unlock()
l.appendList(nl)
}
// appendList append list.List to the end
func (l *TList[T]) appendList(nl *list.List) {
if nl.Len() == 0 {
return
}
l.lazyInit()
for e := nl.Front(); e != nil; e = e.Next() {
if v, ok := e.Value.(T); ok {
l.insertValue(v, l.root.prev)
}
}
}
// AssignList assigns list.List to now TList[T].
// It will clear TList[T] first, and append the list.List.
// Note: Elements in nl that are not assignable to T are silently skipped.
// Returns the number of skipped (incompatible) elements.
func (l *TList[T]) AssignList(nl *list.List) int {
l.mu.Lock()
defer l.mu.Unlock()
return l.assignList(nl)
}
// assignList assigns list.List to now TList[T].
// It will clear TList[T] first, and append the list.List.
// Returns the number of skipped (incompatible) elements.
func (l *TList[T]) assignList(nl *list.List) int {
l.init()
if nl.Len() == 0 {
return 0
}
skipped := 0
for e := nl.Front(); e != nil; e = e.Next() {
if v, ok := e.Value.(T); ok {
l.insertValue(v, l.root.prev)
} else {
skipped++
}
}
return skipped
}
// RLockFunc locks reading with given callback function `f` within RWMutex.RLock.
func (l *TList[T]) RLockFunc(f func(list *list.List)) {
l.mu.RLock()
defer l.mu.RUnlock()
f(l.toList())
}
// LockFunc locks writing with given callback function `f` within RWMutex.Lock.
func (l *TList[T]) LockFunc(f func(list *list.List)) {
l.mu.Lock()
defer l.mu.Unlock()
nl := l.toList()
f(nl)
l.assignList(nl)
}
// Iterator is alias of IteratorAsc.
func (l *TList[T]) Iterator(f func(e *TElement[T]) bool) {
l.IteratorAsc(f)
}
// IteratorAsc iterates the list readonly in ascending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (l *TList[T]) IteratorAsc(f func(e *TElement[T]) bool) {
l.mu.RLock()
defer l.mu.RUnlock()
l.lazyInit()
length := l.len
if length > 0 {
for i, e := 0, l.front(); i < length; i, e = i+1, e.Next() {
if !f(e) {
break
}
}
}
}
// IteratorDesc iterates the list readonly in descending order with given callback function `f`.
// If `f` returns true, then it continues iterating; or false to stop.
func (l *TList[T]) IteratorDesc(f func(e *TElement[T]) bool) {
l.mu.RLock()
defer l.mu.RUnlock()
l.lazyInit()
length := l.len
if length > 0 {
for i, e := 0, l.back(); i < length; i, e = i+1, e.Prev() {
if !f(e) {
break
}
}
}
}
// Join joins list elements with a string `glue`.
func (l *TList[T]) Join(glue string) string {
l.mu.RLock()
defer l.mu.RUnlock()
l.lazyInit()
buffer := bytes.NewBuffer(nil)
length := l.len
if length > 0 {
for i, e := 0, l.front(); i < length; i, e = i+1, e.Next() {
buffer.WriteString(gconv.String(e.Value))
if i != length-1 {
buffer.WriteString(glue)
}
}
}
return buffer.String()
}
// String returns current list as a string.
func (l *TList[T]) String() string {
if l == nil {
return ""
}
return "[" + l.Join(",") + "]"
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (l TList[T]) MarshalJSON() ([]byte, error) {
return json.Marshal(l.FrontAll())
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (l *TList[T]) UnmarshalJSON(b []byte) error {
var array []T
if err := json.UnmarshalUseNumber(b, &array); err != nil {
return err
}
l.init()
l.PushBacks(array)
return nil
}
// UnmarshalValue is an interface implement which sets any type of value for list.
func (l *TList[T]) UnmarshalValue(value any) (err error) {
var array []T
switch value.(type) {
case string, []byte:
err = json.UnmarshalUseNumber(gconv.Bytes(value), &array)
default:
anyArray := gconv.SliceAny(value)
if err = gconv.Scan(anyArray, &array); err != nil {
return
}
}
l.init()
l.PushBacks(array)
return err
}
// DeepCopy implements interface for deep copy of current type.
func (l *TList[T]) DeepCopy() any {
if l == nil {
return nil
}
l.mu.RLock()
defer l.mu.RUnlock()
l.lazyInit()
var (
length = l.len
valuesT = make([]T, length)
)
if length > 0 {
for i, e := 0, l.front(); i < length; i, e = i+1, e.Next() {
valuesT[i] = deepcopy.Copy(e.Value).(T)
}
}
return NewTFrom(valuesT, l.mu.IsSafe())
}
// Init initializes or clears list l.
func (l *TList[T]) init() *TList[T] {
l.root.next = &l.root
l.root.prev = &l.root
l.len = 0
return l
}
// lazyInit lazily initializes a zero List value.
func (l *TList[T]) lazyInit() {
if l.root.next == nil {
l.init()
}
}
// insert inserts e after at, increments l.len, and returns e.
func (l *TList[T]) insert(e, at *TElement[T]) *TElement[T] {
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
e.list = l
l.len++
return e
}
// insertValue is a convenience wrapper for insert(&Element{Value: v}, at).
func (l *TList[T]) insertValue(v T, at *TElement[T]) *TElement[T] {
return l.insert(&TElement[T]{Value: v}, at)
}
// remove removes e from its list, decrements l.len
func (l *TList[T]) remove(e *TElement[T]) (val T) {
if e.list != l {
return
}
e.prev.next = e.next
e.next.prev = e.prev
e.next = nil // avoid memory leaks
e.prev = nil // avoid memory leaks
e.list = nil
l.len--
return e.Value
}
// move moves e to next to at.
func (l *TList[T]) move(e, at *TElement[T]) {
if e == at {
return
}
e.prev.next = e.next
e.next.prev = e.prev
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
}
// front returns the first element of list l or nil if the list is empty.
func (l *TList[T]) front() *TElement[T] {
if l.len == 0 {
return nil
}
return l.root.next
}
// back returns the last element of list l or nil if the list is empty.
func (l *TList[T]) back() *TElement[T] {
if l.len == 0 {
return nil
}
return l.root.prev
}

View File

@ -0,0 +1,61 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
package glist
import (
"testing"
)
var (
lt = NewT[any](true)
)
func Benchmark_T_PushBack(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
lt.PushBack(i)
i++
}
})
}
func Benchmark_T_PushFront(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
lt.PushFront(i)
i++
}
})
}
func Benchmark_T_Len(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
lt.Len()
}
})
}
func Benchmark_T_PopFront(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
lt.PopFront()
}
})
}
func Benchmark_T_PopBack(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
lt.PopBack()
}
})
}

View File

@ -0,0 +1,689 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package glist_test
import (
"container/list"
"fmt"
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/container/glist"
"github.com/gogf/gf/v2/frame/g"
)
func ExampleNewT() {
n := 10
l := glist.NewT[any]()
for i := 0; i < n; i++ {
l.PushBack(i)
}
fmt.Println(l.Len())
fmt.Println(l)
fmt.Println(l.FrontAll())
fmt.Println(l.BackAll())
for i := 0; i < n; i++ {
fmt.Print(l.PopFront())
}
fmt.Println()
fmt.Println(l.Len())
// Output:
// 10
// [0,1,2,3,4,5,6,7,8,9]
// [0 1 2 3 4 5 6 7 8 9]
// [9 8 7 6 5 4 3 2 1 0]
// 0123456789
// 0
}
func ExampleNewTFrom() {
n := 10
l := glist.NewTFrom[any](garray.NewArrayRange(1, 10, 1).Slice())
fmt.Println(l.Len())
fmt.Println(l)
fmt.Println(l.FrontAll())
fmt.Println(l.BackAll())
for i := 0; i < n; i++ {
fmt.Print(l.PopFront())
}
fmt.Println()
fmt.Println(l.Len())
// Output:
// 10
// [1,2,3,4,5,6,7,8,9,10]
// [1 2 3 4 5 6 7 8 9 10]
// [10 9 8 7 6 5 4 3 2 1]
// 12345678910
// 0
}
func ExampleTList_PushFront() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Len())
fmt.Println(l)
l.PushFront(0)
fmt.Println(l.Len())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// 6
// [0,1,2,3,4,5]
}
func ExampleTList_PushBack() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Len())
fmt.Println(l)
l.PushBack(6)
fmt.Println(l.Len())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// 6
// [1,2,3,4,5,6]
}
func ExampleTList_PushFronts() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Len())
fmt.Println(l)
l.PushFronts(g.Slice{0, -1, -2, -3, -4})
fmt.Println(l.Len())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// 10
// [-4,-3,-2,-1,0,1,2,3,4,5]
}
func ExampleTList_PushBacks() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Len())
fmt.Println(l)
l.PushBacks(g.Slice{6, 7, 8, 9, 10})
fmt.Println(l.Len())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// 10
// [1,2,3,4,5,6,7,8,9,10]
}
func ExampleTList_PopBack() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Len())
fmt.Println(l)
fmt.Println(l.PopBack())
fmt.Println(l.Len())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// 5
// 4
// [1,2,3,4]
}
func ExampleTList_PopFront() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Len())
fmt.Println(l)
fmt.Println(l.PopFront())
fmt.Println(l.Len())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// 1
// 4
// [2,3,4,5]
}
func ExampleTList_PopBacks() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Len())
fmt.Println(l)
fmt.Println(l.PopBacks(2))
fmt.Println(l.Len())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// [5 4]
// 3
// [1,2,3]
}
func ExampleTList_PopFronts() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Len())
fmt.Println(l)
fmt.Println(l.PopFronts(2))
fmt.Println(l.Len())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// [1 2]
// 3
// [3,4,5]
}
func ExampleTList_PopBackAll() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Len())
fmt.Println(l)
fmt.Println(l.PopBackAll())
fmt.Println(l.Len())
// Output:
// 5
// [1,2,3,4,5]
// [5 4 3 2 1]
// 0
}
func ExampleTList_PopFrontAll() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Len())
fmt.Println(l)
fmt.Println(l.PopFrontAll())
fmt.Println(l.Len())
// Output:
// 5
// [1,2,3,4,5]
// [1 2 3 4 5]
// 0
}
func ExampleTList_FrontAll() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l)
fmt.Println(l.FrontAll())
// Output:
// [1,2,3,4,5]
// [1 2 3 4 5]
}
func ExampleTList_BackAll() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l)
fmt.Println(l.BackAll())
// Output:
// [1,2,3,4,5]
// [5 4 3 2 1]
}
func ExampleTList_FrontValue() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l)
fmt.Println(l.FrontValue())
// Output:
// [1,2,3,4,5]
// 1
}
func ExampleTList_BackValue() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l)
fmt.Println(l.BackValue())
// Output:
// [1,2,3,4,5]
// 5
}
func ExampleTList_Front() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Front().Value)
fmt.Println(l)
e := l.Front()
l.InsertBefore(e, 0)
l.InsertAfter(e, "a")
fmt.Println(l)
// Output:
// 1
// [1,2,3,4,5]
// [0,1,a,2,3,4,5]
}
func ExampleTList_Back() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Back().Value)
fmt.Println(l)
e := l.Back()
l.InsertBefore(e, "a")
l.InsertAfter(e, 6)
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// [1,2,3,4,a,5,6]
}
func ExampleTList_Len() {
l := glist.NewTFrom[any](g.Slice{1, 2, 3, 4, 5})
fmt.Println(l.Len())
// Output:
// 5
}
func ExampleTList_Size() {
l := glist.NewTFrom[any](g.Slice{1, 2, 3, 4, 5})
fmt.Println(l.Size())
// Output:
// 5
}
func ExampleTList_MoveBefore() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Size())
fmt.Println(l)
// element of `l`
e := l.PushBack(6)
fmt.Println(l.Size())
fmt.Println(l)
l.MoveBefore(e, l.Front())
fmt.Println(l.Size())
fmt.Println(l)
// not element of `l`
e = &glist.TElement[any]{Value: 7}
l.MoveBefore(e, l.Front())
fmt.Println(l.Size())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// 6
// [1,2,3,4,5,6]
// 6
// [6,1,2,3,4,5]
// 6
// [6,1,2,3,4,5]
}
func ExampleTList_MoveAfter() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Size())
fmt.Println(l)
// element of `l`
e := l.PushFront(0)
fmt.Println(l.Size())
fmt.Println(l)
l.MoveAfter(e, l.Back())
fmt.Println(l.Size())
fmt.Println(l)
// not element of `l`
e = &glist.TElement[any]{Value: -1}
l.MoveAfter(e, l.Back())
fmt.Println(l.Size())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// 6
// [0,1,2,3,4,5]
// 6
// [1,2,3,4,5,0]
// 6
// [1,2,3,4,5,0]
}
func ExampleTList_MoveToFront() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Size())
fmt.Println(l)
// element of `l`
l.MoveToFront(l.Back())
fmt.Println(l.Size())
fmt.Println(l)
// not element of `l`
e := &glist.TElement[any]{Value: 6}
l.MoveToFront(e)
fmt.Println(l.Size())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// 5
// [5,1,2,3,4]
// 5
// [5,1,2,3,4]
}
func ExampleTList_MoveToBack() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Size())
fmt.Println(l)
// element of `l`
l.MoveToBack(l.Front())
fmt.Println(l.Size())
fmt.Println(l)
// not element of `l`
e := &glist.TElement[any]{Value: 0}
l.MoveToBack(e)
fmt.Println(l.Size())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// 5
// [2,3,4,5,1]
// 5
// [2,3,4,5,1]
}
func ExampleTList_PushBackList() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Size())
fmt.Println(l)
other := glist.NewTFrom[any](g.Slice{6, 7, 8, 9, 10})
fmt.Println(other.Size())
fmt.Println(other)
l.PushBackList(other)
fmt.Println(l.Size())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// 5
// [6,7,8,9,10]
// 10
// [1,2,3,4,5,6,7,8,9,10]
}
func ExampleTList_PushFrontList() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Size())
fmt.Println(l)
other := glist.NewTFrom[any](g.Slice{-4, -3, -2, -1, 0})
fmt.Println(other.Size())
fmt.Println(other)
l.PushFrontList(other)
fmt.Println(l.Size())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// 5
// [-4,-3,-2,-1,0]
// 10
// [-4,-3,-2,-1,0,1,2,3,4,5]
}
func ExampleTList_InsertAfter() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Len())
fmt.Println(l)
l.InsertAfter(l.Front(), "a")
l.InsertAfter(l.Back(), "b")
fmt.Println(l.Len())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// 7
// [1,a,2,3,4,5,b]
}
func ExampleTList_InsertBefore() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Len())
fmt.Println(l)
l.InsertBefore(l.Front(), "a")
l.InsertBefore(l.Back(), "b")
fmt.Println(l.Len())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// 7
// [a,1,2,3,4,b,5]
}
func ExampleTList_Remove() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Len())
fmt.Println(l)
fmt.Println(l.Remove(l.Front()))
fmt.Println(l.Remove(l.Back()))
fmt.Println(l.Len())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// 1
// 5
// 3
// [2,3,4]
}
func ExampleTList_Removes() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Len())
fmt.Println(l)
l.Removes([]*glist.TElement[any]{l.Front(), l.Back()})
fmt.Println(l.Len())
fmt.Println(l)
// Output:
// 5
// [1,2,3,4,5]
// 3
// [2,3,4]
}
func ExampleTList_RemoveAll() {
l := glist.NewTFrom[any](garray.NewArrayRange(1, 5, 1).Slice())
fmt.Println(l.Len())
fmt.Println(l)
l.RemoveAll()
fmt.Println(l.Len())
// Output:
// 5
// [1,2,3,4,5]
// 0
}
func ExampleTList_RLockFunc() {
// concurrent-safe list.
l := glist.NewTFrom[any](garray.NewArrayRange(1, 10, 1).Slice(), true)
// iterate reading from head.
l.RLockFunc(func(list *list.List) {
length := list.Len()
if length > 0 {
for i, e := 0, list.Front(); i < length; i, e = i+1, e.Next() {
fmt.Print(e.Value)
}
}
})
fmt.Println()
// iterate reading from tail.
l.RLockFunc(func(list *list.List) {
length := list.Len()
if length > 0 {
for i, e := 0, list.Back(); i < length; i, e = i+1, e.Prev() {
fmt.Print(e.Value)
}
}
})
fmt.Println()
// Output:
// 12345678910
// 10987654321
}
func ExampleTList_IteratorAsc() {
// concurrent-safe list.
l := glist.NewTFrom[any](garray.NewArrayRange(1, 10, 1).Slice(), true)
// iterate reading from head using IteratorAsc.
l.IteratorAsc(func(e *glist.TElement[any]) bool {
fmt.Print(e.Value)
return true
})
// Output:
// 12345678910
}
func ExampleTList_IteratorDesc() {
// concurrent-safe list.
l := glist.NewTFrom[any](garray.NewArrayRange(1, 10, 1).Slice(), true)
// iterate reading from tail using IteratorDesc.
l.IteratorDesc(func(e *glist.TElement[any]) bool {
fmt.Print(e.Value)
return true
})
// Output:
// 10987654321
}
func ExampleTList_LockFunc() {
// concurrent-safe list.
l := glist.NewTFrom[any](garray.NewArrayRange(1, 10, 1).Slice(), true)
// iterate writing from head.
l.LockFunc(func(list *list.List) {
length := list.Len()
if length > 0 {
for i, e := 0, list.Front(); i < length; i, e = i+1, e.Next() {
if e.Value == 6 {
e.Value = "M"
break
}
}
}
})
fmt.Println(l)
// Output:
// [1,2,3,4,5,M,7,8,9,10]
}
func ExampleTList_Join() {
var l glist.TList[any]
l.PushBacks(g.Slice{"a", "b", "c", "d"})
fmt.Println(l.Join(","))
// Output:
// a,b,c,d
}

View File

@ -0,0 +1,933 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package glist
import (
"container/list"
"testing"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/gconv"
)
func checkTListLen(t *gtest.T, l *TList[any], len int) bool {
if n := l.Len(); n != len {
t.Errorf("l.Len() = %d, want %d", n, len)
return false
}
return true
}
func checkTListPointers(t *gtest.T, l *TList[any], es []*TElement[any]) {
if !checkTListLen(t, l, len(es)) {
return
}
i := 0
l.Iterator(func(e *TElement[any]) bool {
if e.Prev() != es[i].Prev() {
t.Errorf("list[%d].Prev = %p, want %p", i, e.Prev(), es[i].Prev())
return false
}
if e.Next() != es[i].Next() {
t.Errorf("list[%d].Next = %p, want %p", i, e.Next(), es[i].Next())
return false
}
i++
return true
})
}
func TestTVar(t *testing.T) {
var l TList[any]
l.PushFront(1)
l.PushFront(2)
if v := l.PopBack(); v != 1 {
t.Errorf("EXPECT %v, GOT %v", 1, v)
} else {
// fmt.Println(v)
}
if v := l.PopBack(); v != 2 {
t.Errorf("EXPECT %v, GOT %v", 2, v)
} else {
// fmt.Println(v)
}
if v := l.PopBack(); v != nil {
t.Errorf("EXPECT %v, GOT %v", nil, v)
} else {
// fmt.Println(v)
}
l.PushBack(1)
l.PushBack(2)
if v := l.PopFront(); v != 1 {
t.Errorf("EXPECT %v, GOT %v", 1, v)
} else {
// fmt.Println(v)
}
if v := l.PopFront(); v != 2 {
t.Errorf("EXPECT %v, GOT %v", 2, v)
} else {
// fmt.Println(v)
}
if v := l.PopFront(); v != nil {
t.Errorf("EXPECT %v, GOT %v", nil, v)
} else {
// fmt.Println(v)
}
}
func TestTBasic(t *testing.T) {
l := NewT[any]()
l.PushFront(1)
l.PushFront(2)
if v := l.PopBack(); v != 1 {
t.Errorf("EXPECT %v, GOT %v", 1, v)
} else {
// fmt.Println(v)
}
if v := l.PopBack(); v != 2 {
t.Errorf("EXPECT %v, GOT %v", 2, v)
} else {
// fmt.Println(v)
}
if v := l.PopBack(); v != nil {
t.Errorf("EXPECT %v, GOT %v", nil, v)
} else {
// fmt.Println(v)
}
l.PushBack(1)
l.PushBack(2)
if v := l.PopFront(); v != 1 {
t.Errorf("EXPECT %v, GOT %v", 1, v)
} else {
// fmt.Println(v)
}
if v := l.PopFront(); v != 2 {
t.Errorf("EXPECT %v, GOT %v", 2, v)
} else {
// fmt.Println(v)
}
if v := l.PopFront(); v != nil {
t.Errorf("EXPECT %v, GOT %v", nil, v)
} else {
// fmt.Println(v)
}
}
func TestTList(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
checkTListPointers(t, l, []*TElement[any]{})
// Single element list
e := l.PushFront("a")
checkTListPointers(t, l, []*TElement[any]{e})
l.MoveToFront(e)
checkTListPointers(t, l, []*TElement[any]{e})
l.MoveToBack(e)
checkTListPointers(t, l, []*TElement[any]{e})
l.Remove(e)
checkTListPointers(t, l, []*TElement[any]{})
// Bigger list
e2 := l.PushFront(2)
e1 := l.PushFront(1)
e3 := l.PushBack(3)
e4 := l.PushBack("banana")
checkTListPointers(t, l, []*TElement[any]{e1, e2, e3, e4})
l.Remove(e2)
checkTListPointers(t, l, []*TElement[any]{e1, e3, e4})
l.MoveToFront(e3) // move from middle
checkTListPointers(t, l, []*TElement[any]{e3, e1, e4})
l.MoveToFront(e1)
l.MoveToBack(e3) // move from middle
checkTListPointers(t, l, []*TElement[any]{e1, e4, e3})
l.MoveToFront(e3) // move from back
checkTListPointers(t, l, []*TElement[any]{e3, e1, e4})
l.MoveToFront(e3) // should be no-op
checkTListPointers(t, l, []*TElement[any]{e3, e1, e4})
l.MoveToBack(e3) // move from front
checkTListPointers(t, l, []*TElement[any]{e1, e4, e3})
l.MoveToBack(e3) // should be no-op
checkTListPointers(t, l, []*TElement[any]{e1, e4, e3})
e2 = l.InsertBefore(e1, 2) // insert before front
checkTListPointers(t, l, []*TElement[any]{e2, e1, e4, e3})
l.Remove(e2)
e2 = l.InsertBefore(e4, 2) // insert before middle
checkTListPointers(t, l, []*TElement[any]{e1, e2, e4, e3})
l.Remove(e2)
e2 = l.InsertBefore(e3, 2) // insert before back
checkTListPointers(t, l, []*TElement[any]{e1, e4, e2, e3})
l.Remove(e2)
e2 = l.InsertAfter(e1, 2) // insert after front
checkTListPointers(t, l, []*TElement[any]{e1, e2, e4, e3})
l.Remove(e2)
e2 = l.InsertAfter(e4, 2) // insert after middle
checkTListPointers(t, l, []*TElement[any]{e1, e4, e2, e3})
l.Remove(e2)
e2 = l.InsertAfter(e3, 2) // insert after back
checkTListPointers(t, l, []*TElement[any]{e1, e4, e3, e2})
l.Remove(e2)
// Check standard iteration.
sum := 0
for e := l.Front(); e != nil; e = e.Next() {
if i, ok := e.Value.(int); ok {
sum += i
}
}
if sum != 4 {
t.Errorf("sum over l = %d, want 4", sum)
}
// Clear all elements by iterating
var next *TElement[any]
for e := l.Front(); e != nil; e = next {
next = e.Next()
l.Remove(e)
}
checkTListPointers(t, l, []*TElement[any]{})
})
}
func checkTList(t *gtest.T, l *TList[any], es []any) {
if !checkTListLen(t, l, len(es)) {
return
}
i := 0
for e := l.Front(); e != nil; e = e.Next() {
switch e.Value.(type) {
case int:
if le := e.Value.(int); le != es[i] {
t.Errorf("elt[%d].Value() = %v, want %v", i, le, es[i])
}
// default string
default:
if le := e.Value.(string); le != es[i] {
t.Errorf("elt[%v].Value() = %v, want %v", i, le, es[i])
}
}
i++
}
// for e := l.Front(); e != nil; e = e.Next() {
// le := e.Value.(int)
// if le != es[i] {
// t.Errorf("elt[%d].Value() = %v, want %v", i, le, es[i])
// }
// i++
// }
}
func TestTExtending(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l1 := NewT[any]()
l2 := NewT[any]()
l1.PushBack(1)
l1.PushBack(2)
l1.PushBack(3)
l2.PushBack(4)
l2.PushBack(5)
l3 := NewT[any]()
l3.PushBackList(l1)
checkTList(t, l3, []any{1, 2, 3})
l3.PushBackList(l2)
checkTList(t, l3, []any{1, 2, 3, 4, 5})
l3 = NewT[any]()
l3.PushFrontList(l2)
checkTList(t, l3, []any{4, 5})
l3.PushFrontList(l1)
checkTList(t, l3, []any{1, 2, 3, 4, 5})
checkTList(t, l1, []any{1, 2, 3})
checkTList(t, l2, []any{4, 5})
l3 = NewT[any]()
l3.PushBackList(l1)
checkTList(t, l3, []any{1, 2, 3})
l3.PushBackList(l3)
checkTList(t, l3, []any{1, 2, 3, 1, 2, 3})
l3 = NewT[any]()
l3.PushFrontList(l1)
checkTList(t, l3, []any{1, 2, 3})
l3.PushFrontList(l3)
checkTList(t, l3, []any{1, 2, 3, 1, 2, 3})
l3 = NewT[any]()
l1.PushBackList(l3)
checkTList(t, l1, []any{1, 2, 3})
l1.PushFrontList(l3)
checkTList(t, l1, []any{1, 2, 3})
})
}
func TestTRemove(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
e1 := l.PushBack(1)
e2 := l.PushBack(2)
checkTListPointers(t, l, []*TElement[any]{e1, e2})
// e := l.Front()
// l.Remove(e)
// checkTListPointers(t, l, []*TElement[any]{e2})
// l.Remove(e)
// checkTListPointers(t, l, []*TElement[any]{e2})
})
}
func Test_T_Issue4103(t *testing.T) {
l1 := NewT[any]()
l1.PushBack(1)
l1.PushBack(2)
l2 := NewT[any]()
l2.PushBack(3)
l2.PushBack(4)
e := l1.Front()
l2.Remove(e) // l2 should not change because e is not an element of l2
if n := l2.Len(); n != 2 {
t.Errorf("l2.Len() = %d, want 2", n)
}
l1.InsertBefore(e, 8)
if n := l1.Len(); n != 3 {
t.Errorf("l1.Len() = %d, want 3", n)
}
}
func Test_T_Issue6349(t *testing.T) {
l := NewT[any]()
l.PushBack(1)
l.PushBack(2)
e := l.Front()
l.Remove(e)
if e.Value != 1 {
t.Errorf("e.value = %d, want 1", e.Value)
}
// if e.Next() != nil {
// t.Errorf("e.Next() != nil")
// }
// if e.Prev() != nil {
// t.Errorf("e.Prev() != nil")
// }
}
func TestTMove(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
e1 := l.PushBack(1)
e2 := l.PushBack(2)
e3 := l.PushBack(3)
e4 := l.PushBack(4)
l.MoveAfter(e3, e3)
checkTListPointers(t, l, []*TElement[any]{e1, e2, e3, e4})
l.MoveBefore(e2, e2)
checkTListPointers(t, l, []*TElement[any]{e1, e2, e3, e4})
l.MoveAfter(e3, e2)
checkTListPointers(t, l, []*TElement[any]{e1, e2, e3, e4})
l.MoveBefore(e2, e3)
checkTListPointers(t, l, []*TElement[any]{e1, e2, e3, e4})
l.MoveBefore(e2, e4)
checkTListPointers(t, l, []*TElement[any]{e1, e3, e2, e4})
e2, e3 = e3, e2
l.MoveBefore(e4, e1)
checkTListPointers(t, l, []*TElement[any]{e4, e1, e2, e3})
e1, e2, e3, e4 = e4, e1, e2, e3
l.MoveAfter(e4, e1)
checkTListPointers(t, l, []*TElement[any]{e1, e4, e2, e3})
e2, e3, e4 = e4, e2, e3
l.MoveAfter(e2, e3)
checkTListPointers(t, l, []*TElement[any]{e1, e3, e2, e4})
e2, e3 = e3, e2
})
}
// Test PushFront, PushBack, PushFrontList, PushBackList with uninitialized List
func TestTZeroList(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var l1 = NewT[any]()
l1.PushFront(1)
checkTList(t, l1, []any{1})
var l2 = NewT[any]()
l2.PushBack(1)
checkTList(t, l2, []any{1})
var l3 = NewT[any]()
l3.PushFrontList(l1)
checkTList(t, l3, []any{1})
var l4 = NewT[any]()
l4.PushBackList(l2)
checkTList(t, l4, []any{1})
})
}
// Test that a list l is not modified when calling InsertBefore with a mark that is not an element of l.
func TestTInsertBeforeUnknownMark(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.InsertBefore(new(TElement[any]), 1)
checkTList(t, l, []any{1, 2, 3})
})
}
// Test that a list l is not modified when calling InsertAfter with a mark that is not an element of l.
func TestTInsertAfterUnknownMark(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.InsertAfter(new(TElement[any]), 1)
checkTList(t, l, []any{1, 2, 3})
})
}
// Test that a list l is not modified when calling MoveAfter or MoveBefore with a mark that is not an element of l.
func TestTMoveUnknownMark(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l1 := NewT[any]()
e1 := l1.PushBack(1)
l2 := NewT[any]()
e2 := l2.PushBack(2)
l1.MoveAfter(e1, e2)
checkTList(t, l1, []any{1})
checkTList(t, l2, []any{2})
l1.MoveBefore(e1, e2)
checkTList(t, l1, []any{1})
checkTList(t, l2, []any{2})
})
}
func TestTList_RemoveAll(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
l.PushBack(1)
l.RemoveAll()
checkTList(t, l, []any{})
l.PushBack(2)
checkTList(t, l, []any{2})
})
}
func TestTList_PushFronts(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
a1 := []any{1, 2}
l.PushFronts(a1)
checkTList(t, l, []any{2, 1})
a1 = []any{3, 4, 5}
l.PushFronts(a1)
checkTList(t, l, []any{5, 4, 3, 2, 1})
})
}
func TestTList_PushBacks(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
a1 := []any{1, 2}
l.PushBacks(a1)
checkTList(t, l, []any{1, 2})
a1 = []any{3, 4, 5}
l.PushBacks(a1)
checkTList(t, l, []any{1, 2, 3, 4, 5})
})
}
func TestTList_PopBacks(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
a1 := []any{1, 2, 3, 4}
a2 := []any{"a", "c", "b", "e"}
l.PushFronts(a1)
i1 := l.PopBacks(2)
t.Assert(i1, []any{1, 2})
l.PushBacks(a2) // 4.3,a,c,b,e
i1 = l.PopBacks(3)
t.Assert(i1, []any{"e", "b", "c"})
})
}
func TestTList_PopFronts(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
a1 := []any{1, 2, 3, 4}
l.PushFronts(a1)
i1 := l.PopFronts(2)
t.Assert(i1, []any{4, 3})
t.Assert(l.Len(), 2)
})
}
func TestTList_PopBackAll(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
a1 := []any{1, 2, 3, 4}
l.PushFronts(a1)
i1 := l.PopBackAll()
t.Assert(i1, []any{1, 2, 3, 4})
t.Assert(l.Len(), 0)
})
}
func TestTList_PopFrontAll(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
a1 := []any{1, 2, 3, 4}
l.PushFronts(a1)
i1 := l.PopFrontAll()
t.Assert(i1, []any{4, 3, 2, 1})
t.Assert(l.Len(), 0)
})
}
func TestTList_FrontAll(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
a1 := []any{1, 2, 3, 4}
l.PushFronts(a1)
i1 := l.FrontAll()
t.Assert(i1, []any{4, 3, 2, 1})
t.Assert(l.Len(), 4)
})
}
func TestTList_BackAll(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
a1 := []any{1, 2, 3, 4}
l.PushFronts(a1)
i1 := l.BackAll()
t.Assert(i1, []any{1, 2, 3, 4})
t.Assert(l.Len(), 4)
})
}
func TestTList_FrontValue(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
l2 := NewT[any]()
a1 := []any{1, 2, 3, 4}
l.PushFronts(a1)
i1 := l.FrontValue()
t.Assert(gconv.Int(i1), 4)
t.Assert(l.Len(), 4)
i1 = l2.FrontValue()
t.Assert(i1, nil)
})
}
func TestTList_BackValue(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
l2 := NewT[any]()
a1 := []any{1, 2, 3, 4}
l.PushFronts(a1)
i1 := l.BackValue()
t.Assert(gconv.Int(i1), 1)
t.Assert(l.Len(), 4)
i1 = l2.FrontValue()
t.Assert(i1, nil)
})
}
func TestTList_Back(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
a1 := []any{1, 2, 3, 4}
l.PushFronts(a1)
e1 := l.Back()
t.Assert(e1.Value, 1)
t.Assert(l.Len(), 4)
})
}
func TestTList_Size(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
a1 := []any{1, 2, 3, 4}
l.PushFronts(a1)
t.Assert(l.Size(), 4)
l.PopFront()
t.Assert(l.Size(), 3)
})
}
func TestTList_Removes(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
a1 := []any{1, 2, 3, 4}
l.PushFronts(a1)
e1 := l.Back()
l.Removes([]*TElement[any]{e1})
t.Assert(l.Len(), 3)
e2 := l.Back()
l.Removes([]*TElement[any]{e2})
t.Assert(l.Len(), 2)
checkTList(t, l, []any{4, 3})
})
}
func TestTList_Pop(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewTFrom([]any{1, 2, 3, 4, 5, 6, 7, 8, 9})
t.Assert(l.PopBack(), 9)
t.Assert(l.PopBacks(2), []any{8, 7})
t.Assert(l.PopFront(), 1)
t.Assert(l.PopFronts(2), []any{2, 3})
})
}
func TestTList_Clear(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
a1 := []any{1, 2, 3, 4}
l.PushFronts(a1)
l.Clear()
t.Assert(l.Len(), 0)
})
}
func TestTList_IteratorAsc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
a1 := []any{1, 2, 5, 6, 3, 4}
l.PushFronts(a1)
e1 := l.Back()
fun1 := func(e *TElement[any]) bool {
return gconv.Int(e1.Value) > 2
}
checkTList(t, l, []any{4, 3, 6, 5, 2, 1})
l.IteratorAsc(fun1)
checkTList(t, l, []any{4, 3, 6, 5, 2, 1})
})
}
func TestTList_IteratorDesc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
a1 := []any{1, 2, 3, 4}
l.PushFronts(a1)
e1 := l.Back()
fun1 := func(e *TElement[any]) bool {
return gconv.Int(e1.Value) > 6
}
l.IteratorDesc(fun1)
t.Assert(l.Len(), 4)
checkTList(t, l, []any{4, 3, 2, 1})
})
}
func TestTList_Iterator(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
a1 := []any{"a", "b", "c", "d", "e"}
l.PushFronts(a1)
e1 := l.Back()
fun1 := func(e *TElement[any]) bool {
return gconv.String(e1.Value) > "c"
}
checkTList(t, l, []any{"e", "d", "c", "b", "a"})
l.Iterator(fun1)
checkTList(t, l, []any{"e", "d", "c", "b", "a"})
})
}
func TestTList_Join(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewTFrom([]any{1, 2, "a", `"b"`, `\c`})
t.Assert(l.Join(","), `1,2,a,"b",\c`)
t.Assert(l.Join("."), `1.2.a."b".\c`)
})
}
func TestTList_String(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewTFrom([]any{1, 2, "a", `"b"`, `\c`})
t.Assert(l.String(), `[1,2,a,"b",\c]`)
})
}
func TestTList_Json(t *testing.T) {
// Marshal
gtest.C(t, func(t *gtest.T) {
a := []any{"a", "b", "c"}
l := NewT[any]()
l.PushBacks(a)
b1, err1 := json.Marshal(l)
b2, err2 := json.Marshal(a)
t.Assert(err1, err2)
t.Assert(b1, b2)
})
// Unmarshal
gtest.C(t, func(t *gtest.T) {
a := []any{"a", "b", "c"}
l := NewT[any]()
b, err := json.Marshal(a)
t.AssertNil(err)
err = json.UnmarshalUseNumber(b, l)
t.AssertNil(err)
t.Assert(l.FrontAll(), a)
})
gtest.C(t, func(t *gtest.T) {
var l TList[any]
a := []any{"a", "b", "c"}
b, err := json.Marshal(a)
t.AssertNil(err)
err = json.UnmarshalUseNumber(b, &l)
t.AssertNil(err)
t.Assert(l.FrontAll(), a)
})
}
func TestTList_UnmarshalValue(t *testing.T) {
type list struct {
Name string
List *TList[any]
}
// JSON
gtest.C(t, func(t *gtest.T) {
var tlist *list
err := gconv.Struct(map[string]any{
"name": "john",
"list": []byte(`[1,2,3]`),
}, &tlist)
t.AssertNil(err)
t.Assert(tlist.Name, "john")
t.Assert(tlist.List.FrontAll(), []any{1, 2, 3})
})
// Map
gtest.C(t, func(t *gtest.T) {
var tlist *list
err := gconv.Struct(map[string]any{
"name": "john",
"list": []any{1, 2, 3},
}, &tlist)
t.AssertNil(err)
t.Assert(tlist.Name, "john")
t.Assert(tlist.List.FrontAll(), []any{1, 2, 3})
})
}
func TestTList_DeepCopy(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewTFrom([]any{1, 2, "a", `"b"`, `\c`})
copyList := l.DeepCopy()
cl := copyList.(*TList[any])
cl.PopBack()
t.AssertNE(l.Size(), cl.Size())
})
// Nil pointer deep copy
gtest.C(t, func(t *gtest.T) {
var l *TList[any]
copyList := l.DeepCopy()
t.AssertNil(copyList)
})
}
func TestTList_ToList(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewTFrom([]any{1, 2, 3, 4, 5})
nl := l.ToList()
t.Assert(nl.Len(), 5)
// Verify elements
i := 1
for e := nl.Front(); e != nil; e = e.Next() {
t.Assert(e.Value, i)
i++
}
})
// Empty list
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
nl := l.ToList()
t.Assert(nl.Len(), 0)
})
}
func TestTList_AppendList(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewTFrom([]any{1, 2, 3})
nl := list.New()
nl.PushBack(4)
nl.PushBack(5)
l.AppendList(nl)
t.Assert(l.Len(), 5)
t.Assert(l.FrontAll(), []any{1, 2, 3, 4, 5})
})
// Append empty list
gtest.C(t, func(t *gtest.T) {
l := NewTFrom([]any{1, 2, 3})
nl := list.New()
l.AppendList(nl)
t.Assert(l.Len(), 3)
t.Assert(l.FrontAll(), []any{1, 2, 3})
})
// Append with type mismatch (should skip)
gtest.C(t, func(t *gtest.T) {
l := NewT[int]()
nl := list.New()
nl.PushBack(1)
nl.PushBack("string") // type mismatch
nl.PushBack(2)
l.AppendList(nl)
t.Assert(l.Len(), 2)
t.Assert(l.FrontAll(), []int{1, 2})
})
}
func TestTList_AssignList(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewTFrom([]any{1, 2, 3})
nl := list.New()
nl.PushBack(4)
nl.PushBack(5)
nl.PushBack(6)
skipped := l.AssignList(nl)
t.Assert(skipped, 0)
t.Assert(l.Len(), 3)
t.Assert(l.FrontAll(), []any{4, 5, 6})
})
// Assign empty list
gtest.C(t, func(t *gtest.T) {
l := NewTFrom([]any{1, 2, 3})
nl := list.New()
skipped := l.AssignList(nl)
t.Assert(skipped, 0)
t.Assert(l.Len(), 0)
})
// Assign with type mismatch (should return skipped count)
gtest.C(t, func(t *gtest.T) {
l := NewT[int]()
nl := list.New()
nl.PushBack(1)
nl.PushBack("string") // type mismatch
nl.PushBack(2)
nl.PushBack("another") // type mismatch
skipped := l.AssignList(nl)
t.Assert(skipped, 2)
t.Assert(l.Len(), 2)
t.Assert(l.FrontAll(), []int{1, 2})
})
}
func TestTList_String_Nil(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var l *TList[any]
t.Assert(l.String(), "")
})
}
func TestTList_UnmarshalJSON_Error(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
err := l.UnmarshalJSON([]byte("invalid json"))
t.AssertNE(err, nil)
})
}
func TestTList_UnmarshalValue_String(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
err := l.UnmarshalValue(`[1,2,3]`)
t.AssertNil(err)
t.Assert(l.FrontAll(), []any{1, 2, 3})
})
}
func TestTList_UnmarshalValue_Bytes(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
err := l.UnmarshalValue([]byte(`[1,2,3]`))
t.AssertNil(err)
t.Assert(l.FrontAll(), []any{1, 2, 3})
})
}
func TestTList_DeepCopy_Empty(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
copyList := l.DeepCopy()
cl := copyList.(*TList[any])
t.Assert(cl.Len(), 0)
})
}
func TestTList_AppendList_WithTypeMismatch(t *testing.T) {
// Test appendList internal function through AppendList with mixed types
gtest.C(t, func(t *gtest.T) {
l := NewT[int]()
nl := list.New()
// Only add non-matching types
nl.PushBack("string1")
nl.PushBack("string2")
l.AppendList(nl)
t.Assert(l.Len(), 0)
})
}
func TestTList_UnmarshalValue_Error(t *testing.T) {
// Test UnmarshalValue with data through default case
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
// Pass a slice directly through default case
_ = l.UnmarshalValue([]any{1, 2, 3})
t.Assert(l.Len(), 3)
t.Assert(l.FrontAll(), []any{1, 2, 3})
})
// Test UnmarshalValue error in string case
gtest.C(t, func(t *gtest.T) {
l := NewT[any]()
err := l.UnmarshalValue("invalid json")
t.AssertNE(err, nil)
})
}

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package gmap provides most commonly used map container which also support concurrent-safe/unsafe switch feature.

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
//

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
//

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
//

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
//

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gpool_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gset_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gset_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gset_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gtree_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gtree_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gtree_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/Agogf/gf.
package gtree_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gtree_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/Agogf/gf.
package gtree_test

View File

@ -1,7 +1,7 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with gm file,
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gtree_test

View File

@ -19,6 +19,7 @@ import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
)
@ -28,6 +29,10 @@ var (
_ gcfg.WatcherAdapter = (*Client)(nil)
)
const (
apolloNamespaceDelimiter = ","
)
// Config is the configuration object for apollo client.
type Config struct {
AppID string `v:"required"` // See apolloConfig.Config.
@ -97,11 +102,19 @@ func (c *Client) Available(ctx context.Context, resource ...string) (ok bool) {
if len(resource) == 0 && !c.value.IsNil() {
return true
}
namespace := c.config.NamespaceName
namespaces := gstr.SplitAndTrim(c.config.NamespaceName, apolloNamespaceDelimiter)
if len(resource) > 0 {
namespace = resource[0]
namespaces = resource
}
return c.client.GetConfig(namespace) != nil
for _, namespace := range namespaces {
if c.client.GetConfig(namespace) == nil {
return false
}
}
return true
}
// Get retrieves and returns value by specified `pattern` in current resource.
@ -142,19 +155,20 @@ func (c *Client) OnNewestChange(event *storage.FullChangeEvent) {
func (c *Client) updateLocalValue(ctx context.Context) (err error) {
j := gjson.New(nil)
content := gjson.New(nil, true)
cache := c.client.GetConfigCache(c.config.NamespaceName)
cache.Range(func(key, value any) bool {
err = j.Set(gconv.String(key), value)
if err != nil {
return false
}
err = content.Set(gconv.String(key), value)
if err != nil {
return false
}
return true
})
cache.Clear()
for _, namespace := range gstr.SplitAndTrim(c.config.NamespaceName, apolloNamespaceDelimiter) {
cache := c.client.GetConfigCache(namespace)
cache.Range(func(key, value any) bool {
err = j.Set(gconv.String(key), value)
if err != nil {
return false
}
err = content.Set(gconv.String(key), value)
return err == nil
})
cache.Clear()
}
if err == nil {
c.value.Set(j)
adapterCtx := NewAdapterCtx(ctx).WithOperation(gcfg.OperationUpdate).WithNamespace(c.config.NamespaceName).

View File

@ -15,7 +15,7 @@ import (
"github.com/gogf/gf/v2/os/gctx"
)
// Driver is the driver for postgresql database.
// Driver is the driver for clickhouse database.
type Driver struct {
*gdb.Core
}
@ -29,12 +29,11 @@ var (
)
const (
updateFilterPattern = `(?i)UPDATE[\s]+?(\w+[\.]?\w+)[\s]+?SET`
deleteFilterPattern = `(?i)DELETE[\s]+?FROM[\s]+?(\w+[\.]?\w+)`
filterTypePattern = `(?i)^UPDATE|DELETE`
replaceSchemaPattern = `@(.+?)/([\w\.\-]+)+`
needParsedSqlInCtx gctx.StrKey = "NeedParsedSql"
driverName = "clickhouse"
updateFilterPattern = `(?i)UPDATE[\s]+?(\w+[\.]?\w+)[\s]+?SET`
deleteFilterPattern = `(?i)DELETE[\s]+?FROM[\s]+?(\w+[\.]?\w+)`
filterTypePattern = `(?i)^UPDATE|DELETE`
needParsedSqlInCtx gctx.StrKey = "NeedParsedSql"
driverName = "clickhouse"
)
func init() {

View File

@ -26,6 +26,7 @@ func (d *Driver) ConvertValueForField(ctx context.Context, fieldType string, fie
if itemValue.IsZero() {
return nil, nil
}
return itemValue, nil
case uuid.UUID:
return itemValue, nil
@ -48,15 +49,13 @@ func (d *Driver) ConvertValueForField(ctx context.Context, fieldType string, fie
return itemValue.Time, nil
case *gtime.Time:
// for gtime type, needs to get time.Time
if itemValue != nil {
return itemValue.Time, nil
}
// If the time is zero, it then updates it to nil,
// which will insert/update the value to database as "null".
if itemValue == nil || itemValue.IsZero() {
return nil, nil
}
// for gtime type, needs to get time.Time
return itemValue.Time, nil
case decimal.Decimal:
return itemValue, nil
@ -81,5 +80,4 @@ func (d *Driver) ConvertValueForField(ctx context.Context, fieldType string, fie
}
return convertedValue, nil
}
return fieldValue, nil
}

View File

@ -73,13 +73,11 @@ func (d *Driver) DoFilter(
}
return newSql, args, nil
default:
return originSql, args, nil
}
return originSql, args, nil
}
func (d *Driver) getNeedParsedSqlFromCtx(ctx context.Context) bool {
if ctx.Value(needParsedSqlInCtx) != nil {
return true
}
return false
return ctx.Value(needParsedSqlInCtx) != nil
}

View File

@ -19,10 +19,8 @@ import (
func (d *Driver) DoInsert(
ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption,
) (result sql.Result, err error) {
var (
keys []string // Field names.
valueHolder = make([]string, 0)
)
var keys, valueHolder []string
// Handle the field names and placeholders.
for k := range list[0] {
keys = append(keys, k)
@ -56,7 +54,12 @@ func (d *Driver) DoInsert(
if err != nil {
return
}
for i := 0; i < len(list); i++ {
defer func() {
_ = stmt.Close()
}()
for i := range len(list) {
// Values that will be committed to underlying database driver.
params := make([]any, 0)
for _, k := range keys {

View File

@ -303,6 +303,8 @@ func Test_DB_Tables(t *testing.T) {
createTable(v)
}
defer dropTable(tables...)
result, err := db.Tables(ctx)
gtest.AssertNil(err)

View File

@ -52,8 +52,10 @@ func createInitTable(table ...string) string {
return createInitTableWithDb(db, table...)
}
func dropTable(table string) {
dropTableWithDb(db, table)
func dropTable(tables ...string) {
for _, table := range tables {
dropTableWithDb(db, table)
}
}
func createTableWithDb(db gdb.DB, table ...string) (name string) {

View File

@ -8,7 +8,6 @@ package clickhouse
import (
"context"
"fmt"
"testing"
"time"
@ -166,22 +165,22 @@ func createClickhouseExampleTable(connect gdb.DB) error {
}
func dropClickhouseTableVisits(conn gdb.DB) {
sqlStr := fmt.Sprintf("DROP TABLE IF EXISTS `visits`")
sqlStr := "DROP TABLE IF EXISTS `visits`"
_, _ = conn.Exec(context.Background(), sqlStr)
}
func dropClickhouseTableDim(conn gdb.DB) {
sqlStr := fmt.Sprintf("DROP TABLE IF EXISTS `dim`")
sqlStr := "DROP TABLE IF EXISTS `dim`"
_, _ = conn.Exec(context.Background(), sqlStr)
}
func dropClickhouseTableFact(conn gdb.DB) {
sqlStr := fmt.Sprintf("DROP TABLE IF EXISTS `fact`")
sqlStr := "DROP TABLE IF EXISTS `fact`"
_, _ = conn.Exec(context.Background(), sqlStr)
}
func dropClickhouseExampleTable(conn gdb.DB) {
sqlStr := fmt.Sprintf("DROP TABLE IF EXISTS `data_type`")
sqlStr := "DROP TABLE IF EXISTS `data_type`"
_, _ = conn.Exec(context.Background(), sqlStr)
}

View File

@ -8,13 +8,60 @@ package mysql
import (
"context"
"strings"
"github.com/gogf/gf/v2/database/gdb"
)
// DoFilter handles the sql before posts it to database.
// This method helps handle MySQL-specific issues including key length limitations.
//
// For MySQL tables using utf8mb4 charset, this method automatically adds ROW_FORMAT=DYNAMIC
// to CREATE TABLE statements to prevent "Specified key was too long; max key length is 1000 bytes" errors.
// This is particularly important for compatibility when upgrading from older GoFrame versions.
func (d *Driver) DoFilter(
ctx context.Context, link gdb.Link, sql string, args []any,
) (newSql string, newArgs []any, err error) {
return d.Core.DoFilter(ctx, link, sql, args)
newSql, newArgs, err = d.Core.DoFilter(ctx, link, sql, args)
if err != nil {
return newSql, newArgs, err
}
// Handle MySQL-specific SQL filtering to prevent key length issues
// This is particularly important for compatibility between GoFrame versions
newSql = d.handleMySQLKeyLengthCompatibility(newSql)
return newSql, newArgs, err
}
// handleMySQLKeyLengthCompatibility modifies SQL statements to be more compatible
// with MySQL key length limitations, especially for upgrade scenarios from older GoFrame versions
func (d *Driver) handleMySQLKeyLengthCompatibility(sql string) string {
// For CREATE TABLE statements with utf8mb4 charset, ensure key length compatibility
// This helps prevent "Specified key was too long; max key length is 1000 bytes" errors
sqlUpper := strings.ToUpper(sql)
sqlLower := strings.ToLower(sql)
if strings.Contains(sqlUpper, "CREATE TABLE") &&
(strings.Contains(sqlLower, "utf8mb4") || strings.Contains(sqlLower, "charset=utf8mb4")) {
// Add ROW_FORMAT=DYNAMIC to enable larger key prefixes when using utf8mb4
if !strings.Contains(sqlUpper, "ROW_FORMAT") {
// Insert ROW_FORMAT=DYNAMIC before ENGINE clause if it exists
if strings.Contains(sqlUpper, "ENGINE=") {
sql = strings.Replace(sql, "ENGINE=", "ROW_FORMAT=DYNAMIC ENGINE=", 1)
} else if strings.Contains(sqlUpper, "ENGINE ") {
// Handle case where there's a space after ENGINE
sql = strings.Replace(sql, "ENGINE ", "ROW_FORMAT=DYNAMIC ENGINE ", 1)
} else {
// Append ROW_FORMAT=DYNAMIC at the end of CREATE TABLE statement
sql = strings.TrimSuffix(strings.TrimSpace(sql), ";")
sql += " ROW_FORMAT=DYNAMIC"
if !strings.HasSuffix(sql, ";") {
sql += ";"
}
}
}
}
return sql
}

View File

@ -47,12 +47,14 @@ func configNodeToSource(config *gdb.ConfigNode) string {
"%s:%s@%s(%s%s)/%s?charset=%s",
config.User, config.Pass, config.Protocol, config.Host, portStr, config.Name, config.Charset,
)
if config.Timezone != "" {
if strings.Contains(config.Timezone, "/") {
config.Timezone = url.QueryEscape(config.Timezone)
}
source = fmt.Sprintf("%s&loc=%s", source, config.Timezone)
}
if config.Extra != "" {
source = fmt.Sprintf("%s&%s", source, config.Extra)
}

View File

@ -0,0 +1,47 @@
// Test case for MySQL key length issue #4382
package mysql_test
import (
"testing"
"github.com/gogf/gf/v2/test/gtest"
)
// Test_Issue4382_KeyLengthLimit tests the MySQL key length limitation issue
// This test reproduces the issue reported in #4382 where upgrading from GoFrame 2.6 to 2.9
// causes "Specified key was too long; max key length is 1000 bytes" error
func Test_Issue4382_KeyLengthLimit(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// This test should not fail due to key length limitations
// when using proper MySQL configuration
table := createTable("test_key_length")
defer dropTable(table)
// Try to create a table with potentially long keys (using utf8mb4)
// This scenario could trigger the key length issue
longTableSQL := `
CREATE TABLE test_long_keys (
id INT PRIMARY KEY AUTO_INCREMENT,
long_field_1 VARCHAR(255) CHARACTER SET utf8mb4,
long_field_2 VARCHAR(255) CHARACTER SET utf8mb4,
KEY idx_long_composite (long_field_1, long_field_2)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
`
_, err := db.Exec(ctx, "DROP TABLE IF EXISTS test_long_keys")
t.AssertNil(err)
// This should not fail with key length error in GoFrame 2.9
// Our DoFilter enhancement should automatically add ROW_FORMAT=DYNAMIC
_, err = db.Exec(ctx, longTableSQL)
if err != nil {
// If we get the specific key length error, this confirms the issue
// With our fix, this should not happen
t.Logf("Error creating table: %v", err)
}
t.AssertNil(err)
// Clean up
db.Exec(ctx, "DROP TABLE IF EXISTS test_long_keys")
})
}

View File

@ -1 +1 @@
SELECT managed_resource.resource_id,managed_resource.user,managed_resource.status,managed_resource.status_message,managed_resource.safe_publication,managed_resource.rule_template_id,managed_resource.created_at,managed_resource.comments,managed_resource.expired_at,managed_resource.resource_mark_id,managed_resource.instance_id,managed_resource.resource_name,managed_resource.pay_mode,resource_mark.mark_name,resource_mark.color,rules_template.name,common_resource.src_instance_id,common_resource.database_kind,common_resource.source_type,common_resource.ip,common_resource.port FROM `managed_resource` LEFT JOIN `common_resource` ON (`managed_resource`.`resource_id`=`common_resource`.`resource_id`) LEFT JOIN `resource_mark` ON (`managed_resource`.`resource_mark_id` = `resource_mark`.`id`) LEFT JOIN `rules_template` ON (`managed_resource`.`rule_template_id` = `rules_template`.`template_id`) ORDER BY `src_instance_id` ASC
SELECT `managed_resource`.`resource_id`,`managed_resource`.`user`,`managed_resource`.`status`,`managed_resource`.`status_message`,`managed_resource`.`safe_publication`,`managed_resource`.`rule_template_id`,`managed_resource`.`created_at`,`managed_resource`.`comments`,`managed_resource`.`expired_at`,`managed_resource`.`resource_mark_id`,`managed_resource`.`instance_id`,`managed_resource`.`resource_name`,`managed_resource`.`pay_mode`,`resource_mark`.`mark_name`,`resource_mark`.`color`,`rules_template`.`name`,`common_resource`.`src_instance_id`,`common_resource`.`database_kind`,`common_resource`.`source_type`,`common_resource`.`ip`,`common_resource`.`port` FROM `managed_resource` LEFT JOIN `common_resource` ON (`managed_resource`.`resource_id`=`common_resource`.`resource_id`) LEFT JOIN `resource_mark` ON (`managed_resource`.`resource_mark_id` = `resource_mark`.`id`) LEFT JOIN `rules_template` ON (`managed_resource`.`rule_template_id` = `rules_template`.`template_id`) ORDER BY `src_instance_id` ASC

View File

@ -77,6 +77,8 @@ func (d *Driver) CheckLocalTypeForField(ctx context.Context, fieldType string, f
case
"_varchar", "_text":
return gdb.LocalTypeStringSlice, nil
case "_numeric", "_decimal":
return gdb.LocalTypeFloat64Slice, nil
default:
return d.Core.CheckLocalTypeForField(ctx, fieldType, fieldValue)
@ -130,6 +132,13 @@ func (d *Driver) ConvertValueForLocal(ctx context.Context, fieldType string, fie
}
return []string(result), nil
// Float64 slice.
case "_numeric", "_decimal":
var result pq.Float64Array
if err := result.Scan(fieldValue); err != nil {
return nil, err
}
return []float64(result), nil
default:
return d.Core.ConvertValueForLocal(ctx, fieldType, fieldValue)
}

View File

@ -27,7 +27,7 @@ FROM pg_attribute a
left join pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid
left join pg_type t ON a.atttypid = t.oid
left join information_schema.columns ic on ic.column_name = a.attname and ic.table_name = c.relname
WHERE c.relname = '%s' and a.attisdropped is false and a.attnum > 0
WHERE c.oid = '%s'::regclass and a.attisdropped is false and a.attnum > 0
ORDER BY a.attnum`
)

View File

@ -339,8 +339,8 @@ int_col INT);`
IntCol int64
}
// pgsql converts table names to lowercase
// mark: [c.oid = '%s'::regclass] is not case-sensitive
tableName := "Error_table"
errStr := fmt.Sprintf(`The table "%s" may not exist, or the table contains no fields`, tableName)
_, err := db.Exec(ctx, fmt.Sprintf(createSql, tableName))
gtest.AssertNil(err)
defer dropTable(tableName)
@ -351,7 +351,7 @@ int_col INT);`
IntCol: 2,
}
_, err = db.Model(tableName).Data(data).Insert()
t.Assert(err, errStr)
t.AssertNE(err, nil)
// Insert a piece of test data using lowercase
_, err = db.Model(strings.ToLower(tableName)).Data(data).Insert()
@ -360,7 +360,7 @@ int_col INT);`
_, err = db.Model(tableName).Where("id", 1).Data(g.Map{
"int_col": 9999,
}).Update()
t.Assert(err, errStr)
t.AssertNE(err, nil)
})
// The inserted field does not exist in the table
@ -370,7 +370,7 @@ int_col INT);`
"int_col_22": 11111,
}
_, err = db.Model(tableName).Data(data).Insert()
t.Assert(err, errStr)
t.Assert(err, fmt.Errorf(`input data match no fields in table "%s"`, tableName))
lowerTableName := strings.ToLower(tableName)
_, err = db.Model(lowerTableName).Data(data).Insert()

View File

@ -85,6 +85,8 @@ func createTableWithDb(db gdb.DB, table ...string) (name string) {
create_time timestamp NOT NULL,
favorite_movie varchar[],
favorite_music text[],
numeric_values numeric[],
decimal_values decimal[],
PRIMARY KEY (id)
) ;`, name,
)); err != nil {

View File

@ -692,3 +692,68 @@ func Test_ConvertSliceString(t *testing.T) {
t.Assert(len(user2.FavoriteMovie), 0)
})
}
func Test_ConvertSliceFloat64(t *testing.T) {
table := createTable()
defer dropTable(table)
type Args struct {
NumericValues []float64 `orm:"numeric_values"`
DecimalValues []float64 `orm:"decimal_values"`
}
type User struct {
Id int `orm:"id"`
Passport string `orm:"passport"`
Password string `json:"password"`
NickName string `json:"nickname"`
CreateTime *gtime.Time `json:"create_time"`
Args
}
tests := []struct {
name string
args Args
}{
{
name: "nil",
args: Args{
NumericValues: nil,
DecimalValues: nil,
},
},
{
name: "not nil",
args: Args{
NumericValues: []float64{1.1, 2.2, 3.3},
DecimalValues: []float64{1.1, 2.2, 3.3},
},
},
{
name: "not empty",
args: Args{
NumericValues: []float64{},
DecimalValues: []float64{},
},
},
}
now := gtime.New(CreateTime)
for i, tt := range tests {
gtest.C(t, func(t *gtest.T) {
user := User{
Id: i + 1,
Passport: "",
Password: "",
NickName: "",
CreateTime: now,
Args: tt.args,
}
_, err := db.Model(table).OmitNilData().Insert(user)
t.AssertNil(err)
var got Args
err = db.Model(table).Where("id", user.Id).Limit(1).Scan(&got)
t.AssertNil(err)
t.AssertEQ(tt.args, got)
})
}
}

View File

@ -787,28 +787,29 @@ const (
type LocalType string
const (
LocalTypeUndefined LocalType = ""
LocalTypeString LocalType = "string"
LocalTypeTime LocalType = "time"
LocalTypeDate LocalType = "date"
LocalTypeDatetime LocalType = "datetime"
LocalTypeInt LocalType = "int"
LocalTypeUint LocalType = "uint"
LocalTypeInt64 LocalType = "int64"
LocalTypeUint64 LocalType = "uint64"
LocalTypeBigInt LocalType = "bigint"
LocalTypeIntSlice LocalType = "[]int"
LocalTypeInt64Slice LocalType = "[]int64"
LocalTypeUint64Slice LocalType = "[]uint64"
LocalTypeStringSlice LocalType = "[]string"
LocalTypeInt64Bytes LocalType = "int64-bytes"
LocalTypeUint64Bytes LocalType = "uint64-bytes"
LocalTypeFloat32 LocalType = "float32"
LocalTypeFloat64 LocalType = "float64"
LocalTypeBytes LocalType = "[]byte"
LocalTypeBool LocalType = "bool"
LocalTypeJson LocalType = "json"
LocalTypeJsonb LocalType = "jsonb"
LocalTypeUndefined LocalType = ""
LocalTypeString LocalType = "string"
LocalTypeTime LocalType = "time"
LocalTypeDate LocalType = "date"
LocalTypeDatetime LocalType = "datetime"
LocalTypeInt LocalType = "int"
LocalTypeUint LocalType = "uint"
LocalTypeInt64 LocalType = "int64"
LocalTypeUint64 LocalType = "uint64"
LocalTypeBigInt LocalType = "bigint"
LocalTypeIntSlice LocalType = "[]int"
LocalTypeInt64Slice LocalType = "[]int64"
LocalTypeUint64Slice LocalType = "[]uint64"
LocalTypeStringSlice LocalType = "[]string"
LocalTypeFloat64Slice LocalType = "[]float64"
LocalTypeInt64Bytes LocalType = "int64-bytes"
LocalTypeUint64Bytes LocalType = "uint64-bytes"
LocalTypeFloat32 LocalType = "float32"
LocalTypeFloat64 LocalType = "float64"
LocalTypeBytes LocalType = "[]byte"
LocalTypeBool LocalType = "bool"
LocalTypeJson LocalType = "json"
LocalTypeJsonb LocalType = "jsonb"
)
const (

View File

@ -45,8 +45,9 @@ func (m *Model) FieldsPrefix(prefixOrAlias string, fieldNamesOrMapStruct ...any)
if len(fields) == 0 {
return m
}
prefixOrAlias = m.QuoteWord(prefixOrAlias)
for i, field := range fields {
fields[i] = prefixOrAlias + "." + gconv.String(field)
fields[i] = fmt.Sprintf("%s.%s", prefixOrAlias, m.QuoteWord(gconv.String(field)))
}
model := m.getModel()
return model.appendToFields(fields...)
@ -81,14 +82,21 @@ func (m *Model) doFieldsEx(table string, fieldNamesOrMapStruct ...any) *Model {
}
// FieldsExPrefix performs as function FieldsEx but add extra prefix for each field.
// Note that this function must be used together with FieldsPrefix, otherwise it will be invalid.
func (m *Model) FieldsExPrefix(prefixOrAlias string, fieldNamesOrMapStruct ...any) *Model {
model := m.doFieldsEx(
fields := m.filterFieldsFrom(
m.getTableNameByPrefixOrAlias(prefixOrAlias),
fieldNamesOrMapStruct...,
)
for i, field := range model.fieldsEx {
model.fieldsEx[i] = prefixOrAlias + "." + gconv.String(field)
if len(fields) == 0 {
return m
}
prefixOrAlias = m.QuoteWord(prefixOrAlias)
for i, field := range fields {
fields[i] = fmt.Sprintf("%s.%s", prefixOrAlias, m.QuoteWord(gconv.String(field)))
}
model := m.getModel()
model.fieldsEx = append(model.fieldsEx, fields...)
return model
}
@ -96,7 +104,7 @@ func (m *Model) FieldsExPrefix(prefixOrAlias string, fieldNamesOrMapStruct ...an
func (m *Model) FieldCount(column string, as ...string) *Model {
asStr := ""
if len(as) > 0 && as[0] != "" {
asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0]))
asStr = fmt.Sprintf(` AS %s`, m.QuoteWord(as[0]))
}
model := m.getModel()
return model.appendToFields(
@ -108,7 +116,7 @@ func (m *Model) FieldCount(column string, as ...string) *Model {
func (m *Model) FieldSum(column string, as ...string) *Model {
asStr := ""
if len(as) > 0 && as[0] != "" {
asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0]))
asStr = fmt.Sprintf(` AS %s`, m.QuoteWord(as[0]))
}
model := m.getModel()
return model.appendToFields(
@ -120,7 +128,7 @@ func (m *Model) FieldSum(column string, as ...string) *Model {
func (m *Model) FieldMin(column string, as ...string) *Model {
asStr := ""
if len(as) > 0 && as[0] != "" {
asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0]))
asStr = fmt.Sprintf(` AS %s`, m.QuoteWord(as[0]))
}
model := m.getModel()
return model.appendToFields(
@ -132,7 +140,7 @@ func (m *Model) FieldMin(column string, as ...string) *Model {
func (m *Model) FieldMax(column string, as ...string) *Model {
asStr := ""
if len(as) > 0 && as[0] != "" {
asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0]))
asStr = fmt.Sprintf(` AS %s`, m.QuoteWord(as[0]))
}
model := m.getModel()
return model.appendToFields(
@ -144,7 +152,7 @@ func (m *Model) FieldMax(column string, as ...string) *Model {
func (m *Model) FieldAvg(column string, as ...string) *Model {
asStr := ""
if len(as) > 0 && as[0] != "" {
asStr = fmt.Sprintf(` AS %s`, m.db.GetCore().QuoteWord(as[0]))
asStr = fmt.Sprintf(` AS %s`, m.QuoteWord(as[0]))
}
model := m.getModel()
return model.appendToFields(

View File

@ -752,7 +752,7 @@ func (m *Model) getHolderAndArgsAsSubModel(ctx context.Context) (holder string,
func (m *Model) getAutoPrefix() string {
autoPrefix := ""
if gstr.Contains(m.tables, " JOIN ") {
autoPrefix = m.db.GetCore().QuoteWord(
autoPrefix = m.QuoteWord(
m.db.GetCore().guessPrimaryTableName(m.tablesInit),
)
}
@ -762,7 +762,6 @@ func (m *Model) getAutoPrefix() string {
func (m *Model) getFieldsAsStr() string {
var (
fieldsStr string
core = m.db.GetCore()
)
for _, v := range m.fields {
field := gconv.String(v)
@ -773,7 +772,7 @@ func (m *Model) getFieldsAsStr() string {
switch v.(type) {
case Raw, *Raw:
default:
field = core.QuoteString(field)
field = m.QuoteWord(field)
}
}
if fieldsStr != "" {
@ -829,7 +828,7 @@ func (m *Model) getFieldsFiltered() string {
if len(newFields) > 0 {
newFields += ","
}
newFields += m.db.GetCore().QuoteWord(k)
newFields += m.QuoteWord(k)
}
return newFields
}

View File

@ -142,6 +142,8 @@ func (j *Json) Contains(pattern string) bool {
// The target value by `pattern` should be type of slice or map.
// It returns -1 if the target value is not found, or its type is invalid.
func (j *Json) Len(pattern string) int {
j.mu.RLock()
defer j.mu.RUnlock()
p := j.getPointerByPattern(pattern)
if p != nil {
switch (*p).(type) {

View File

@ -207,6 +207,7 @@ func (oai *OpenApiV3) golangTypeToSchemaName(t reflect.Type) string {
for t.Kind() == reflect.Pointer {
t = t.Elem()
}
schemaName = gstr.Replace(schemaName, `/`, `.`)
if pkgPath = t.PkgPath(); pkgPath != "" && pkgPath != "." {
if !oai.Config.IgnorePkgPath {
schemaName = gstr.Replace(pkgPath, `/`, `.`) + gstr.SubStrFrom(schemaName, ".")
@ -216,6 +217,8 @@ func (oai *OpenApiV3) golangTypeToSchemaName(t reflect.Type) string {
` `: ``,
`{`: ``,
`}`: ``,
`[`: `.`,
`]`: `.`,
})
return schemaName
}

View File

@ -0,0 +1,244 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package goai_test
import (
"context"
"strings"
"testing"
"github.com/gogf/gf/v2/net/goai"
"github.com/gogf/gf/v2/test/gtest"
"github.com/gogf/gf/v2/util/gmeta"
)
// TestOpenApiV3_GenericType tests the schema name generation for generic types
// This test validates the PR fix for swagger $ref replace that handles Go generics
// Specifically testing that [ and ] characters in type names are replaced with dots
func TestOpenApiV3_GenericType(t *testing.T) {
// Define a generic type wrapper
type GenericItem[T any] struct {
Value T `dc:"Generic value"`
}
type StringItem = GenericItem[string]
type IntItem = GenericItem[int]
type Req struct {
gmeta.Meta `path:"/generic" method:"POST" tags:"default"`
StringData StringItem `dc:"String generic type"`
IntData IntItem `dc:"Int generic type"`
}
type Res struct {
gmeta.Meta `description:"Generic Response"`
Data string `dc:"Response data"`
}
f := func(ctx context.Context, req *Req) (res *Res, err error) {
return
}
gtest.C(t, func(t *gtest.T) {
var (
err error
oai = goai.New()
)
err = oai.Add(goai.AddInput{
Path: "/generic",
Object: f,
})
t.AssertNil(err)
// Verify that schema names are properly generated without special characters
schemas := oai.Components.Schemas.Map()
t.AssertGT(len(schemas), 0)
// Check that bracket characters [ and ] have been replaced with dots
// According to PR fix: `[`: `.`, `]`: `.`
for schemaName := range schemas {
// Should not contain [ or ] characters after replacement
t.Assert(!strings.Contains(schemaName, "["), true)
t.Assert(!strings.Contains(schemaName, "]"), true)
}
})
}
// TestOpenApiV3_SchemaNameReplacement tests the special character replacement in schema names
// This verifies the core PR change which replaces:
// - [ with .
// - ] with .
// - { with empty string
// - } with empty string
// - spaces with empty string
func TestOpenApiV3_SchemaNameReplacement(t *testing.T) {
type SimpleReq struct {
gmeta.Meta `path:"/test" method:"POST"`
Name string `dc:"Name field"`
}
type SimpleRes struct {
gmeta.Meta `description:"Simple Response"`
Status string `dc:"Status field"`
}
f := func(ctx context.Context, req *SimpleReq) (res *SimpleRes, err error) {
return
}
gtest.C(t, func(t *gtest.T) {
var (
err error
oai = goai.New()
)
err = oai.Add(goai.AddInput{
Path: "/test",
Object: f,
})
t.AssertNil(err)
// Get schema names and verify they are properly formatted
schemas := oai.Components.Schemas.Map()
for schemaName := range schemas {
// Verify special characters have been replaced:
// - [ should be replaced with .
// - ] should be replaced with .
// - { should be replaced with empty
// - } should be replaced with empty
// - spaces should be replaced with empty
t.Assert(!strings.Contains(schemaName, "["), true)
t.Assert(!strings.Contains(schemaName, "]"), true)
t.Assert(!strings.Contains(schemaName, "{"), true)
t.Assert(!strings.Contains(schemaName, "}"), true)
}
})
}
// TestOpenApiV3_ComplexGenericType tests more complex generic types
// This specifically tests handling of map types and nested generic structures
func TestOpenApiV3_ComplexGenericType(t *testing.T) {
type MapWrapper struct {
gmeta.Meta `path:"/mapwrapper" method:"POST"`
Data map[string]string `dc:"Map data"`
}
type Res struct {
gmeta.Meta `description:"Map Response"`
Result string `dc:"Result"`
}
f := func(ctx context.Context, req *MapWrapper) (res *Res, err error) {
return
}
gtest.C(t, func(t *gtest.T) {
var (
err error
oai = goai.New()
)
err = oai.Add(goai.AddInput{
Path: "/mapwrapper",
Object: f,
})
t.AssertNil(err)
// Verify schema generation completes without errors
schemas := oai.Components.Schemas.Map()
t.AssertGT(len(schemas), 0)
// All schema names should be valid (no bracket characters)
for schemaName := range schemas {
t.Assert(!strings.Contains(schemaName, "["), true)
t.Assert(!strings.Contains(schemaName, "]"), true)
}
})
}
// TestOpenApiV3_PathWithSpecialChars tests path parameters with special handling
// This ensures the PR changes don't affect regular parameter handling
func TestOpenApiV3_PathWithSpecialChars(t *testing.T) {
type GetDetailReq struct {
gmeta.Meta `path:"/detail" method:"GET"`
ResourceId string `json:"resourceId" in:"query" dc:"Resource identifier"`
Type string `json:"type" in:"query" dc:"Resource type"`
}
type DetailRes struct {
gmeta.Meta `description:"Detail Response"`
Content string `dc:"Detail content"`
}
f := func(ctx context.Context, req *GetDetailReq) (res *DetailRes, err error) {
return
}
gtest.C(t, func(t *gtest.T) {
var (
err error
oai = goai.New()
)
err = oai.Add(goai.AddInput{
Path: "/detail",
Object: f,
})
t.AssertNil(err)
// Verify all schemas are properly named
schemas := oai.Components.Schemas.Map()
for schemaName := range schemas {
// Should not contain special characters that were supposed to be replaced
t.Assert(!strings.Contains(schemaName, "["), true)
t.Assert(!strings.Contains(schemaName, "]"), true)
}
})
}
// TestOpenApiV3_SliceOfGenericTypes tests slice of generic types
// This validates that slices containing generics are properly handled
func TestOpenApiV3_SliceOfGenericTypes(t *testing.T) {
type Item[T any] struct {
Value T `dc:"Item value"`
}
type StringItem = Item[string]
type SliceReq struct {
gmeta.Meta `path:"/slice" method:"POST"`
Items []StringItem `dc:"Slice of generic items"`
}
type SliceRes struct {
gmeta.Meta `description:"Slice Response"`
Count int `dc:"Item count"`
}
f := func(ctx context.Context, req *SliceReq) (res *SliceRes, err error) {
return
}
gtest.C(t, func(t *gtest.T) {
var (
err error
oai = goai.New()
)
err = oai.Add(goai.AddInput{
Path: "/slice",
Object: f,
})
t.AssertNil(err)
schemas := oai.Components.Schemas.Map()
t.AssertGT(len(schemas), 0)
// Verify no bracket characters in schema names
for schemaName := range schemas {
t.Assert(!strings.Contains(schemaName, "["), true)
t.Assert(!strings.Contains(schemaName, "]"), true)
}
})
}