mirror of
https://gitee.com/johng/gf
synced 2026-06-08 02:27:42 +08:00
Compare commits
19 Commits
v2.9.5
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e3a426c47 | |||
| 99d69857fa | |||
| 1b26013a66 | |||
| 6c2155bd26 | |||
| 9018a3d4ac | |||
| 362d4202c4 | |||
| a85b221d32 | |||
| cb8594eb80 | |||
| ac88e640d1 | |||
| 2d307c5dd1 | |||
| 54453c8e8f | |||
| 072b962b81 | |||
| a80f58b7f6 | |||
| ded6773042 | |||
| 99a7fe5dd1 | |||
| 1a8977157d | |||
| bae001b83b | |||
| 507211ecdd | |||
| 32a002fc51 |
14
.github/workflows/ci-main.yml
vendored
14
.github/workflows/ci-main.yml
vendored
@ -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
|
||||
|
||||
|
||||
29
.github/workflows/scripts/ci-main.sh
vendored
29
.github/workflows/scripts/ci-main.sh
vendored
@ -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 ./...
|
||||
|
||||
20
.github/workflows/scripts/ci-sub.sh
vendored
20
.github/workflows/scripts/ci-sub.sh
vendored
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
17
README.MD
17
README.MD
@ -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.
|
||||
|
||||
@ -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=
|
||||
|
||||
@ -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
|
||||
|
||||
12
cmd/gf/internal/cmd/testdata/build/varmap/go.sum
vendored
12
cmd/gf/internal/cmd/testdata/build/varmap/go.sum
vendored
@ -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=
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
@ -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]),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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]),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
721
container/glist/glist_t.go
Normal 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
|
||||
}
|
||||
61
container/glist/glist_z_bench_t_test.go
Normal file
61
container/glist/glist_z_bench_t_test.go
Normal 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()
|
||||
}
|
||||
})
|
||||
}
|
||||
689
container/glist/glist_z_example_t_test.go
Normal file
689
container/glist/glist_z_example_t_test.go
Normal 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
|
||||
}
|
||||
933
container/glist/glist_z_unit_t_test.go
Normal file
933
container/glist/glist_z_unit_t_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
//
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
//
|
||||
|
||||
|
||||
@ -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.
|
||||
//
|
||||
|
||||
|
||||
@ -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.
|
||||
//
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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).
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -303,6 +303,8 @@ func Test_DB_Tables(t *testing.T) {
|
||||
createTable(v)
|
||||
}
|
||||
|
||||
defer dropTable(tables...)
|
||||
|
||||
result, err := db.Tables(ctx)
|
||||
gtest.AssertNil(err)
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
47
contrib/drivers/mysql/mysql_z_unit_issue_4382_test.go
Normal file
47
contrib/drivers/mysql/mysql_z_unit_issue_4382_test.go
Normal 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")
|
||||
})
|
||||
}
|
||||
@ -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
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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`
|
||||
)
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
244
net/goai/goai_z_unit_generic_type_test.go
Normal file
244
net/goai/goai_z_unit_generic_type_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user