Compare commits

...

190 Commits

Author SHA1 Message Date
7e95058cb5 donator updates 2020-06-08 22:34:48 +08:00
f33753e0cd add example for package ghttp/glog 2020-06-08 20:21:35 +08:00
ac71e1d753 rename Dump to RawDump for ghttp.ClientResponse 2020-06-08 19:26:14 +08:00
8151b6efd6 add more example for package gredis 2020-06-08 19:17:24 +08:00
94de306c93 add zero time filtering for package gdb 2020-06-08 13:46:45 +08:00
51f8ea26de add example for package gredis 2020-06-06 18:12:42 +08:00
29d9bb17cd improve function Unscoped for package gdb 2020-06-06 16:28:16 +08:00
30501a882d improve function Struct for package gconv 2020-06-06 15:31:04 +08:00
f2f98e1d16 rename testing file names for package gdb 2020-06-06 14:38:05 +08:00
cbf465eeea improve body interface implements for ghttp.Reqest/Response 2020-06-06 13:34:58 +08:00
79c400e912 Merge branch 'master' of https://github.com/gogf/gf 2020-06-06 10:10:05 +08:00
cc4c49b5b0 add unit testing case for pointer attribute mapping for function Parse 2020-06-06 10:09:19 +08:00
96ea2c911d Merge pull request #696 from sanrentai/master
the sqlserver type money  should be converted to float64
2020-06-05 21:08:49 +08:00
131b11680a readme update 2020-06-05 20:36:05 +08:00
3c6ab96283 fix issue in function Walk and add more example for package gset 2020-06-04 21:46:22 +08:00
d2c4fa921a add more example for package gmap 2020-06-04 20:45:18 +08:00
dbb51a9328 improve package glist 2020-06-04 20:13:33 +08:00
55bfc3fa73 add more examples for package garray 2020-06-04 18:28:33 +08:00
d9c5182d1a gitignore updates 2020-06-04 17:56:32 +08:00
899fcbf2da add mor example for package garray 2020-06-04 17:55:43 +08:00
6e2c0d8706 comment and readme update 2020-06-04 17:29:16 +08:00
c4e599ce63 improve random bytes buffer hanlding for package grand 2020-06-04 14:45:56 +08:00
9cad9e4467 add more unit testing cases for package gstr 2020-06-04 00:03:30 +08:00
48019444ea README and DONATOR update 2020-06-03 23:49:12 +08:00
76d31a7fbb improve slice result handling by treating it as string slice for package gredis 2020-06-03 23:44:21 +08:00
facb9d93c0 fix issue of multiple slice arguments handling in function where 2020-06-03 21:36:16 +08:00
1d6bd46c5e README updates 2020-06-03 20:33:21 +08:00
8646dc1825 Merge pull request #702 from misitebao/master
更新 readme 中文版
2020-06-03 18:49:35 +08:00
2992bdeed7 修改之前的捐赠名称 2020-06-03 11:38:21 +08:00
c5601e6c88 更新 readme 中文版 2020-06-03 10:59:20 +08:00
ef1d9a561c improve the route feature for ghttp.Server 2020-06-03 00:09:51 +08:00
2d3b32c94a money 类型转换错误 2020-05-29 15:41:37 +08:00
269378aa0d fix issue in unit testing case for package gsession; version updates 2020-05-28 20:28:07 +08:00
6889542801 improve package guid 2020-05-27 11:26:05 +08:00
8e6f1f1740 improve performance of random buffer usage for package grand 2020-05-25 23:39:09 +08:00
b6ab1a992c improve empty checks for common interfaces implementer 2020-05-25 14:26:08 +08:00
a5a267567c comment update for package garray 2020-05-22 12:04:58 +08:00
f05f855c07 improve default max header bytes from 1KB to 10KB for ghttp.Server 2020-05-21 20:10:38 +08:00
bc5f773ba6 make WriteTimeout default to 0 for ghttp.Server 2020-05-19 19:16:47 +08:00
55308cc37c improve package gipv4 by formating the value retrieving fuctions to Get* 2020-05-18 23:50:31 +08:00
4d9db6edf0 improve package gconv for map converting 2020-05-18 20:58:19 +08:00
38111a64e3 improve files listing for ghttp.Server 2020-05-18 20:09:00 +08:00
bd8d3fca08 improve domain rule for package gvalid 2020-05-18 19:44:15 +08:00
89ccaa3915 improve function Do for package gredis, adding auto marshal feature for arguments 2020-05-18 19:18:53 +08:00
7c2cff7d99 add function GetVar for package gcache 2020-05-17 18:16:26 +08:00
788e15dbb6 comment update for package guid 2020-05-17 15:40:24 +08:00
a0172d9d7e README updates 2020-05-17 15:16:13 +08:00
bc1a7a1644 README updates 2020-05-17 15:11:07 +08:00
c98234d3e6 improve session feature by allowing custom session id creating function for ghttp.Server 2020-05-17 14:33:27 +08:00
45a94d23d5 improve session feature by allowing custom session id creating function for ghttp.Server 2020-05-17 14:33:21 +08:00
351de5ee6a fix concurrent issue in buffer handling of package grand 2020-05-17 12:46:28 +08:00
3559436d1e improve package guid 2020-05-17 08:50:37 +08:00
189f4c1637 improve package gsession 2020-05-16 22:08:16 +08:00
caead810e2 add package guid; improve performance of package grand 2020-05-16 21:56:31 +08:00
ebdc5d9c9d comment update for package grand 2020-05-16 16:08:30 +08:00
4164059211 add function B for package grand; improve package grand 2020-05-16 16:05:31 +08:00
bd27258c46 donator updates 2020-05-16 15:43:16 +08:00
ddf0605bc4 go mod tidy 2020-05-16 15:36:04 +08:00
991dbe50e0 add more functions for package gipv4; move package guuid to standalone package of gogf 2020-05-16 15:35:21 +08:00
8050efb835 improve package gfile 2020-05-16 14:06:35 +08:00
9355bc73a2 improve package gvalid 2020-05-16 13:31:24 +08:00
acc2a6a353 improve copy feature for package gfile 2020-05-16 00:50:01 +08:00
09e83e7b8d improve version functions for package gstr 2020-05-16 00:12:23 +08:00
c0df8a3d80 improve version functions for package gstr 2020-05-16 00:11:08 +08:00
33e890d225 improve version functions for package gstr 2020-05-16 00:10:12 +08:00
750e5df962 improve gstr.CompareVersion 2020-05-15 23:42:17 +08:00
74c65439fc add function ScanDirFunc for package gfile 2020-05-15 21:51:03 +08:00
f290bd7170 fix issue in http.File implements for package gres 2020-05-14 21:25:54 +08:00
d398b749d4 improve support for types of database pgsql 2020-05-14 21:15:44 +08:00
2fd5a1574a improve package gres 2020-05-14 20:32:01 +08:00
a7504f0629 add more unit testing cases for package gdb 2020-05-13 23:21:11 +08:00
3c35bb85ee improve unit testing cases for package gi18n/ghttp 2020-05-13 19:35:56 +08:00
6621edb04c improve i18n of chinese for package gvalid 2020-05-13 19:10:38 +08:00
b0c722e297 improve package gvalid 2020-05-10 22:34:16 +08:00
47a6284274 improve package gvalid 2020-05-10 22:32:10 +08:00
150b8edb70 improve package gvalid 2020-05-10 17:49:23 +08:00
aa5d3285eb improve package gvalid 2020-05-10 16:48:00 +08:00
993d6e3076 improve package gvalid 2020-05-10 10:56:11 +08:00
78ad9d8c2d improve error message of package gvalid 2020-05-10 09:05:52 +08:00
feff3ddce3 comment update for package ghttp 2020-05-09 19:19:42 +08:00
80fddad64d fix issue in PopRight function for package garray; improve json.Marshal for package garray 2020-05-08 21:08:06 +08:00
1e6dd0be02 readme updates 2020-05-08 19:16:52 +08:00
22ecf4f1b6 add context feature for package glog 2020-05-08 17:12:37 +08:00
5d21148657 comment updates for package ghttp 2020-05-07 23:05:33 +08:00
edb745ed92 version updates 2020-05-07 11:31:34 +08:00
61f4e0da6f improve package gcache 2020-05-06 21:09:02 +08:00
767afa3106 improve package gcache 2020-05-06 19:06:49 +08:00
16779359ee remove package gchan 2020-05-06 18:43:19 +08:00
770653fafa enable the route dump for unit testing of logging feature for ghttp.Server 2020-05-05 00:09:39 +08:00
0c021b6da7 add unit testing case of status handler feature for ghttp.Server 2020-05-05 00:01:09 +08:00
ec92b08f25 add function Maps for package gvar; add function GetMaps for package gjson; improve MapToMap* functions for package gconv 2020-05-04 23:42:51 +08:00
13e2353729 fix lock issue in function search for package garray 2020-05-03 23:08:18 +08:00
90d19a84ce improve package garray/gset, adding function ContainsI 2020-05-03 22:52:04 +08:00
b4c0b95d8a add support for old version of gres 2020-05-03 22:16:12 +08:00
b7e8255066 add support for old version of gre 2020-05-03 22:08:08 +08:00
5f8e6ad9ed comment update for package ghttp 2020-05-01 03:31:04 +08:00
c8d253eb56 change binary content from hex string to base64 string for package gres 2020-05-01 02:35:24 +08:00
4814624cff change binary content from hex string to base64 string for package gres 2020-05-01 02:16:42 +08:00
cc67f3d388 improve package gcompress 2020-05-01 01:47:02 +08:00
f7c2a51c9f fix issue in zip feature for package gcompress; improve package gres 2020-05-01 00:18:45 +08:00
3db83e1159 improve package gtimer 2020-04-30 22:22:35 +08:00
3bb002796c donator update 2020-04-30 20:46:27 +08:00
45170bc53e add ClientMaxBodySize configuration for ghttp.Server 2020-04-30 20:37:09 +08:00
b79ff84c6f add struct slice conversion for request parameters for ghttp.Request; improve package gconv 2020-04-30 16:53:47 +08:00
938c46fec9 readme update 2020-04-29 19:33:14 +08:00
8a13d94526 improve Record.Struct for package gdb 2020-04-29 09:12:13 +08:00
1e844d505a comment update for package ghttp/gconv; readme update 2020-04-29 00:14:29 +08:00
a123a2c086 improve struct conversion of empty result/record for package gdb 2020-04-28 21:03:25 +08:00
1eeeeb853e improve unit testing case of error logging for ghttp.Server 2020-04-28 15:21:17 +08:00
6e7224e306 improve error handling for gconv.Struct/ghttp.Server; add NewSkip/NewfSkip function for package gerror 2020-04-28 15:04:07 +08:00
9e064e2651 donator updates 2020-04-27 23:34:22 +08:00
8d9dd17eac add Walk function for package gset; improve fields handling feature for package gdb 2020-04-27 21:18:42 +08:00
cf1d3d3d2b improve package gdebug; add more unit testing case for package gdb 2020-04-27 17:56:04 +08:00
9480ffcdc0 improve function SetPath/AddPath for package gview 2020-04-27 17:07:00 +08:00
5db10add4a fix issue in unnacessary quoting of fields in select statement of gdb.Model 2020-04-27 16:30:53 +08:00
fa66bf5d9d improve cache feature of package gdb.Model 2020-04-26 21:31:55 +08:00
f69da3ace1 add function Transaction for package gdb 2020-04-26 17:47:19 +08:00
e01bfa05c3 listening ports change for unit testing cases of ghttp.Server 2020-04-26 17:13:48 +08:00
231238c157 improve parameter handing for ghttp.Request 2020-04-26 17:08:07 +08:00
7edec099ab improve raw request/response content dump for ghttp.Client 2020-04-24 00:00:52 +08:00
83eb8be064 Merge pull request #609 from kirileec/master
add Raw* method in ClientResponse to get request and response string
2020-04-23 22:58:04 +08:00
7af30df494 Merge branch 'master' into master 2020-04-23 22:57:23 +08:00
f026686fda fix issue in dupicated expiration handling in response cookies og ghttp.Client 2020-04-23 21:12:32 +08:00
35a50b9c6c readme update 2020-04-23 21:06:42 +08:00
010e2f951a downgrade the required golang version from v1.13 to v1.11 2020-04-23 20:38:25 +08:00
f7f86ad65a downgrade the required golang version from v1.13 to v1.11 2020-04-23 20:25:59 +08:00
1e19f447d1 improve ghttp.Client in context handling 2020-04-23 20:23:23 +08:00
8cc378331d add one unit testing case for ghttp.Server 2020-04-23 20:10:10 +08:00
4721f68fd8 add performance testing result 2020-04-23 19:51:08 +08:00
0d11c0a1f8 add performance testing result 2020-04-23 19:41:34 +08:00
5076613a8f add performance testing result 2020-04-23 17:25:11 +08:00
c5a44daa65 add performance testing result 2020-04-23 17:23:57 +08:00
520970b71f add performance testing result 2020-04-23 17:19:08 +08:00
ebfb08ee3f add performance testing result 2020-04-23 17:18:15 +08:00
9e38b2cb90 add performance testing result 2020-04-23 17:14:11 +08:00
71b1f00dc5 improve package gdb/gstr/gvalid 2020-04-20 22:36:28 +08:00
59d6830ab1 version update 2020-04-18 13:42:06 +08:00
8779a3f211 add function Walk for package garray; improve comment for package garray 2020-04-18 13:30:49 +08:00
c10149baa0 fix issue in stack trace for package gdebug; improve package gsmtp 2020-04-18 10:17:54 +08:00
4f87668780 improve retry feature for ghttp.Client 2020-04-16 15:43:21 +08:00
63f33d1d8c improve package gdb supporting gtime.Time parameter for Where condition 2020-04-15 18:02:32 +08:00
734aa5a6fe improve create_at,update_at,delete_at feature for package gdb 2020-04-15 12:56:41 +08:00
371aef224d donator updates 2020-04-15 11:41:44 +08:00
362e380ada improve SetConfigWithMap function for package ghttp/glog/gview; reveal some logger functions for default logger of package glog 2020-04-15 11:04:39 +08:00
e995bd8c9a add unit testing cases of https feature for ghttp.Server 2020-04-15 09:55:44 +08:00
81b211dd1a improve select operation details handling for package gdb 2020-04-15 09:37:46 +08:00
0515fc94cb add MapMerge/MapMargeCopy functions for package gutil; improve template view feature for indefinite parameters 2020-04-14 21:11:12 +08:00
46ee070f0a fix issue in package gfsnotify when gset changes 2020-04-14 00:20:39 +08:00
b17e3a6804 improve package gset 2020-04-13 23:44:43 +08:00
9160bee1af change comments 2020-04-11 12:16:53 +08:00
8e6018cfff improve soft deleting feature for package gdb 2020-04-11 10:14:49 +08:00
c08739b5a3 add function Having for gdb.Model 2020-04-11 10:11:52 +08:00
385a503d31 add soft deleting feature, improve chars handling for package gdb 2020-04-11 09:09:25 +08:00
ef286b0c15 add unit testing case for auto time and soft deleting features for package gdb 2020-04-09 22:48:21 +08:00
53aba2d4b8 add unit testing case of cache feature for package gdb 2020-04-09 22:00:02 +08:00
f22da4ba3a improve gconv.MapDeep with anonymous checks for attribute struct field 2020-04-09 15:34:23 +08:00
23c2f12672 improve MapDeep function for package gconv; improve gjson.New function for loading struct parameter 2020-04-09 13:37:27 +08:00
7fd53673ce add automatic time and soft deleting features: create_at/update_at/delete_at for package gdb; improve schema changing feature for package gdb 2020-04-08 21:26:14 +08:00
e64fd088b9 fix when no response 2020-04-08 20:11:06 +08:00
15672e7a09 #591 add Raw* method in ClientResponse to get request and response string 2020-04-08 19:24:03 +08:00
2ea1d2c7b2 update build-in template function substr/strlimit for package gview 2020-04-07 23:53:17 +08:00
90d751f98d add function SubStrRune/StrLimitRune/PosRune/PosIRune/PosRRune/PosRIRune for package gstr; remove unicode support in function SubStr/StrLimit for package gstr 2020-04-07 23:51:48 +08:00
25a91c732c improve String/Map converting for package gconv 2020-04-07 21:29:41 +08:00
01995f5501 improve function Marge for package garray using interface feature; improve slice converting using interface feature for package gconv 2020-04-07 21:25:52 +08:00
68abb3cf3d improve package glist for var initilalization feature 2020-04-07 20:58:58 +08:00
77cc323d0e improve package gset/gmap for initialization 2020-04-07 20:41:49 +08:00
c7a9c03495 improve package garray/gmap for initialization 2020-04-07 20:06:26 +08:00
f7850e3ed3 improve package garray 2020-04-07 11:29:42 +08:00
82125416a2 comment update for package ghttp 2020-04-06 22:31:45 +08:00
2c1e2155e3 improve function Server.Handler for package ghttp 2020-04-06 20:29:35 +08:00
2d30a53c3a remove function From for package gdb; add function g.Table; add ServeHTTP interface implements for ghttp.Server 2020-04-04 22:46:52 +08:00
ccceeae29c change third party package for yaml parsing to gopkg.in/yaml.v3 2020-04-03 19:51:33 +08:00
f22b98456f add default value of MaxActive configuration for package gredis 2020-04-03 15:18:35 +08:00
e7f1bd692b remove Content-Type header set in Response.WriteStatus for package ghttp 2020-04-03 14:16:26 +08:00
f82f7ab199 change default value of safe from false to true for gdb.Model 2020-04-03 10:16:57 +08:00
4d33b527b6 readme update 2020-04-03 09:32:04 +08:00
7bcc596308 add GetVar function for package genv; add DryRun feature for package gdb 2020-04-02 20:52:37 +08:00
be2d4b080e Merge pull request #601 from bdliyq/master
Get ghttp a chance to add custom host.
2020-04-01 19:55:25 +08:00
c96e5f5a9e readme update 2020-04-01 18:08:20 +08:00
3c23766674 readme update 2020-04-01 18:05:17 +08:00
718089fc11 Get ghttp a chance to add custom host. 2020-03-31 23:28:21 +08:00
8ab44dcb44 Get ghttp a chance to add custom host. 2020-03-31 21:50:15 +08:00
992522342c version/readme update 2020-03-30 23:56:26 +08:00
040898cdc3 improve unit testing case for package gmutex 2020-03-30 22:55:03 +08:00
343126ef22 fix usage for garray.PopRand 2020-03-30 20:56:00 +08:00
05760d1eac fix usage for garray.PopRand 2020-03-30 20:47:50 +08:00
c10f73f1f7 add zero time.Time filtering for omitempty feature of gdb.Model 2020-03-30 20:44:36 +08:00
7e0fa8e0cd improve package garray 2020-03-30 20:31:47 +08:00
6fe6218505 README updates 2020-03-29 20:23:10 +08:00
6059782de8 add unit testing case of basic auth for ghttp.Client; remove intlog for New function of package gspath 2020-03-29 19:36:49 +08:00
372 changed files with 15437 additions and 6913 deletions

View File

@ -1,8 +1,9 @@
# MySQL数据库配置
[database]
# debug = true
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local"
debug = true
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local"
MaxOpen = 100
#[database]
# [[database.default]]

View File

@ -1,16 +1,68 @@
package main
import (
"archive/zip"
"fmt"
"github.com/gogf/gf/encoding/gcompress"
"io"
"os"
"path/filepath"
"strings"
)
func main() {
err := gcompress.ZipPath(
`D:\Workspace\Go\GOPATH\src\github.com\gogf\gf\geg`,
`D:\Workspace\Go\GOPATH\src\github.com\gogf\gf\geg\encoding\gcompress\data.zip`,
"my-dir",
)
fmt.Println(err)
// srcFile could be a single file or a directory
func Zip(srcFile string, destZip string) error {
zipfile, err := os.Create(destZip)
if err != nil {
return err
}
defer zipfile.Close()
archive := zip.NewWriter(zipfile)
defer archive.Close()
filepath.Walk(srcFile, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = strings.TrimPrefix(path, filepath.Dir(srcFile)+"/")
// header.Name = path
if info.IsDir() {
header.Name += "/"
} else {
header.Method = zip.Deflate
}
writer, err := archive.CreateHeader(header)
if err != nil {
return err
}
if !info.IsDir() {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(writer, file)
}
return err
})
return err
}
func main() {
src := `/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/test`
dst := `/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/test.zip`
//src := `/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/README.MD`
//dst := `/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/README.MD.zip`
fmt.Println(gcompress.ZipPath(src, dst))
//fmt.Println(Zip(src, dst))
}

View File

@ -9,7 +9,7 @@ import (
func Upload(r *ghttp.Request) {
saveDirPath := "/tmp/"
files := r.GetUploadFiles("upload-file")
if err := files.Save(saveDirPath); err != nil {
if _, err := files.Save(saveDirPath); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit("upload successfully")

View File

@ -2,12 +2,34 @@ package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
"path/filepath"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/os/glog"
)
func SendXmlFile(gameId int, areaName string, filePath string) error {
path := filepath.FromSlash(filePath)
fmt.Println(path)
data := g.Map{
"gameName": gameId,
"area": areaName,
"file": "@file:" + path,
"contentType": "json",
}
if r, err := ghttp.Post("http://127.0.0.1:8199/upload", data); err != nil {
panic(err)
} else {
defer r.Close()
fmt.Println("ok")
}
return nil
}
func main() {
SendXmlFile(1, "xxx", "/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/net/ghttp/server/session.go")
return
path := "/home/john/Workspace/Go/github.com/gogf/gf/version.go"
r, e := ghttp.Post("http://127.0.0.1:8199/upload", "upload-file=@file:"+path)
if e != nil {

View File

@ -8,8 +8,8 @@ import (
// Upload uploads files to /tmp .
func Upload(r *ghttp.Request) {
saveDirPath := "/tmp/"
files := r.GetUploadFiles("upload-file")
if err := files.Save(saveDirPath); err != nil {
files := r.GetUploadFiles("file")
if _, err := files.Save(saveDirPath); err != nil {
r.Response.WriteExit(err)
}
r.Response.WriteExit("upload successfully")

View File

@ -5,21 +5,18 @@ import (
"github.com/gogf/gf/net/ghttp"
)
type User struct {
}
type User struct{}
func (c *User) Index(r *ghttp.Request) {
r.Response.Write("Index")
}
// 不符合规范,不会被注册
func (c *User) Test(r *ghttp.Request, value interface{}) {
func (c *User) Test(r *ghttp.Request) {
r.Response.Write("Test")
}
func main() {
s := g.Server()
s.BindObject("/user", new(User))
u := new(User)
s.Group("/", func(group *ghttp.RouterGroup) {
group.GET("/db-{table}/{id}", u, "Test")
})
s.SetPort(8199)
s.Run()
}

View File

@ -0,0 +1,14 @@
package main
import (
"context"
"github.com/gogf/gf/os/glog"
)
func main() {
glog.SetCtxKeys("Trace-Id", "Span-Id", "Test")
ctx := context.WithValue(context.Background(), "Trace-Id", "1234567890")
ctx = context.WithValue(ctx, "Span-Id", "abcdefg")
glog.Ctx(ctx).Print(1, 2, 3)
}

View File

@ -1,18 +0,0 @@
[database]
debug = true
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
[redis]
default = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1"
# Logger.
[logger]
Path = "/tmp/log/gf-app"
Level = "all"
Stdout = true
[viewer]
delimiters = ["${", "}"]
autoencode = true

View File

@ -1,25 +0,0 @@
package main
import (
"github.com/gogf/gf/crypto/gaes"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/os/gres"
)
var (
CryptoKey = []byte("x76cgqt36i9c863bzmotuf8626dxiwu0")
)
func main() {
binContent, err := gres.Pack("public,config")
if err != nil {
panic(err)
}
binContent, err = gaes.Encrypt(binContent, CryptoKey)
if err != nil {
panic(err)
}
if err := gfile.PutBytes("data.bin", binContent); err != nil {
panic(err)
}
}

View File

@ -1,11 +0,0 @@
package main
import (
"fmt"
"github.com/gogf/gf/net/ghttp"
)
func main() {
r := ghttp.PostContent("http://127.0.0.1:8199/test", `<doc><id>1</id><name>john</name><password1>123Abc!@#</password1><password2>123Abc!@#</password2></doc>`)
fmt.Println(r)
}

View File

@ -8,6 +8,9 @@ import (
func main() {
for i := 0; i < 100; i++ {
fmt.Println(grand.Rand(0, 99999))
fmt.Println(grand.S(16))
}
for i := 0; i < 100; i++ {
fmt.Println(grand.N(0, 99999999))
}
}

View File

@ -0,0 +1,13 @@
package main
import (
"fmt"
"github.com/gogf/gf/util/guid"
)
func main() {
for i := 0; i < 100; i++ {
s := guid.S()
fmt.Println(s, len(s))
}
}

View File

@ -0,0 +1,20 @@
package main
import (
"fmt"
"math"
"strconv"
)
func main() {
// 36*36^2+36*36+36
var s string
fmt.Println(strconv.ParseUint("zzz", 36, 3))
fmt.Println(1 << 1)
// MaxInt64
s = strconv.FormatUint(math.MaxUint64, 16)
fmt.Println(s, len(s))
// PID
s = strconv.FormatInt(1000000, 36)
fmt.Println(s, len(s))
}

View File

@ -0,0 +1,23 @@
package main
import (
"fmt"
"github.com/gogf/gf/util/guid"
)
func main() {
for i := 0; i < 100; i++ {
s := guid.S([]byte("123"))
fmt.Println(s, len(s))
}
fmt.Println()
for i := 0; i < 100; i++ {
s := guid.S([]byte("123"), []byte("456"))
fmt.Println(s, len(s))
}
fmt.Println()
for i := 0; i < 100; i++ {
s := guid.S([]byte("123"), []byte("456"), []byte("789"))
fmt.Println(s, len(s))
}
}

View File

@ -0,0 +1,13 @@
package main
import (
"fmt"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gvalid"
)
func main() {
g.I18n().SetLanguage("cn")
err := gvalid.Check("", "required", nil)
fmt.Println(err.String())
}

View File

@ -0,0 +1,14 @@
"gf.gvalid.required" = "字段不能为空"

1
.gitignore vendored
View File

@ -15,3 +15,4 @@ cbuild
**/.DS_Store
.vscode/
go.sum
.example/other/

View File

@ -1,6 +1,8 @@
language: go
go:
- "1.11.x"
- "1.12.x"
- "1.13.x"
- "1.14.x"

View File

@ -56,7 +56,7 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
|🚶|wechat|¥6.88| 喝杯冰阔落
|a*l|wechat|¥10.00| gf
|[wxkj](https://gitee.com/wxkj)|wechat|¥10.00|
|*包|wechat|¥9.99|
|[米司特包](https://github.com/misitebao)|wechat|¥9.99|
|重庆宝尔威科技|wechat|¥6.66|
|琦玉-QPT|wechat|¥6.66|
|sailsea|wechat|¥11.00|
@ -64,11 +64,33 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
|*华|wechat|¥6.66| 感谢郭强的热心
|[Playhi](https://github.com/Playhi)|alipay|¥10.00|
|北京京纬互动科技|alipay|¥200.00|
|米司特包|wechat|¥99.99|
|[米司特包](https://github.com/misitebao)|wechat|¥99.99|
|金毛|alipay|¥100.00|
|1*1x|wechat|¥100.00|
|[ywanbing](https://github.com/ywanbing)|wechat|¥66.66|
|[侯哥](http://www.macnie.com)|wechat|¥10.00|
|如果🍋|alipay|¥100.00| 错过的奶茶^_^
|蔡蔡|wechat|¥666.00| gf真强大让项目省心
|jack|wechat|¥100.00|
|sbilly|wechat|¥100.00| 祝好!
|[米司特包](https://github.com/misitebao)|wechat|¥166.66| 大佬加油!
|*秦|wechat|¥20.00| 给群主献上一杯咖啡...
|[zhuhuan12](https://gitee.com/zhuhuan12)|wechat|¥50.00|
|faddei|qq|¥9.99|
|*天|wechat|¥10.00|
|*.|wechat|¥20.00| 学生一个微薄之力,冲
|李勇|wechat|¥50.00|
|[米司特包](https://github.com/misitebao)|wechat|¥66.66|
|千年|wechat|¥1.08|
|[szzxing](https://github.com/szzxing)|wechat|¥50.00|
|伟客互联|wechat|¥66.66|
|sbilly|wechat|¥99.00|
|**阳|alipay|¥100.01|
|**亮|alipay|¥10.00|
|[mingzaily](https://github.com/mingzaily)|alipay|¥30.00|
|[seny0929](https://gitee.com/seny0929)|alipay|¥9.90|
|Fly的狐狸|alipay|¥100.00|
<img src="https://goframe.org/images/donate.png"/>

103
README.MD
View File

@ -9,13 +9,13 @@
English | [简体中文](README_ZH.MD)
`GF(GoFrame)` is a modular, loose-coupled, full-featured and production-ready application development framework of golang,
providing a series of core components and dozens of practical modules,
such as: memcache, configure, validator, logging, array/queue/set/map containers,
timer/timing tasks, file/memory lock, object pool, database ORM, etc,
supporting web server integrated with router, cookie, session, middleware, logger,
template, https, hooks, rewrites and many more features.
`GF(GoFrame)` is a modular, powerful, high-performance and production-ready application development framework
of golang. Providing a series of core components and dozens of practical modules, such as:
cache, logging, containers, timer, resource, validator, database orm, etc.
Supporting web server integrated with router, cookie, session, middleware, logger, configure,
template, https, hooks, rewrites and many more features.
> If you're a newbie to `Go`, you may consider `GoFrame` easy and great as `Laravel` in `PHP`, `SpringBoot` in `Java` or `Django` in `Python`.
# Installation
```
@ -28,13 +28,81 @@ require github.com/gogf/gf latest
# Limitation
```
golang version >= 1.13
golang version >= 1.11
```
# Packages
1. **Primary Package**
The `gf` repository maintains some basic and most commonly used packages, keeping it as lightweight and simple as possible.
1. **Community Package**
The community packages are contributed and maintained by community members, which are hosted in `gogf` organization. Some of the community packages are separated from the `gf` repository, which are not of common usage or are with heavy dependencies.
# Architecture
<div align=center>
<img src="https://goframe.org/images/arch.png?v=11"/>
</div>
# Performance
Here's the most popular Golang frameworks and libraries performance testing result in `WEB Server`. Performance testing cases source codes are hosted at: https://github.com/gogf/gf-performance
## Environment
OS : Ubuntu 18.04 amd64
CPU : AMD A8-6600K x 4
MEM : 32GB
GO : v1.13.4
## Testing Tool
`ab`: Apache HTTP server benchmarking tool.
Command:
```
ab -t 10 -c 100 http://127.0.0.1:3000/hello
ab -t 10 -c 100 http://127.0.0.1:3000/query?id=10000
ab -t 10 -c 100 http://127.0.0.1:3000/json
```
The concurrency starts from `100` to `10000`.
> Run `5` times for each case of each project and pick up the best testing result.
## 1. Hello World
<table>
<tr>
<th>Throughputs</th>
<th>Mean Latency</th>
<th>P99 Latency</th>
</tr>
<tr>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/throughputs1.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/meanlatency1.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/p99latency1.jpeg"></td>
</tr>
</table>
## 2. Json Response
<table>
<tr>
<th>Throughputs</th>
<th>Mean Latency</th>
<th>P99 Latency</th>
</tr>
<tr>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/throughputs3.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/meanlatency3.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/p99latency3.jpeg"></td>
</tr>
</table>
# Documentation
* [APIDoc](https://godoc.org/github.com/gogf/gf)
* [中文文档](https://goframe.org)
* 中文官网: https://goframe.org
* GoDoc API: https://godoc.org/github.com/gogf/gf
# Discussion
- QQ Group[116707870](//shang.qq.com/wpa/qunwpa?idkey=195f91eceeb5d7fa76009b7cd5a4641f70bf4897b7f5a520635eb26ff17adfe7)
@ -43,20 +111,20 @@ golang version >= 1.13
> It's recommended learning `GoFrame` through its awesome source codes and API reference.
# Architecture
<div align=center>
<img src="https://goframe.org/images/arch.png?v=11"/>
</div>
# License
`GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.
# Known Users
Logos are not authorized to be shown due to trademark copyrights.
# Contributors
This project exists thanks to all the people who contribute. [[Contributors](https://github.com/gogf/gf/graphs/contributors)].
<a href="https://github.com/gogf/gf/graphs/contributors"><img src="https://opencollective.com/goframe/contributors.svg?width=890&button=false" /></a>
# Donators
We currently accept donation by Alipay/WechatPay, please note your github/gitee account in your payment bill. If you like `GF`, why not [buy developer a cup of coffee](DONATOR.MD)?
@ -65,6 +133,7 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
We appreciate any kind of sponsorship for `GF` development. If you've got some interesting, please contact WeChat `389961817` / Email `john@goframe.org`.
# Thanks
<a href="https://www.jetbrains.com/?from=GoFrame"><img src="https://goframe.org/images/jetbrains.png" width="100" alt="JetBrains"/></a>
@ -75,3 +144,7 @@ We appreciate any kind of sponsorship for `GF` development. If you've got some i

View File

@ -15,16 +15,17 @@
并提供了Web服务开发的系列核心组件Router、Cookie、Session、Middleware、服务注册、模板引擎等等
支持热重启、热更新、域名绑定、TLS/HTTPS、Rewrite等特性。
> 如果您初识`Go`语言,您可以将`GoFrame`类似于`PHP`中的`Laravel`, `Java`中的`SpringBoot`或者`Python`中的`Django`。
# 特点
* 模块化、松耦合设计;
* 模块丰富开箱即用;
* 简便易用易于维护;
* 社区活跃,大牛谦逊低调脾气好;
* 模块丰富开箱即用;
* 简便易用易于维护;
* 高代码质量、高单元测试覆盖率;
* 社区活跃,大牛谦逊低调脾气好;
* 详尽的开发文档及示例;
* 完善的本地中文化支持;
* 更适合企业及团队使用;
* 设计为团队及企业使用;
# 地址
- **主库**https://github.com/gogf/gf
@ -41,33 +42,101 @@ require github.com/gogf/gf latest
# 限制
```shell
golang版本 >= 1.13
golang版本 >= 1.11
```
# 模块
1. **核心模块**
`GoFrame`提供了一些基础的、常用的模块,简单、易用和轻量级,并保持极少的外部依赖,这些模块由`gf`主仓库细致维护。
1. **社区模块**
社区模块主要由社区贡献并维护,大部分也是由`gf`主仓库的贡献者提供及维护,存放于`gogf`空间下,与`gf`主仓库处于同一级别。有的社区模块是从`gf`主仓库中剥离出来单独维护的模块,这些模块并不是特别常用,或者对外部依赖较重。
# 架构
<div align=center>
<img src="https://goframe.org/images/arch.png?v=11"/>
</div>
# 性能
以下是目前最流行的`WEB Server` Golang框架/类库性能测试结果。
性能测试用例源代码仓库: https://github.com/gogf/gf-performance
## 环境:
OS : Ubuntu 18.04 amd64
CPU : AMD A8-6600K x 4
MEM : 32GB
GO : v1.13.4
## 工具
`ab`: Apache HTTP server benchmarking tool.
测试命令:
```
ab -t 10 -c 100 http://127.0.0.1:3000/hello
ab -t 10 -c 100 http://127.0.0.1:3000/query?id=10000
ab -t 10 -c 100 http://127.0.0.1:3000/json
```
并发客户端数量从 `100` 递增到 `10000`。
> 每个项目的每个用例均运行`5`次,取最优的结果展示。
## 1. Hello World
<table>
<tr>
<th>Throughputs</th>
<th>Mean Latency</th>
<th>P99 Latency</th>
</tr>
<tr>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/throughputs1.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/meanlatency1.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/p99latency1.jpeg"></td>
</tr>
</table>
## 2. Json Response
<table>
<tr>
<th>Throughputs</th>
<th>Mean Latency</th>
<th>P99 Latency</th>
</tr>
<tr>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/throughputs3.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/meanlatency3.jpeg"></td>
<td width="30%"><img src="http://gfcdn.johng.cn/images/performance/p99latency3.jpeg"></td>
</tr>
</table>
# 文档
开发文档:[https://goframe.org](https://goframe.org)
开发文档https://goframe.org
接口文档:[https://godoc.org/github.com/gogf/gf](https://godoc.org/github.com/gogf/gf)
接口文档https://godoc.org/github.com/gogf/gf
# 帮助
- QQ交流群[116707870](//shang.qq.com/wpa/qunwpa?idkey=195f91eceeb5d7fa76009b7cd5a4641f70bf4897b7f5a520635eb26ff17adfe7)
- WX交流群微信添加`389961817`备注`GF`
- 主库ISSUEhttps://github.com/gogf/gf/issues
> 建议通过阅读`Gorame`的源码以及API文档深度学习`GoFrame`,了解更多的精妙设计。
> 建议通过阅读`GoFrame`的源码以及API文档深度学习`GoFrame`,了解更多的精妙设计。
# 协议
`GF` 使用非常友好的 [MIT](LICENSE) 开源协议进行发布,永久`100%`开源免费。
# 用户
由于商标版权缘故,未经厂商商务部授权允许无法用于宣传展示。
# 贡献
感谢所有参与`GoFrame`开发的贡献者。 [[贡献者列表](https://github.com/gogf/gf/graphs/contributors)].
@ -84,4 +153,5 @@ golang版本 >= 1.13
赞助支持`GF`框架的快速研发,如果您感兴趣,请联系 微信 `389961817` / 邮件 `john@goframe.org`。
# 感谢
<a href="https://www.jetbrains.com/?from=GoFrame"><img src="https://goframe.org/images/jetbrains.png" width="100" alt="JetBrains"/></a>
<a href="https://www.jetbrains.com/?from=GoFrame"><img src="https://goframe.org/images/jetbrains.png" width="100" alt="JetBrains"/></a>

View File

@ -1,3 +1,351 @@
# `v1.12.1` (2020-03-31)
大家好啊!久等啦!
由于自从上次版本的发布以来,越来越多小伙伴加入了`GF`的大家庭,并提供了许多不错的建议和反馈,这次版本对其中大部分反馈进行了处理,包括大部分的改进建议和部分新特性,因此这次的版本发布时隔了两个多月。`GF`非常注重代码质量以及可持续维护性,这次版本也进一步对框架大部分模块的示例、注释和单元测试用例进行了完善,目前单元测试用例数量约为`1991`例,代码覆盖率为`71%`,覆盖了所有模块的绝大部分主要功能。
`GF`框架提供了比较常用、高质量的基础开发模块,轻量级、模块化、高性能,推荐大家阅读框架源码了解更多细节。本次发布有个别的不兼容升级,往往批量替换即可,以下`change log`比较完善,建议升级前仔细阅读。
本次发布即意味下一版本开发计划的开启欢迎更多小伙伴参与开源贡献https://github.com/gogf/gf/projects/8
感谢大家支持Enjoy your `GF`
# GoFrame
`GF(Go Frame)`是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设以及开发工具链提供了常用的基础开发模块缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。并提供了Web服务开发的系列核心组件Router、Cookie、Session、Middleware、服务注册、模板引擎等等支持热重启、热更新、域名绑定、TLS/HTTPS、Rewrite等特性。
## 特点
* 模块化、松耦合设计;
* 模块丰富,开箱即用;
* 简便易用,易于维护;
* 社区活跃,大牛谦逊低调脾气好;
* 高代码质量、高单元测试覆盖率;
* 详尽的开发文档及示例;
* 完善的本地中文化支持;
* 更适合企业及团队使用;
## 地址
- 官网https://goframe.org
- 主库https://github.com/gogf/gf
- 码云https://gitee.com/johng/gf
# Change Log
从`GF v1.12`版本开始,框架要求的最低`Golang`运行版本为`v1.13`,由于`Golang`新版本都是向后兼容的,因此推荐大家更新使用`Golang`新版本https://golang.google.cn/dl/
> 本次版本也新增了`Swagger`的工具及插件支持,另行独立发布。
## `tool chain`
1. `gen model`命令新增对`pgsql/mssql/sqlite/oracle`的模型生成支持。
1. `gen model`命令生成模型新增公开包变量`Columns`用于获得表的字段名称。
## `net`
1. `ghttp`
- 注意:从该版本开始,`Server`默认关闭了平滑重启特性。开发者可以通过相应的配置选项打开。
- 改进`Client.Get`方法,增加可选的请求参数。
- 新增`Client`链式操作方法:`Header`, `HeaderRaw`, `Cookie`, `ContentType`, `ContentJson`, `ContentXml`, `Timeout`, `BasicAuth`, `Ctx`https://goframe.org/net/ghttp/client/chain
- 新增`Request.GetCtx/GetCtxVar/SetCtxVar/Context`上下文变量管理方法,用于请求内部的上下文变量特性:
- 自定义变量https://goframe.org/net/ghttp/request/custom
- 上下文变量https://goframe.org/net/ghttp/request/context
- 新增`Request.GetUploadFile/GetUploadFiles`方法,以及`UploadFile`类型极大简化文件上传处理逻辑https://goframe.org/net/ghttp/client/demo/upload
- 新增`Request.GetPage`方法,用于便捷地获得分页对象:
- 基本介绍https://goframe.org/util/gpage/index
- 动态分页https://goframe.org/util/gpage/dynamic
- 静态分页https://goframe.org/util/gpage/static
- Ajax分页https://goframe.org/util/gpage/ajax
- URL模板https://goframe.org/util/gpage/template
- 自定义分页https://goframe.org/util/gpage/custom
- 改进`Response.Redirect*`方法增加自定义的跳转HTTP状态码参数。
- 改进`CORS`特性,完善跨域功能处理,并完全遵守`W3C`关于`OPTIONS`请求方法的规范约定https://goframe.org/net/ghttp/cors
- 模板视图对象增加`Request`内置变量,用于模板获得请求参数。由于`GF`框架的模板引擎采用两级缓存设计,减少`IO`开销的同时提升了执行效率,即使增加了大量的内置变量以及内置方法,经过大规模的性能测试,性能比其他`WebServer`库相同逻辑下高出`50% - 200%`的效率。
- 新增`Server`实验性的`Plugin`特性。
- 改进`Server`底层路由存储、检索逻辑,优化代码,提升效率。
- 改进分组路由注册的源码位置记录功能,当路由注册冲突时能够精准定位及提示重复路由源码位置。
- 改进`Server`日志处理。
- 完善单元测试。
## `database`
1. `gdb`
- 代码重构改进,增加`Driver`驱动接口设计,方便开发者自定义驱动实现。新增`Register`包方法用于开发者注册自定义的数据库类型驱动https://goframe.org/database/gdb/driver
- 新增`GetArray`接口及实现用于获取指定字段列的数据构造成数组返回https://goframe.org/database/gdb/chaining/select
- 新增`InsertIgnore`接口及实现,用于写入时忽略写入冲突,仅对`mysql`数据库类型有效https://goframe.org/database/gdb/chaining/insert-save
- 新增`Schema`接口及实现用于动态切换并获取指定名称的数据库对象https://goframe.org/database/gdb/chaining/schema
- 新增`FieldsStr/FieldsExStr`模型方法用于获取表字段并构造成字符串返回hhttps://goframe.org/database/gdb/chaining/fields-retrieve
- 新增`LockUpdate/LockShared`模型链式操作方法用于实现悲观锁操作https://goframe.org/database/gdb/chaining/lock
- 改进`Where/Data`方法对更新参数输入方式的支持,提高灵活性:
- https://goframe.org/database/gdb/chaining/select
- https://goframe.org/database/gdb/chaining/update-delete
- 查询结果对象`Result`新增`Array`方法用于获得指定字段的数值构造成数组返回https://goframe.org/database/gdb/result
- 改进`OmitEmpty`方法对`Data`输入参数的过滤,当给定的`Data`参数为空时间对象时,将会被过滤。
- 增加默认的数据库连接池参数:`MaxIdleConns=10`。
- 其他一些改进。
- 完善单元测试。
1. `gredis`
- 增加/修改默认的数据库连接池参数:`MaxIdle=10`, `IdleTimeout=10s`, `MaxConnLifetime=30s`, `Wait=true`。
- 完善单元测试。
## `container`
1. 所有容器对象新增`UnmarshalValue(interface{}) error`接口方法实现,用于`gconv`转换时根据任意类型变量创建/设置对象内容,`GF`框架的所有容器对象均实现了该接口。
1. `garray`
- 新增`RemoveValue`方法,用于根据数值检索并删除元素项。
- 新增`FilterNil`方法,用于遍历并删除数组中的`nil`元素项。
- 新增`FilterEmpty`方法,用于遍历并删除数组中的空值元素项,空值包括:`0, nil, false, "", len(slice/map/chan) == 0`。
- 改进`Set/Fill/InsertBefore/InsertAfter`方法严谨性,将原有的链式操作返回值对象修改为`error`返回值。
- 改进`Get/Remove/PopRand/PopLeft/PopRight/Rand`方法严谨性,增加`found`的`bool`返回值,标识当前方法是否有数据返回,当空数组或者操作越界时`found`返回值为`false`。
- 改变`Rands`方法原有逻辑,保证按照给定大小返回随机数组。
- 完善单元测试。
1. `gpool`
- 调整缓存池过期时间参数类型,旧版本为`int`类型表示毫秒,新版本为`time.Duration`类型使得时间控制更灵活。
- 内部代码优化,性能改进。
- 完善单元测试。
- 完善注释。
## `os`
1. `glog`
- 增加`Rotation`日志滚动切分特性,新增按照文件大小或过期时间进行日志切分,并支持切分文件数量限制、对日志文件进行自动压缩、可自定义压缩级别(`1-9`、支持对切分文件过期时间清理https://goframe.org/os/glog/rotate
- 新增`LevelPrefixes`特性支持对日志级别的前缀名称进行自定义https://goframe.org/os/glog/level
- 新增`SetLevelStr`方法,并增加按照字符串进行日志级别配置的特性:
- https://goframe.org/os/glog/level
- https://goframe.org/os/glog/config
- 新增`SetDefaultLogger`包方法,用于设置全局默认的`Logger`对象。
1. `gres`
1. 改进资源内容编码设计,采用新的压缩算法,减少资源文件大小,例如原本`15MB`的网站静态资源文件(`css/js/html`等),资源文件打包后约为`4MB`左右https://goframe.org/os/gres/index
1. 注意:该改进与旧版本无法兼容,需要重新打包原有的资源文件。
1. 完善单元测试。
1. `gcfg`
- 去掉配置对象属性的原子并发安全控制,改为普通变量类型。由于配置管理是最常用模块之一,因此确保高效的设计及方法实现。
- 单例对象做较大调整:为方便多文件场景下的配置文件调用,简便使用并提高开发效率,因此当给定的单例名称对应的`toml`配置文件在配置目录中存在时,将自动设置该单例对象的默认配置文件为该文件。例如:`g.Cfg("redis")`获取到的单例对象将会默认去检索并设置默认的配置文件为`redis.toml`,当该文件不存在时,则使用默认的配置文件(`config.toml`https://goframe.org/net/ghttp/config
- 完善单元测试。
1. `gview`
- 新增`concat`内置字符串拼接方法https://goframe.org/os/gview/function/buildin
- 完善单元测试。
1. `gfile`
- 改进`SelfPath`获取当前执行文件路径方法,提高执行效率。
- 改进`Join`文件路径连接方法,防止多余的路径连接符号。
- 改建`GetContents`文件内容获取方法执行效率,降低内存占用。
- 新增`StrToSize`方法,用于将大小字符串转换为字节数字,大小字符串例如`10m`, `5KB`, `1.25Gib`等。
- 新增`ReadLines/ReadByteLines`方法,用于按行读取指定文件内容,并给定读取回调函数。
- 完善单元测试。
1. `gtime`
- 改进`gtime.Time`对象实现,统一字符串打印时间格式为`Y-m-d H:i:s`,如:`2020-01-01 12:00:00`。
1. `gcmd`
- 命令行解析方法增加`strict`参数,用于设置当前解析是否严格解析,严格解析下如果给定了非法的选项,将会停止解析并返回错误。默认情况下为非严格解析。
1. `genv`
- 改进`Remove`删除环境变量键值对方法,增加对多个键值对环境变量的删除。
1. `gfpool`
- 改进代码实现,提高效率。
- 完善单元测试。
1. `gfsnotify`
- 改进包初始化方法,当系统原因引起默认`Watcher`对象创建失败时,直接`panic`。
1. `gproc`
- 改进`SearchBinaryPath`方法。
- 改进`Process.Kill`方法在`windows`及`*niux`平台下不同表现的处理。
## `encoding`
1. `gjson`
- 代码改进、完善注释、新增大量代码示例。
- 文档更新:
- 基本介绍https://goframe.org/encoding/gjson/index
- 对象创建https://goframe.org/encoding/gjson/object
- 层级访问https://goframe.org/encoding/gjson/pattern
- Struct转换https://goframe.org/encoding/gjson/conversion-struct
- 动态创建修改https://goframe.org/encoding/gjson/dataset
- 数据格式转换https://goframe.org/encoding/gjson/conversion-format
## `frame`
1. `g`
- 新增`IsNil`方法,用于判断当前给定的变量是否为`nil`,该方法有可能会使用到反射来实现判断。
- 新增`IsEmpty`方法,用于判断当前给定的变量是否为`空`,该方法优先使用断言判断但有可能会使用到反射来实现判断。空值包括:`0, nil, false, "", len(slice/map/chan) == 0`。
- 标记废弃`SetServerGraceful`方法,转而统一交给`Server`的配置来管理。
1. `gins`
- 改进代码结构,方便维护。
- 改进、完善单元测试。
1. `gmvc`
- 新增`M`类型,为`*gdb.Model`的别名简称,用于工具链自动生成模型中的`M`属性。
## `text`
1. `gstr`
- 新增`HasPrefix/HasSuffix`方法。
- 新增`OctStr`方法,用于将八进制字符串转换为其对应的`unicode`字符串,例如转换为中文。常用于`gRPC`底层通信编码中。
- 完善单元测试。
## `debug`
1. `gdebug`
- 改进代码结构,方便维护。
- 新增`TestDataPath`方法,用于当前包单元测试中获得当前包中`testdata`目录的绝对路径。
## `util`
1. `gconv`
- 改进`String`字符串转换方法,增加对`time.Time/*time.Time/gtime.Time`类型的内置支持。
- 改进`Map/Struct`转换方法,增加对一些特殊场景的细节处理。经过大规模的使用,大量的反馈改进,不断完善了细节。
- 改进`Struct`转换方法,增加对`UnmarshalValue(interface{}) error`接口的支持。
- 完善单元测试。
1. `grand`
- 注意:不兼容调整,原有的`Str`方法更名为`S`方法,用以获取指定长度的随机字符串、数字,并增加`symbol`参数,指定是否可以随机返回特殊可见字符。
- 新增`Str`方法,用于从指定的字符串字符中随机获取指定长度的字符串。该方法同时支持`unicode`字符串例如中文https://goframe.org/util/grand/index
- 新增`Symbols`方法用于随机返回指定场孤独的特殊可见字符https://goframe.org/util/grand/index
- 完善单元测试。
1. `gvalid`
- 长度校验规则增加对`unicode`字符串的支持,例如:中文。
# Bug Fix
1. 修复`Server`的视图对象配置失效问题。
1. 修复`Server`中间件在中间件`panic`情况下,忽略`Middleware.Next`方法控制,导致鉴权中间件失效的问题。
1. 修复`gudp.Server`在请求包大小超过`64bytes`时的断包问题,并调整默认缓冲区大小为`1024byte`,开发者可自定义缓冲区大小。
1. 修复`gfile.MTimeMillisecond`方法返回错误的文件修改毫秒时间戳。
1. 修复`gconv.Int64`对负数转换的支持。
1. 其他一些修复。
1. 详见https://github.com/gogf/gf/issues?q=label%3Abug
# `v1.11.2` (2020-01-14)
`GF(Go Frame)` https://goframe.org 是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设包括常用的核心开发组件 如:缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、资源管理、数据校验、数据编码、文件监控、 定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、并发安全容器等等。 并提供了Web服务开发的系列核心组件Router、Cookie、Session、Middleware、服务注册、配置管理、模板引擎等等 支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。
`GF`有着丰富的基础模块、完善的工具链、详尽的开发文档。开源近两年以来,`GF`得到越来越多小伙伴的肯定和支持从寂寂无名到现在被广泛应用于微服务、物联网、区块链、电商系统、银行系统等企业级的生产项目中经历了百万级、千万级项目的考验2019年度被码云`gitee`评选为`GVP`最有价值开源项目。`GF`正在快速地成长中目前保持着1-2个月迭代版本的发布规律社区活跃欢迎加入`GF`大家庭。
最后祝大家2020新年快乐鼠年大吉
## 新特性
1. 新年新气象官网文档大量更新https://goframe.org/index
1. `GF`工具链更新https://goframe.org/toolchain/cli
- 新增`gf run`热编译运行命令;
- 新增`gf docker` Docker镜像编译命令
- 新增`gf gen model` 强大的模型自动生成命令;
- `gf build`命令增加对配置文件配置支持;
- 大量命令行工具改进工作;
- 新增自动代理设置特性;
1. 数据库`ORM`新特性:
- 增加`prefix`数据表前缀支持https://goframe.org/database/gdb/config
- 新增`Schema`数据库对象并改进数据库切换特性https://goframe.org/database/gdb/chaining/schema
- 新增`WherePri`方法用于自动识别主键的条件方法https://goframe.org/database/gdb/chaining/select
- 文档及示例大量更新覆盖95%以上的功能特性;
## 功能改进
### `container`
1. `garray`
- 新增`New*ArrayRange`方法,用于初始化创建指定数值范围的数组。
- 新增`Iterator*`方法,用于数组项元素回调遍历。
- 完善单元测试。
1. `gvar`
- 改进`MapStrStr`、`MapStrStrDeep`方法实现。
### `net`
1. `ghttp`
- 改进HTTP客户端增加对提交参数的自动`Content-Type`识别功能。
- `Request`对象增加`Parse`方法,用于快捷的对象转换即参数校验。
- `Request.GetPost*`方法全部标记为`deprecated`,统一客户端参数提交方式为`QueryString`, `Form`, `Body`。
- 去掉`Response`模板解析时的`Get`/`Post`内置变量,新增`Query`, `Form`, `Request`内置变量https://goframe.org/net/ghttp/response/template
- 改进`Response.WriteJson*`及`Response.WriteXml*`方法,增加对`string`, `[]byte`类型参数的支持。
- `Server`新增`GetRouterArray`方法,用于向应用层暴露并获取`Server`的路由列表。
- `Server`新增`Use`方法,该方法为`BindMiddlewareDefault`的别名,用以全局中间件的注册。
- `Server`新增`RouteOverWrite`配置项,用于控制是否在注册路由冲突时自动覆盖,默认关闭并提示。
- `Server`新增`Graceful`配置项,用于在单服务场景下控制平滑重启特性的开启/关闭,默认开启。
- 完善单元测试。
1. `gtcp`
- 改进简单协议下的数据包发送接收功能。
- 将连接池默认的缓存过期时间`30`秒修改为`10`秒。
- 完善单元测试。
### `database`
1. `gdb`
- 新增`As`数据表别名方法。
- 改进数据表、字段的安全字符自动识别添加功能。
- 新增`DB`数据库对象切换方法。
- 新增`TX`链式操作事务支持方法。
- 完善单元测试。
### `os`
1. `gcfg`
- 新增`GetMapStrStr`方法。
1. `gcmd`
- 增加参数解析的`strict`严格参数,默认严格解析,不存在指定参数/选项名称时则报错返回。
1. `genv`
- 改进`Remove`方法支持多个环境变量的删除。
1. `gfile`
- 改进`TempDir`临时目录获取方法,在`*nix`系统下默认为`/tmp`目录。
- 新增`ReadLines`, `ReadByteLines`方法,用以按行回调读取文件内容。
- 新增`Copy*`方法,用以文件/目录的拷贝,支持递归。
- 新增`Replace*`方法,用以目录下的文件内容替换,支持递归。
- 改进`Scan*`方法,用以检索并返回指定目录下的所有文件/目录,支持文件模式指定,支持递归。
- 完善单元测试。
1. `gproc`
- 改进命令行运行方法。
- 改进`Shell`命令文件检索逻辑。
- 改进实验性的进程间通信设计。
1. `gtime`
- 将包方法以及`Time`对象的时间戳方法`Second`, `Millisecond`, `Microsecond`, `Nanosecond`标记为废除,
并新增`Timestamp`, `TimestampMilli`, `TimestampMicro`, `TimestampNano`替换。
- 需要注意的是以上修改可能和老版本存在兼容性问题。
1. `gview`
- 解析功能、缓存设计改进。
- 新增`encode`, `decode`HTML编码/解码模板函数。
- 新增`concat`字符串拼接模板函数。
- 新增`dump`模板函数,功能类似于`g.Dump`方法。
- 新增`AutoEncode`配置项,用于自动转码输出的`HTML`内容,常用于防止`XSS`,默认关闭。需要注意的是该特性并不会影响`include`内置函数: https://goframe.org/os/gview/xss
- 单元测试完善。
### `crypto`
1. `gmd5`
- 增加`MustEncrypt`, `MustEncryptBytes`, `MustEncryptString`, `MustEncryptFile`方法。
1. `gsha1`
- 增加`MustEncryptFile`方法
### `encoding`
1. `gbase64`
- 新增`MustEncodeFile`, `MustEncodeFileToString`, `MustDecode`, `MustDecodeToString`方法。
1. `gjson`/`gparser`
- 新增`GetMapStrStr`方法。
- 新增`Must*`方法,用于指定数据格式的转换失败时产生`panic`错误,而不会返回`error`参数。
### `util`
1. `gconv`
- 改进`Convert`方法增加对`[]int32`, `[]int64`, `[]uint`, `[]uint32`, `[]uint64`, `[]float32`, `[]float64`数据类型的转换支持。
- 改进`String`字符串转换方法对指针参数的支持。
- 改进`Map*` Map转换方法的代码结构及性能。
- 新增`Floats`, `Float32s`, `Float64s`对`[]float32`, `[]float64`类型转换方法。
- 新增`Ints`, `Int32s`, `Int64s`对`[]int`, `[]int32`, `[]int64`类型转换方法。
- 新增`Uints`, `Uint32s`, `Uint64s`对`[]uint`, `[]uint32`, `[]uint64`类型转换方法。
- 完善单元测试。
### `frame`
1. `gins`
- 所有的单例对象在获取失败时产生`panic`错误。
## Bug Fix
1. 增加对常见错误路由格式例如`/user//index`的兼容支持。
1. 修复`gtcp`/`gudp`在数据接收时的间隔时间单位问题。
1. 修复`gfile`/`gspath`/`gfsnotify`包对文件的存在性判断不严谨问题。
1. 修复`gproc.Kill`方法在`windows`系统下的运行阻塞问题。
1. 修复`gstr.TrimLeftStr`/`gstr.TrimRightStr`在被替换字符串长度小于替换字符串长度时的数组溢出问题。
# `v1.10.0` (2019-12-05)
各位`gfer`久等了,较上一次发布时间过去已有两个多月了,这段时间`GF`也在不断地迭代改进,细节比较多,拟了个大概,以下是`release log`。

View File

@ -1,3 +1,5 @@
> This markdown is deprecated and will be removed in future. All TODO features are staged in the issue: https://github.com/gogf/gf/issues
# ON THE WAY
1. 增加图形验证码支持,至少支持数字和英文字母;
1. Cookie&Session数据池化处理

View File

@ -4,5 +4,5 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package garray provides concurrent-safe/unsafe arrays.
// Package garray provides most commonly used array containers which also support concurrent-safe/unsafe switch feature.
package garray

View File

@ -8,6 +8,12 @@ package garray
import "strings"
// apiInterfaces is used for type assert api for Interfaces.
type apiInterfaces interface {
Interfaces() []interface{}
}
// defaultComparatorInt for int comparison.
func defaultComparatorInt(a, b int) int {
if a < b {
return -1
@ -18,6 +24,7 @@ func defaultComparatorInt(a, b int) int {
return 0
}
// defaultComparatorStr for string comparison.
func defaultComparatorStr(a, b string) int {
return strings.Compare(a, b)
}

View File

@ -9,6 +9,7 @@ package garray
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/gogf/gf/internal/empty"
"github.com/gogf/gf/text/gstr"
@ -20,8 +21,11 @@ import (
"github.com/gogf/gf/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
mu rwmutex.RWMutex
array []interface{}
}
@ -42,7 +46,7 @@ func NewArray(safe ...bool) *Array {
// which is false in default.
func NewArraySize(size int, cap int, safe ...bool) *Array {
return &Array{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: make([]interface{}, size, cap),
}
}
@ -77,7 +81,7 @@ func NewFromCopy(array []interface{}, safe ...bool) *Array {
// which is false in default.
func NewArrayFrom(array []interface{}, safe ...bool) *Array {
return &Array{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: array,
}
}
@ -89,26 +93,31 @@ func NewArrayFromCopy(array []interface{}, safe ...bool) *Array {
newArray := make([]interface{}, len(array))
copy(newArray, array)
return &Array{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: newArray,
}
}
// Get returns the value of the specified index,
// the caller should notice the boundary of the array.
func (a *Array) Get(index int) interface{} {
// 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 interface{}, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
if index < 0 || index >= len(a.array) {
return nil, false
}
return a.array[index], true
}
// Set sets value to specified index.
func (a *Array) Set(index int, value interface{}) *Array {
func (a *Array) Set(index int, value interface{}) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
}
a.array[index] = value
return a
return nil
}
// SetArray sets the underlying slice array with the given <array>.
@ -154,48 +163,60 @@ func (a *Array) SortFunc(less func(v1, v2 interface{}) bool) *Array {
}
// InsertBefore inserts the <value> to the front of <index>.
func (a *Array) InsertBefore(index int, value interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]interface{}{}, a.array[index:]...)
a.array = append(a.array[0:index], value)
a.array = append(a.array, rear...)
return a
}
// InsertAfter inserts the <value> to the back of <index>.
func (a *Array) InsertAfter(index int, value interface{}) *Array {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]interface{}{}, a.array[index+1:]...)
a.array = append(a.array[0:index+1], value)
a.array = append(a.array, rear...)
return a
}
// Remove removes an item by index.
func (a *Array) Remove(index int) interface{} {
func (a *Array) InsertBefore(index int, value interface{}) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return nil
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
}
// Determine array boundaries when deleting to improve deletion efficiency。
rear := append([]interface{}{}, a.array[index:]...)
a.array = append(a.array[0:index], value)
a.array = append(a.array, rear...)
return nil
}
// InsertAfter inserts the <value> to the back of <index>.
func (a *Array) InsertAfter(index int, value interface{}) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
}
rear := append([]interface{}{}, a.array[index+1:]...)
a.array = append(a.array[0:index+1], value)
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 *Array) Remove(index int) (value interface{}, 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 interface{}, 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
return value, true
} else if index == len(a.array)-1 {
value := a.array[index]
a.array = a.array[:index]
return value
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]
value = a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
return value
return value, true
}
// RemoveValue removes an item by value.
@ -226,64 +247,68 @@ func (a *Array) PushRight(value ...interface{}) *Array {
}
// PopRand randomly pops and return an item out of array.
func (a *Array) PopRand() interface{} {
return a.Remove(grand.Intn(len(a.array)))
// Note that if the array is empty, the <found> is false.
func (a *Array) PopRand() (value interface{}, found bool) {
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 *Array) PopRands(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
if size == 0 {
if size <= 0 || len(a.array) == 0 {
return nil
}
if size >= len(a.array) {
size = len(a.array)
}
array := make([]interface{}, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
array[i], _ = a.doRemoveWithoutLock(grand.Intn(len(a.array)))
}
return array
}
// PopLeft pops and returns an item from the beginning of array.
func (a *Array) PopLeft() interface{} {
// Note that if the array is empty, the <found> is false.
func (a *Array) PopLeft() (value interface{}, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.array) == 0 {
return nil
return nil, false
}
value := a.array[0]
value = a.array[0]
a.array = a.array[1:]
return value
return value, true
}
// PopRight pops and returns an item from the end of array.
func (a *Array) PopRight() interface{} {
// Note that if the array is empty, the <found> is false.
func (a *Array) PopRight() (value interface{}, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
if index <= 0 {
return nil
if index < 0 {
return nil, false
}
value := a.array[index]
value = a.array[index]
a.array = a.array[:index]
return value
return value, true
}
// PopLefts pops and returns <size> items from the beginning of array.
func (a *Array) PopLefts(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
if length == 0 {
if size <= 0 || len(a.array) == 0 {
return nil
}
if size > length {
size = length
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:]
@ -294,12 +319,14 @@ func (a *Array) PopLefts(size int) []interface{} {
func (a *Array) PopRights(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.array) == 0 {
if size <= 0 || len(a.array) == 0 {
return nil
}
index := len(a.array) - size
if index < 0 {
index = 0
if index <= 0 {
array := a.array
a.array = a.array[:0]
return array
}
value := a.array[index:]
a.array = a.array[:index]
@ -448,18 +475,7 @@ func (a *Array) Contains(value interface{}) bool {
// or returns -1 if not exists.
func (a *Array) Search(value interface{}) int {
a.mu.RLock()
result := -1
for index, v := range a.array {
if v == value {
result = index
break
}
}
a.mu.RUnlock()
return result
}
func (a *Array) doSearch(value interface{}) int {
defer a.mu.RUnlock()
if len(a.array) == 0 {
return -1
}
@ -474,6 +490,7 @@ func (a *Array) doSearch(value interface{}) int {
}
// Unique uniques the array, clear repeated items.
// Example: [1,1,2,3,2] -> [1,2,3]
func (a *Array) Unique() *Array {
a.mu.Lock()
for i := 0; i < len(a.array)-1; i++ {
@ -508,32 +525,16 @@ func (a *Array) RLockFunc(f func(array []interface{})) *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 interface{}) *Array {
switch v := array.(type) {
case *Array:
a.Append(gconv.Interfaces(v.Slice())...)
case *IntArray:
a.Append(gconv.Interfaces(v.Slice())...)
case *StrArray:
a.Append(gconv.Interfaces(v.Slice())...)
case *SortedArray:
a.Append(gconv.Interfaces(v.Slice())...)
case *SortedIntArray:
a.Append(gconv.Interfaces(v.Slice())...)
case *SortedStrArray:
a.Append(gconv.Interfaces(v.Slice())...)
default:
a.Append(gconv.Interfaces(array)...)
}
return a
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 interface{}) *Array {
func (a *Array) Fill(startIndex int, num int, value interface{}) error {
a.mu.Lock()
defer a.mu.Unlock()
if startIndex < 0 {
startIndex = 0
if startIndex < 0 || startIndex > len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", startIndex, len(a.array)))
}
for i := startIndex; i < startIndex+num; i++ {
if i > len(a.array)-1 {
@ -542,7 +543,7 @@ func (a *Array) Fill(startIndex int, num int, value interface{}) *Array {
a.array[i] = value
}
}
return a
return nil
}
// Chunk splits an array into multiple arrays,
@ -596,27 +597,27 @@ func (a *Array) Pad(size int, val interface{}) *Array {
}
// Rand randomly returns one item from array(no deleting).
func (a *Array) Rand() interface{} {
func (a *Array) Rand() (value interface{}, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
if len(a.array) == 0 {
return nil, false
}
return a.array[grand.Intn(len(a.array))], true
}
// Rands randomly returns <size> items from array(no deleting).
func (a *Array) Rands(size int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
size = len(a.array)
if size <= 0 || len(a.array) == 0 {
return nil
}
n := make([]interface{}, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size-1 {
break
}
array := make([]interface{}, size)
for i := 0; i < size; i++ {
array[i] = a.array[grand.Intn(len(a.array))]
}
return n
return array
}
// Shuffle randomly shuffles the array.
@ -643,6 +644,9 @@ func (a *Array) Reverse() *Array {
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))
@ -669,7 +673,7 @@ func (a *Array) Iterator(f func(k int, v interface{}) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array in ascending order with given callback function <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 *Array) IteratorAsc(f func(k int, v interface{}) bool) {
a.mu.RLock()
@ -681,7 +685,7 @@ func (a *Array) IteratorAsc(f func(k int, v interface{}) bool) {
}
}
// IteratorDesc iterates the array in descending order with given callback function <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 interface{}) bool) {
a.mu.RLock()
@ -716,7 +720,8 @@ func (a *Array) String() string {
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (a *Array) MarshalJSON() ([]byte, error) {
// 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)
@ -724,8 +729,7 @@ func (a *Array) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (a *Array) UnmarshalJSON(b []byte) error {
if a.mu == nil {
a.mu = rwmutex.New()
if a.array == nil {
a.array = make([]interface{}, 0)
}
a.mu.Lock()
@ -738,9 +742,6 @@ func (a *Array) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for array.
func (a *Array) UnmarshalValue(value interface{}) error {
if a.mu == nil {
a.mu = rwmutex.New()
}
a.mu.Lock()
defer a.mu.Unlock()
switch value.(type) {
@ -781,6 +782,16 @@ func (a *Array) FilterEmpty() *Array {
return a
}
// Walk applies a user supplied function <f> to every item of array.
func (a *Array) Walk(f func(value interface{}) interface{}) *Array {
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 *Array) IsEmpty() bool {
return a.Len() == 0

View File

@ -9,6 +9,7 @@ package garray
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"math"
"sort"
@ -18,8 +19,11 @@ import (
"github.com/gogf/gf/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
mu rwmutex.RWMutex
array []int
}
@ -35,7 +39,7 @@ func NewIntArray(safe ...bool) *IntArray {
// which is false in default.
func NewIntArraySize(size int, cap int, safe ...bool) *IntArray {
return &IntArray{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: make([]int, size, cap),
}
}
@ -60,7 +64,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.New(safe...),
mu: rwmutex.Create(safe...),
array: array,
}
}
@ -72,26 +76,31 @@ func NewIntArrayFromCopy(array []int, safe ...bool) *IntArray {
newArray := make([]int, len(array))
copy(newArray, array)
return &IntArray{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: newArray,
}
}
// Get returns the value of the specified index,
// the caller should notice the boundary of the array.
func (a *IntArray) Get(index int) int {
// 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()
value := a.array[index]
return value
if index < 0 || index >= len(a.array) {
return 0, false
}
return a.array[index], true
}
// Set sets value to specified index.
func (a *IntArray) Set(index int, value int) *IntArray {
func (a *IntArray) Set(index int, value int) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
}
a.array[index] = value
return a
return nil
}
// SetArray sets the underlying slice array with the given <array>.
@ -127,8 +136,7 @@ func (a *IntArray) Sum() (sum int) {
}
// Sort sorts the array in increasing order.
// The parameter <reverse> controls whether sort
// in increasing order(default) or decreasing order
// The parameter <reverse> controls whether sort in increasing order(default) or decreasing order.
func (a *IntArray) Sort(reverse ...bool) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
@ -156,57 +164,68 @@ func (a *IntArray) SortFunc(less func(v1, v2 int) bool) *IntArray {
}
// InsertBefore inserts the <value> to the front of <index>.
func (a *IntArray) InsertBefore(index int, value int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]int{}, a.array[index:]...)
a.array = append(a.array[0:index], value)
a.array = append(a.array, rear...)
return a
}
// InsertAfter inserts the <value> to the back of <index>.
func (a *IntArray) InsertAfter(index int, value int) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]int{}, a.array[index+1:]...)
a.array = append(a.array[0:index+1], value)
a.array = append(a.array, rear...)
return a
}
// Remove removes an item by index.
// Note that if the index is out of range of array, it returns 0.
func (a *IntArray) Remove(index int) int {
func (a *IntArray) InsertBefore(index int, value int) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return 0
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
}
rear := append([]int{}, a.array[index:]...)
a.array = append(a.array[0:index], value)
a.array = append(a.array, rear...)
return nil
}
// InsertAfter inserts the <value> to the back of <index>.
func (a *IntArray) InsertAfter(index int, value int) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return errors.New(fmt.Sprintf("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], value)
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 *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
return value, true
} else if index == len(a.array)-1 {
value := a.array[index]
a.array = a.array[:index]
return value
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]
value = a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
return value
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 *IntArray) RemoveValue(value int) bool {
if i := a.Search(value); i != -1 {
a.Remove(i)
return true
_, found := a.Remove(i)
return found
}
return false
}
@ -229,72 +248,72 @@ func (a *IntArray) PushRight(value ...int) *IntArray {
}
// PopLeft pops and returns an item from the beginning of array.
// Note that if the array is empty, it returns 0.
// Be very careful when use this function in loop statement.
// You can use IsEmpty() of Len() == 0 checks if this array empty.
func (a *IntArray) PopLeft() int {
// 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
return 0, false
}
value := a.array[0]
value = a.array[0]
a.array = a.array[1:]
return value
return value, true
}
// PopRight pops and returns an item from the end of array.
// Note that if the array is empty, it returns 0.
// Be very careful when use this function in loop statement.
// You can use IsEmpty() of Len() == 0 checks if this array empty.
func (a *IntArray) PopRight() int {
// 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
if index < 0 {
return 0, false
}
value := a.array[index]
value = a.array[index]
a.array = a.array[:index]
return value
return value, true
}
// PopRand randomly pops and return an item out of array.
// Be very careful when use this function in loop statement.
// You can use IsEmpty() of Len() == 0 checks if this array empty.
func (a *IntArray) PopRand() int {
return a.Remove(grand.Intn(len(a.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)))
}
// 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 > len(a.array) {
size = len(a.array)
}
if size == 0 {
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++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
array[i], _ = a.doRemoveWithoutLock(grand.Intn(len(a.array)))
}
return array
}
// 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()
length := len(a.array)
if length == 0 {
if size <= 0 || len(a.array) == 0 {
return nil
}
if size > length {
size = length
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:]
@ -302,15 +321,19 @@ func (a *IntArray) PopLefts(size int) []int {
}
// 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 len(a.array) == 0 {
if size <= 0 || len(a.array) == 0 {
return nil
}
index := len(a.array) - size
if index < 0 {
index = 0
if index <= 0 {
array := a.array
a.array = a.array[:0]
return array
}
value := a.array[index:]
a.array = a.array[:index]
@ -468,6 +491,10 @@ func (a *IntArray) Contains(value int) bool {
// or returns -1 if not exists.
func (a *IntArray) Search(value int) int {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return -1
}
result := -1
for index, v := range a.array {
if v == value {
@ -475,11 +502,11 @@ func (a *IntArray) Search(value int) int {
break
}
}
a.mu.RUnlock()
return result
}
// Unique uniques the array, clear repeated items.
// Example: [1,1,2,3,2] -> [1,2,3]
func (a *IntArray) Unique() *IntArray {
a.mu.Lock()
for i := 0; i < len(a.array)-1; i++ {
@ -514,32 +541,16 @@ func (a *IntArray) RLockFunc(f func(array []int)) *IntArray {
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *IntArray) Merge(array interface{}) *IntArray {
switch v := array.(type) {
case *Array:
a.Append(gconv.Ints(v.Slice())...)
case *IntArray:
a.Append(gconv.Ints(v.Slice())...)
case *StrArray:
a.Append(gconv.Ints(v.Slice())...)
case *SortedArray:
a.Append(gconv.Ints(v.Slice())...)
case *SortedIntArray:
a.Append(gconv.Ints(v.Slice())...)
case *SortedStrArray:
a.Append(gconv.Ints(v.Slice())...)
default:
a.Append(gconv.Ints(array)...)
}
return a
return a.Append(gconv.Ints(array)...)
}
// 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) *IntArray {
func (a *IntArray) Fill(startIndex int, num int, value int) error {
a.mu.Lock()
defer a.mu.Unlock()
if startIndex < 0 {
startIndex = 0
if startIndex < 0 || startIndex > len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", startIndex, len(a.array)))
}
for i := startIndex; i < startIndex+num; i++ {
if i > len(a.array)-1 {
@ -548,7 +559,7 @@ func (a *IntArray) Fill(startIndex int, num int, value int) *IntArray {
a.array[i] = value
}
}
return a
return nil
}
// Chunk splits an array into multiple arrays,
@ -602,27 +613,27 @@ func (a *IntArray) Pad(size int, value int) *IntArray {
}
// Rand randomly returns one item from array(no deleting).
func (a *IntArray) Rand() int {
func (a *IntArray) Rand() (value int, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
if len(a.array) == 0 {
return 0, false
}
return a.array[grand.Intn(len(a.array))], true
}
// 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 > len(a.array) {
size = len(a.array)
if size <= 0 || len(a.array) == 0 {
return nil
}
n := make([]int, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size-1 {
break
}
array := make([]int, size)
for i := 0; i < size; i++ {
array[i] = a.array[grand.Intn(len(a.array))]
}
return n
return array
}
// Shuffle randomly shuffles the array.
@ -649,6 +660,9 @@ func (a *IntArray) Reverse() *IntArray {
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))
@ -675,7 +689,7 @@ func (a *IntArray) Iterator(f func(k int, v int) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array in ascending order with given callback function <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 *IntArray) IteratorAsc(f func(k int, v int) bool) {
a.mu.RLock()
@ -687,7 +701,7 @@ func (a *IntArray) IteratorAsc(f func(k int, v int) bool) {
}
}
// IteratorDesc iterates the array in descending order with given callback function <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()
@ -705,7 +719,8 @@ func (a *IntArray) String() string {
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (a *IntArray) MarshalJSON() ([]byte, error) {
// 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)
@ -713,8 +728,7 @@ func (a *IntArray) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (a *IntArray) UnmarshalJSON(b []byte) error {
if a.mu == nil {
a.mu = rwmutex.New()
if a.array == nil {
a.array = make([]int, 0)
}
a.mu.Lock()
@ -727,9 +741,6 @@ func (a *IntArray) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for array.
func (a *IntArray) UnmarshalValue(value interface{}) error {
if a.mu == nil {
a.mu = rwmutex.New()
}
a.mu.Lock()
defer a.mu.Unlock()
switch value.(type) {
@ -755,6 +766,16 @@ func (a *IntArray) FilterEmpty() *IntArray {
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)
}
return a
}
// IsEmpty checks whether the array is empty.
func (a *IntArray) IsEmpty() bool {
return a.Len() == 0

View File

@ -9,6 +9,8 @@ package garray
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/gogf/gf/text/gstr"
"math"
"sort"
@ -19,8 +21,11 @@ import (
"github.com/gogf/gf/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
mu rwmutex.RWMutex
array []string
}
@ -36,7 +41,7 @@ func NewStrArray(safe ...bool) *StrArray {
// which is false in default.
func NewStrArraySize(size int, cap int, safe ...bool) *StrArray {
return &StrArray{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: make([]string, size, cap),
}
}
@ -46,7 +51,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.New(safe...),
mu: rwmutex.Create(safe...),
array: array,
}
}
@ -58,26 +63,31 @@ func NewStrArrayFromCopy(array []string, safe ...bool) *StrArray {
newArray := make([]string, len(array))
copy(newArray, array)
return &StrArray{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: newArray,
}
}
// Get returns the value of the specified index,
// the caller should notice the boundary of the array.
func (a *StrArray) Get(index int) string {
// 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()
value := a.array[index]
return value
if index < 0 || index >= len(a.array) {
return "", false
}
return a.array[index], true
}
// Set sets value to specified index.
func (a *StrArray) Set(index int, value string) *StrArray {
func (a *StrArray) Set(index int, value string) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
}
a.array[index] = value
return a
return nil
}
// SetArray sets the underlying slice array with the given <array>.
@ -142,57 +152,68 @@ func (a *StrArray) SortFunc(less func(v1, v2 string) bool) *StrArray {
}
// InsertBefore inserts the <value> to the front of <index>.
func (a *StrArray) InsertBefore(index int, value string) *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]string{}, a.array[index:]...)
a.array = append(a.array[0:index], value)
a.array = append(a.array, rear...)
return a
}
// InsertAfter inserts the <value> to the back of <index>.
func (a *StrArray) InsertAfter(index int, value string) *StrArray {
a.mu.Lock()
defer a.mu.Unlock()
rear := append([]string{}, a.array[index+1:]...)
a.array = append(a.array[0:index+1], value)
a.array = append(a.array, rear...)
return a
}
// Remove removes an item by index.
// Note that if the index is out of range of array, it returns an empty string.
func (a *StrArray) Remove(index int) string {
func (a *StrArray) InsertBefore(index int, value string) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return ""
return errors.New(fmt.Sprintf("index %d out of array range %d", index, len(a.array)))
}
// Determine array boundaries when deleting to improve deletion efficiency。
rear := append([]string{}, a.array[index:]...)
a.array = append(a.array[0:index], value)
a.array = append(a.array, rear...)
return nil
}
// InsertAfter inserts the <value> to the back of <index>.
func (a *StrArray) InsertAfter(index int, value string) error {
a.mu.Lock()
defer a.mu.Unlock()
if index < 0 || index >= len(a.array) {
return errors.New(fmt.Sprintf("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], value)
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 *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
return value, true
} else if index == len(a.array)-1 {
value := a.array[index]
a.array = a.array[:index]
return value
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]
value = a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
return value
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 *StrArray) RemoveValue(value string) bool {
if i := a.Search(value); i != -1 {
a.Remove(i)
return true
_, found := a.Remove(i)
return found
}
return false
}
@ -215,89 +236,92 @@ func (a *StrArray) PushRight(value ...string) *StrArray {
}
// PopLeft pops and returns an item from the beginning of array.
// Note that if the array is empty, it returns an empty string.
// Be very careful when use this function in loop statement.
// You can use IsEmpty() of Len() == 0 checks if this array empty.
func (a *StrArray) PopLeft() string {
// 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 ""
return "", false
}
value := a.array[0]
value = a.array[0]
a.array = a.array[1:]
return value
return value, true
}
// PopRight pops and returns an item from the end of array.
// Note that if the array is empty, it returns an empty string.
// Be very careful when use this function in loop statement.
// You can use IsEmpty() of Len() == 0 checks if this array empty.
func (a *StrArray) PopRight() string {
// 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 ""
if index < 0 {
return "", false
}
value := a.array[index]
value = a.array[index]
a.array = a.array[:index]
return value
return value, true
}
// PopRand randomly pops and return an item out of array.
// Note that if the array is empty, it returns an empty string.
// Be very careful when use this function in loop statement.
// You can use IsEmpty() of Len() == 0 checks if this array empty.
func (a *StrArray) PopRand() string {
return a.Remove(grand.Intn(len(a.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)))
}
// 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 > len(a.array) {
size = len(a.array)
}
if size == 0 {
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++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
array[i], _ = a.doRemoveWithoutLock(grand.Intn(len(a.array)))
}
return array
}
// 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()
length := len(a.array)
if size > length {
size = length
}
if size == 0 {
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.
// 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 len(a.array) == 0 {
if size <= 0 || len(a.array) == 0 {
return nil
}
index := len(a.array) - size
if index < 0 {
index = 0
if index <= 0 {
array := a.array
a.array = a.array[:0]
return array
}
value := a.array[index:]
a.array = a.array[:index]
@ -451,13 +475,30 @@ func (a *StrArray) Contains(value string) bool {
return a.Search(value) != -1
}
// 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.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return false
}
for _, v := range a.array {
if strings.EqualFold(v, value) {
return true
}
}
return false
}
// 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()
if len(a.array) == 0 {
return -1
}
a.mu.RLock()
result := -1
for index, v := range a.array {
if strings.Compare(v, value) == 0 {
@ -465,11 +506,11 @@ func (a *StrArray) Search(value string) int {
break
}
}
a.mu.RUnlock()
return result
}
// Unique uniques the array, clear repeated items.
// Example: [1,1,2,3,2] -> [1,2,3]
func (a *StrArray) Unique() *StrArray {
a.mu.Lock()
for i := 0; i < len(a.array)-1; i++ {
@ -504,32 +545,16 @@ func (a *StrArray) RLockFunc(f func(array []string)) *StrArray {
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *StrArray) Merge(array interface{}) *StrArray {
switch v := array.(type) {
case *Array:
a.Append(gconv.Strings(v.Slice())...)
case *IntArray:
a.Append(gconv.Strings(v.Slice())...)
case *StrArray:
a.Append(gconv.Strings(v.Slice())...)
case *SortedArray:
a.Append(gconv.Strings(v.Slice())...)
case *SortedIntArray:
a.Append(gconv.Strings(v.Slice())...)
case *SortedStrArray:
a.Append(gconv.Strings(v.Slice())...)
default:
a.Append(gconv.Strings(array)...)
}
return a
return a.Append(gconv.Strings(array)...)
}
// 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) *StrArray {
func (a *StrArray) Fill(startIndex int, num int, value string) error {
a.mu.Lock()
defer a.mu.Unlock()
if startIndex < 0 {
startIndex = 0
if startIndex < 0 || startIndex > len(a.array) {
return errors.New(fmt.Sprintf("index %d out of array range %d", startIndex, len(a.array)))
}
for i := startIndex; i < startIndex+num; i++ {
if i > len(a.array)-1 {
@ -538,7 +563,7 @@ func (a *StrArray) Fill(startIndex int, num int, value string) *StrArray {
a.array[i] = value
}
}
return a
return nil
}
// Chunk splits an array into multiple arrays,
@ -592,27 +617,27 @@ func (a *StrArray) Pad(size int, value string) *StrArray {
}
// Rand randomly returns one item from array(no deleting).
func (a *StrArray) Rand() string {
func (a *StrArray) Rand() (value string, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
if len(a.array) == 0 {
return "", false
}
return a.array[grand.Intn(len(a.array))], true
}
// 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 > len(a.array) {
size = len(a.array)
if size <= 0 || len(a.array) == 0 {
return nil
}
n := make([]string, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size-1 {
break
}
array := make([]string, size)
for i := 0; i < size; i++ {
array[i] = a.array[grand.Intn(len(a.array))]
}
return n
return array
}
// Shuffle randomly shuffles the array.
@ -639,6 +664,9 @@ func (a *StrArray) Reverse() *StrArray {
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)
@ -665,7 +693,7 @@ func (a *StrArray) Iterator(f func(k int, v string) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array in ascending order with given callback function <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 *StrArray) IteratorAsc(f func(k int, v string) bool) {
a.mu.RLock()
@ -677,7 +705,7 @@ func (a *StrArray) IteratorAsc(f func(k int, v string) bool) {
}
}
// IteratorDesc iterates the array in descending order with given callback function <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()
@ -706,7 +734,8 @@ func (a *StrArray) String() string {
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (a *StrArray) MarshalJSON() ([]byte, error) {
// 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)
@ -714,8 +743,7 @@ func (a *StrArray) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (a *StrArray) UnmarshalJSON(b []byte) error {
if a.mu == nil {
a.mu = rwmutex.New()
if a.array == nil {
a.array = make([]string, 0)
}
a.mu.Lock()
@ -728,9 +756,6 @@ func (a *StrArray) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for array.
func (a *StrArray) UnmarshalValue(value interface{}) error {
if a.mu == nil {
a.mu = rwmutex.New()
}
a.mu.Lock()
defer a.mu.Unlock()
switch value.(type) {
@ -756,6 +781,16 @@ func (a *StrArray) FilterEmpty() *StrArray {
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)
}
return a
}
// IsEmpty checks whether the array is empty.
func (a *StrArray) IsEmpty() bool {
return a.Len() == 0

View File

@ -16,17 +16,20 @@ import (
"math"
"sort"
"github.com/gogf/gf/container/gtype"
"github.com/gogf/gf/internal/rwmutex"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/grand"
)
// It's using increasing order in default.
// SortedArray is a golang sorted array with rich features.
// It is using increasing order in default, which can be changed by
// setting it a custom comparator.
// It contains a concurrent-safe/unsafe switch, which should be set
// when its initialization and cannot be changed then.
type SortedArray struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
array []interface{}
unique *gtype.Bool // Whether enable unique feature(false)
unique bool // Whether enable unique feature(false)
comparator func(a, b interface{}) int // Comparison function(it returns -1: a < b; 0: a == b; 1: a > b)
}
@ -45,8 +48,7 @@ func NewSortedArray(comparator func(a, b interface{}) int, safe ...bool) *Sorted
// which is false in default.
func NewSortedArraySize(cap int, comparator func(a, b interface{}) int, safe ...bool) *SortedArray {
return &SortedArray{
mu: rwmutex.New(safe...),
unique: gtype.NewBool(),
mu: rwmutex.Create(safe...),
array: make([]interface{}, 0, cap),
comparator: comparator,
}
@ -74,7 +76,7 @@ func NewSortedArrayFrom(array []interface{}, comparator func(a, b interface{}) i
a := NewSortedArraySize(0, comparator, safe...)
a.array = array
sort.Slice(a.array, func(i, j int) bool {
return a.comparator(a.array[i], a.array[j]) < 0
return a.getComparator()(a.array[i], a.array[j]) < 0
})
return a
}
@ -94,19 +96,19 @@ func (a *SortedArray) SetArray(array []interface{}) *SortedArray {
defer a.mu.Unlock()
a.array = array
sort.Slice(a.array, func(i, j int) bool {
return a.comparator(a.array[i], a.array[j]) < 0
return a.getComparator()(a.array[i], a.array[j]) < 0
})
return a
}
// SetComparator sets/changes the comparator for sorting.
// It resorts the array as the comparator is changed.
func (a *SortedArray) SetComparator(comparator func(a, b interface{}) int) {
a.mu.Lock()
defer a.mu.Unlock()
a.comparator = comparator
// Resort the array if comparator is changed.
sort.Slice(a.array, func(i, j int) bool {
return a.comparator(a.array[i], a.array[j]) < 0
return a.getComparator()(a.array[i], a.array[j]) < 0
})
}
@ -117,13 +119,19 @@ func (a *SortedArray) Sort() *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Slice(a.array, func(i, j int) bool {
return a.comparator(a.array[i], a.array[j]) < 0
return a.getComparator()(a.array[i], a.array[j]) < 0
})
return a
}
// Add adds one or multiple values to sorted array, the array always keeps sorted.
// It's alias of function Append, see Append.
func (a *SortedArray) Add(values ...interface{}) *SortedArray {
return a.Append(values...)
}
// Append adds one or multiple values to sorted array, the array always keeps sorted.
func (a *SortedArray) Append(values ...interface{}) *SortedArray {
if len(values) == 0 {
return a
}
@ -131,7 +139,7 @@ func (a *SortedArray) Add(values ...interface{}) *SortedArray {
defer a.mu.Unlock()
for _, value := range values {
index, cmp := a.binSearch(value, false)
if a.unique.Val() && cmp == 0 {
if a.unique && cmp == 0 {
continue
}
if index < 0 {
@ -148,38 +156,46 @@ func (a *SortedArray) Add(values ...interface{}) *SortedArray {
return a
}
// Get returns the value of the specified index,
// the caller should notice the boundary of the array.
func (a *SortedArray) Get(index int) interface{} {
// 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 *SortedArray) Get(index int) (value interface{}, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
if index < 0 || index >= len(a.array) {
return nil, false
}
return a.array[index], true
}
// Remove removes an item by index.
func (a *SortedArray) Remove(index int) interface{} {
// If the given <index> is out of range of the array, the <found> is false.
func (a *SortedArray) Remove(index int) (value interface{}, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
return a.doRemoveWithoutLock(index)
}
// doRemoveWithoutLock removes an item by index without lock.
func (a *SortedArray) doRemoveWithoutLock(index int) (value interface{}, found bool) {
if index < 0 || index >= len(a.array) {
return nil
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
return value, true
} else if index == len(a.array)-1 {
value := a.array[index]
a.array = a.array[:index]
return value
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]
value = a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
return value
return value, true
}
// RemoveValue removes an item by value.
@ -193,50 +209,53 @@ func (a *SortedArray) RemoveValue(value interface{}) bool {
}
// PopLeft pops and returns an item from the beginning of array.
func (a *SortedArray) PopLeft() interface{} {
// Note that if the array is empty, the <found> is false.
func (a *SortedArray) PopLeft() (value interface{}, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.array) == 0 {
return nil
return nil, false
}
value := a.array[0]
value = a.array[0]
a.array = a.array[1:]
return value
return value, true
}
// PopRight pops and returns an item from the end of array.
func (a *SortedArray) PopRight() interface{} {
// Note that if the array is empty, the <found> is false.
func (a *SortedArray) PopRight() (value interface{}, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
if index <= 0 {
return nil
if index < 0 {
return nil, false
}
value := a.array[index]
value = a.array[index]
a.array = a.array[:index]
return value
return value, true
}
// PopRand randomly pops and return an item out of array.
func (a *SortedArray) PopRand() interface{} {
return a.Remove(grand.Intn(len(a.array)))
// Note that if the array is empty, the <found> is false.
func (a *SortedArray) PopRand() (value interface{}, found bool) {
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 *SortedArray) PopRands(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
if size == 0 {
if size <= 0 || len(a.array) == 0 {
return nil
}
if size >= len(a.array) {
size = len(a.array)
}
array := make([]interface{}, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
array[i], _ = a.doRemoveWithoutLock(grand.Intn(len(a.array)))
}
return array
}
@ -245,13 +264,14 @@ func (a *SortedArray) PopRands(size int) []interface{} {
func (a *SortedArray) PopLefts(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
if size > length {
size = length
}
if size == 0 {
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
@ -261,12 +281,14 @@ func (a *SortedArray) PopLefts(size int) []interface{} {
func (a *SortedArray) PopRights(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.array) == 0 {
if size <= 0 || len(a.array) == 0 {
return nil
}
index := len(a.array) - size
if index < 0 {
index = 0
if index <= 0 {
array := a.array
a.array = a.array[:0]
return array
}
value := a.array[index:]
a.array = a.array[:index]
@ -375,7 +397,7 @@ func (a *SortedArray) Len() int {
// 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 *SortedArray) Slice() []interface{} {
array := ([]interface{})(nil)
var array []interface{}
if a.mu.IsSafe() {
a.mu.RLock()
defer a.mu.RUnlock()
@ -412,20 +434,20 @@ func (a *SortedArray) Search(value interface{}) (index int) {
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
// If <result> greater than 0, it means the value at <index> is greater than <value>.
func (a *SortedArray) binSearch(value interface{}, lock bool) (index int, result int) {
if len(a.array) == 0 {
return -1, -2
}
if lock {
a.mu.RLock()
defer a.mu.RUnlock()
}
if len(a.array) == 0 {
return -1, -2
}
min := 0
max := len(a.array) - 1
mid := 0
cmp := -2
for min <= max {
mid = int((min + max) / 2)
cmp = a.comparator(value, a.array[mid])
mid = (min + max) / 2
cmp = a.getComparator()(value, a.array[mid])
switch {
case cmp < 0:
max = mid - 1
@ -442,8 +464,8 @@ func (a *SortedArray) binSearch(value interface{}, lock bool) (index int, result
// which means it does not contain any repeated items.
// It also do unique check, remove all repeated items.
func (a *SortedArray) SetUnique(unique bool) *SortedArray {
oldUnique := a.unique.Val()
a.unique.Set(unique)
oldUnique := a.unique
a.unique = unique
if unique && oldUnique != unique {
a.Unique()
}
@ -462,7 +484,7 @@ func (a *SortedArray) Unique() *SortedArray {
if i == len(a.array)-1 {
break
}
if a.comparator(a.array[i], a.array[i+1]) == 0 {
if a.getComparator()(a.array[i], a.array[i+1]) == 0 {
a.array = append(a.array[:i+1], a.array[i+1+1:]...)
} else {
i++
@ -494,6 +516,12 @@ func (a *SortedArray) Clear() *SortedArray {
func (a *SortedArray) LockFunc(f func(array []interface{})) *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
// Keep the array always sorted.
defer sort.Slice(a.array, func(i, j int) bool {
return a.getComparator()(a.array[i], a.array[j]) < 0
})
f(a.array)
return a
}
@ -511,23 +539,7 @@ func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray {
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *SortedArray) Merge(array interface{}) *SortedArray {
switch v := array.(type) {
case *Array:
a.Add(gconv.Interfaces(v.Slice())...)
case *IntArray:
a.Add(gconv.Interfaces(v.Slice())...)
case *StrArray:
a.Add(gconv.Interfaces(v.Slice())...)
case *SortedArray:
a.Add(gconv.Interfaces(v.Slice())...)
case *SortedIntArray:
a.Add(gconv.Interfaces(v.Slice())...)
case *SortedStrArray:
a.Add(gconv.Interfaces(v.Slice())...)
default:
a.Add(gconv.Interfaces(array)...)
}
return a
return a.Add(gconv.Interfaces(array)...)
}
// Chunk splits an array into multiple arrays,
@ -554,33 +566,36 @@ func (a *SortedArray) Chunk(size int) [][]interface{} {
}
// Rand randomly returns one item from array(no deleting).
func (a *SortedArray) Rand() interface{} {
func (a *SortedArray) Rand() (value interface{}, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
if len(a.array) == 0 {
return nil, false
}
return a.array[grand.Intn(len(a.array))], true
}
// Rands randomly returns <size> items from array(no deleting).
func (a *SortedArray) Rands(size int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
size = len(a.array)
if size <= 0 || len(a.array) == 0 {
return nil
}
n := make([]interface{}, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size-1 {
break
}
array := make([]interface{}, size)
for i := 0; i < size; i++ {
array[i] = a.array[grand.Intn(len(a.array))]
}
return n
return array
}
// Join joins array elements with a string <glue>.
func (a *SortedArray) 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))
@ -607,7 +622,7 @@ func (a *SortedArray) Iterator(f func(k int, v interface{}) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array in ascending order with given callback function <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 *SortedArray) IteratorAsc(f func(k int, v interface{}) bool) {
a.mu.RLock()
@ -619,7 +634,7 @@ func (a *SortedArray) IteratorAsc(f func(k int, v interface{}) bool) {
}
}
// IteratorDesc iterates the array in descending order with given callback function <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 *SortedArray) IteratorDesc(f func(k int, v interface{}) bool) {
a.mu.RLock()
@ -654,19 +669,18 @@ func (a *SortedArray) String() string {
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (a *SortedArray) MarshalJSON() ([]byte, error) {
// Note that do not use pointer as its receiver here.
func (a SortedArray) MarshalJSON() ([]byte, error) {
a.mu.RLock()
defer a.mu.RUnlock()
return json.Marshal(a.array)
}
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
// Note that the comparator is set as string comparator in default.
func (a *SortedArray) UnmarshalJSON(b []byte) error {
if a.mu == nil {
a.mu = rwmutex.New()
if a.comparator == nil {
a.array = make([]interface{}, 0)
a.unique = gtype.NewBool()
// Note that the comparator is string comparator in default.
a.comparator = gutil.ComparatorString
}
a.mu.Lock()
@ -683,11 +697,9 @@ func (a *SortedArray) UnmarshalJSON(b []byte) error {
}
// UnmarshalValue is an interface implement which sets any type of value for array.
// Note that the comparator is set as string comparator in default.
func (a *SortedArray) UnmarshalValue(value interface{}) (err error) {
if a.mu == nil {
a.mu = rwmutex.New()
a.unique = gtype.NewBool()
// Note that the comparator is string comparator in default.
if a.comparator == nil {
a.comparator = gutil.ComparatorString
}
a.mu.Lock()
@ -749,7 +761,30 @@ func (a *SortedArray) FilterEmpty() *SortedArray {
return a
}
// Walk applies a user supplied function <f> to every item of array.
func (a *SortedArray) Walk(f func(value interface{}) interface{}) *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
// Keep the array always sorted.
defer sort.Slice(a.array, func(i, j int) bool {
return a.getComparator()(a.array[i], a.array[j]) < 0
})
for i, v := range a.array {
a.array[i] = f(v)
}
return a
}
// IsEmpty checks whether the array is empty.
func (a *SortedArray) IsEmpty() bool {
return a.Len() == 0
}
// getComparator returns the comparator if it's previously set,
// or else it panics.
func (a *SortedArray) getComparator() func(a, b interface{}) int {
if a.comparator == nil {
panic("comparator is missing for sorted array")
}
return a.comparator
}

View File

@ -13,17 +13,20 @@ import (
"math"
"sort"
"github.com/gogf/gf/container/gtype"
"github.com/gogf/gf/internal/rwmutex"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/grand"
)
// It's using increasing order in default.
// SortedIntArray is a golang sorted int array with rich features.
// It is using increasing order in default, which can be changed by
// setting it a custom comparator.
// It contains a concurrent-safe/unsafe switch, which should be set
// when its initialization and cannot be changed then.
type SortedIntArray struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
array []int
unique *gtype.Bool // Whether enable unique feature(false)
unique bool // Whether enable unique feature(false)
comparator func(a, b int) int // Comparison function(it returns -1: a < b; 0: a == b; 1: a > b)
}
@ -47,9 +50,8 @@ func NewSortedIntArrayComparator(comparator func(a, b int) int, safe ...bool) *S
// which is false in default.
func NewSortedIntArraySize(cap int, safe ...bool) *SortedIntArray {
return &SortedIntArray{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: make([]int, 0, cap),
unique: gtype.NewBool(),
comparator: defaultComparatorInt,
}
}
@ -93,7 +95,7 @@ func (a *SortedIntArray) SetArray(array []int) *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
sort.Ints(a.array)
quickSortInt(a.array, a.getComparator())
return a
}
@ -103,12 +105,18 @@ func (a *SortedIntArray) SetArray(array []int) *SortedIntArray {
func (a *SortedIntArray) Sort() *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
sort.Ints(a.array)
quickSortInt(a.array, a.getComparator())
return a
}
// Add adds one or multiple values to sorted array, the array always keeps sorted.
// It's alias of function Append, see Append.
func (a *SortedIntArray) Add(values ...int) *SortedIntArray {
return a.Append(values...)
}
// Append adds one or multiple values to sorted array, the array always keeps sorted.
func (a *SortedIntArray) Append(values ...int) *SortedIntArray {
if len(values) == 0 {
return a
}
@ -116,7 +124,7 @@ func (a *SortedIntArray) Add(values ...int) *SortedIntArray {
defer a.mu.Unlock()
for _, value := range values {
index, cmp := a.binSearch(value, false)
if a.unique.Val() && cmp == 0 {
if a.unique && cmp == 0 {
continue
}
if index < 0 {
@ -133,135 +141,145 @@ func (a *SortedIntArray) Add(values ...int) *SortedIntArray {
return a
}
// Get returns the value of the specified index,
// the caller should notice the boundary of the array.
func (a *SortedIntArray) Get(index int) int {
// 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 *SortedIntArray) Get(index int) (value int, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
if index < 0 || index >= len(a.array) {
return 0, false
}
return a.array[index], true
}
// Remove removes an item by index.
// Note that if the index is out of range of array, it returns 0.
func (a *SortedIntArray) Remove(index int) int {
// If the given <index> is out of range of the array, the <found> is false.
func (a *SortedIntArray) 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 *SortedIntArray) doRemoveWithoutLock(index int) (value int, found bool) {
if index < 0 || index >= len(a.array) {
return 0
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
return value, true
} else if index == len(a.array)-1 {
value := a.array[index]
a.array = a.array[:index]
return value
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]
value = a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
return value
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 *SortedIntArray) RemoveValue(value int) bool {
if i := a.Search(value); i != -1 {
a.Remove(i)
return true
_, found := a.Remove(i)
return found
}
return false
}
// PopLeft pops and returns an item from the beginning of array.
// Note that if the array is empty, it returns 0.
// Be very careful when use this function in loop statement.
// You can use IsEmpty() of Len() == 0 checks if this array empty.
func (a *SortedIntArray) PopLeft() int {
// Note that if the array is empty, the <found> is false.
func (a *SortedIntArray) PopLeft() (value int, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.array) == 0 {
return 0
return 0, false
}
value := a.array[0]
value = a.array[0]
a.array = a.array[1:]
return value
return value, true
}
// PopRight pops and returns an item from the end of array.
// Note that if the array is empty, it returns 0.
// Be very careful when use this function in loop statement.
// You can use IsEmpty() of Len() == 0 checks if this array empty.
func (a *SortedIntArray) PopRight() int {
// Note that if the array is empty, the <found> is false.
func (a *SortedIntArray) PopRight() (value int, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
if index <= 0 {
return 0
if index < 0 {
return 0, false
}
value := a.array[index]
value = a.array[index]
a.array = a.array[:index]
return value
return value, true
}
// PopRand randomly pops and return an item out of array.
// Note that if the array is empty, it returns 0.
// Be very careful when use this function in loop statement.
// You can use IsEmpty() of Len() == 0 checks if this array empty.
func (a *SortedIntArray) PopRand() int {
return a.Remove(grand.Intn(len(a.array)))
// Note that if the array is empty, the <found> is false.
func (a *SortedIntArray) PopRand() (value int, found bool) {
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.
// 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 *SortedIntArray) PopRands(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
if size == 0 {
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++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
array[i], _ = a.doRemoveWithoutLock(grand.Intn(len(a.array)))
}
return array
}
// 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 *SortedIntArray) PopLefts(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
if size > length {
size = length
}
if size == 0 {
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.
// 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 *SortedIntArray) PopRights(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.array) == 0 {
if size <= 0 || len(a.array) == 0 {
return nil
}
index := len(a.array) - size
if index < 0 {
index = 0
if index <= 0 {
array := a.array
a.array = a.array[:0]
return array
}
value := a.array[index:]
a.array = a.array[:index]
@ -413,20 +431,20 @@ func (a *SortedIntArray) Search(value int) (index int) {
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
// If <result> greater than 0, it means the value at <index> is greater than <value>.
func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int) {
if len(a.array) == 0 {
return -1, -2
}
if lock {
a.mu.RLock()
defer a.mu.RUnlock()
}
if len(a.array) == 0 {
return -1, -2
}
min := 0
max := len(a.array) - 1
mid := 0
cmp := -2
for min <= max {
mid = int((min + max) / 2)
cmp = a.comparator(value, a.array[mid])
mid = (min + max) / 2
cmp = a.getComparator()(value, a.array[mid])
switch {
case cmp < 0:
max = mid - 1
@ -443,8 +461,8 @@ func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int)
// which means it does not contain any repeated items.
// It also do unique check, remove all repeated items.
func (a *SortedIntArray) SetUnique(unique bool) *SortedIntArray {
oldUnique := a.unique.Val()
a.unique.Set(unique)
oldUnique := a.unique
a.unique = unique
if unique && oldUnique != unique {
a.Unique()
}
@ -463,7 +481,7 @@ func (a *SortedIntArray) Unique() *SortedIntArray {
if i == len(a.array)-1 {
break
}
if a.comparator(a.array[i], a.array[i+1]) == 0 {
if a.getComparator()(a.array[i], a.array[i+1]) == 0 {
a.array = append(a.array[:i+1], a.array[i+1+1:]...)
} else {
i++
@ -512,23 +530,7 @@ func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray {
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *SortedIntArray) Merge(array interface{}) *SortedIntArray {
switch v := array.(type) {
case *Array:
a.Add(gconv.Ints(v.Slice())...)
case *IntArray:
a.Add(gconv.Ints(v.Slice())...)
case *StrArray:
a.Add(gconv.Ints(v.Slice())...)
case *SortedArray:
a.Add(gconv.Ints(v.Slice())...)
case *SortedIntArray:
a.Add(gconv.Ints(v.Slice())...)
case *SortedStrArray:
a.Add(gconv.Ints(v.Slice())...)
default:
a.Add(gconv.Ints(array)...)
}
return a
return a.Add(gconv.Ints(array)...)
}
// Chunk splits an array into multiple arrays,
@ -555,33 +557,36 @@ func (a *SortedIntArray) Chunk(size int) [][]int {
}
// Rand randomly returns one item from array(no deleting).
func (a *SortedIntArray) Rand() int {
func (a *SortedIntArray) Rand() (value int, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
if len(a.array) == 0 {
return 0, false
}
return a.array[grand.Intn(len(a.array))], true
}
// Rands randomly returns <size> items from array(no deleting).
func (a *SortedIntArray) Rands(size int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
size = len(a.array)
if size <= 0 || len(a.array) == 0 {
return nil
}
n := make([]int, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size-1 {
break
}
array := make([]int, size)
for i := 0; i < size; i++ {
array[i] = a.array[grand.Intn(len(a.array))]
}
return n
return array
}
// Join joins array elements with a string <glue>.
func (a *SortedIntArray) 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))
@ -608,7 +613,7 @@ func (a *SortedIntArray) Iterator(f func(k int, v int) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array in ascending order with given callback function <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 *SortedIntArray) IteratorAsc(f func(k int, v int) bool) {
a.mu.RLock()
@ -620,7 +625,7 @@ func (a *SortedIntArray) IteratorAsc(f func(k int, v int) bool) {
}
}
// IteratorDesc iterates the array in descending order with given callback function <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 *SortedIntArray) IteratorDesc(f func(k int, v int) bool) {
a.mu.RLock()
@ -638,7 +643,8 @@ func (a *SortedIntArray) String() string {
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (a *SortedIntArray) MarshalJSON() ([]byte, error) {
// Note that do not use pointer as its receiver here.
func (a SortedIntArray) MarshalJSON() ([]byte, error) {
a.mu.RLock()
defer a.mu.RUnlock()
return json.Marshal(a.array)
@ -646,10 +652,8 @@ func (a *SortedIntArray) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (a *SortedIntArray) UnmarshalJSON(b []byte) error {
if a.mu == nil {
a.mu = rwmutex.New()
if a.comparator == nil {
a.array = make([]int, 0)
a.unique = gtype.NewBool()
a.comparator = defaultComparatorInt
}
a.mu.Lock()
@ -665,10 +669,7 @@ func (a *SortedIntArray) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for array.
func (a *SortedIntArray) UnmarshalValue(value interface{}) (err error) {
if a.mu == nil {
a.mu = rwmutex.New()
a.unique = gtype.NewBool()
// Note that the comparator is string comparator in default.
if a.comparator == nil {
a.comparator = defaultComparatorInt
}
a.mu.Lock()
@ -706,7 +707,30 @@ func (a *SortedIntArray) FilterEmpty() *SortedIntArray {
return a
}
// Walk applies a user supplied function <f> to every item of array.
func (a *SortedIntArray) Walk(f func(value int) int) *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
// Keep the array always sorted.
defer quickSortInt(a.array, a.getComparator())
for i, v := range a.array {
a.array[i] = f(v)
}
return a
}
// IsEmpty checks whether the array is empty.
func (a *SortedIntArray) IsEmpty() bool {
return a.Len() == 0
}
// getComparator returns the comparator if it's previously set,
// or else it returns a default comparator.
func (a *SortedIntArray) getComparator() func(a, b int) int {
if a.comparator == nil {
return defaultComparatorInt
}
return a.comparator
}

View File

@ -12,18 +12,22 @@ import (
"github.com/gogf/gf/text/gstr"
"math"
"sort"
"strings"
"github.com/gogf/gf/container/gtype"
"github.com/gogf/gf/internal/rwmutex"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/grand"
)
// It's using increasing order in default.
// SortedStrArray is a golang sorted string array with rich features.
// It is using increasing order in default, which can be changed by
// setting it a custom comparator.
// It contains a concurrent-safe/unsafe switch, which should be set
// when its initialization and cannot be changed then.
type SortedStrArray struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
array []string
unique *gtype.Bool // Whether enable unique feature(false)
unique bool // Whether enable unique feature(false)
comparator func(a, b string) int // Comparison function(it returns -1: a < b; 0: a == b; 1: a > b)
}
@ -47,9 +51,8 @@ func NewSortedStrArrayComparator(comparator func(a, b string) int, safe ...bool)
// which is false in default.
func NewSortedStrArraySize(cap int, safe ...bool) *SortedStrArray {
return &SortedStrArray{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
array: make([]string, 0, cap),
unique: gtype.NewBool(),
comparator: defaultComparatorStr,
}
}
@ -60,7 +63,7 @@ func NewSortedStrArraySize(cap int, safe ...bool) *SortedStrArray {
func NewSortedStrArrayFrom(array []string, safe ...bool) *SortedStrArray {
a := NewSortedStrArraySize(0, safe...)
a.array = array
quickSortStr(a.array, a.comparator)
quickSortStr(a.array, a.getComparator())
return a
}
@ -78,7 +81,7 @@ func (a *SortedStrArray) SetArray(array []string) *SortedStrArray {
a.mu.Lock()
defer a.mu.Unlock()
a.array = array
quickSortStr(a.array, a.comparator)
quickSortStr(a.array, a.getComparator())
return a
}
@ -88,12 +91,18 @@ func (a *SortedStrArray) SetArray(array []string) *SortedStrArray {
func (a *SortedStrArray) Sort() *SortedStrArray {
a.mu.Lock()
defer a.mu.Unlock()
quickSortStr(a.array, a.comparator)
quickSortStr(a.array, a.getComparator())
return a
}
// Add adds one or multiple values to sorted array, the array always keeps sorted.
// It's alias of function Append, see Append.
func (a *SortedStrArray) Add(values ...string) *SortedStrArray {
return a.Append(values...)
}
// Append adds one or multiple values to sorted array, the array always keeps sorted.
func (a *SortedStrArray) Append(values ...string) *SortedStrArray {
if len(values) == 0 {
return a
}
@ -101,7 +110,7 @@ func (a *SortedStrArray) Add(values ...string) *SortedStrArray {
defer a.mu.Unlock()
for _, value := range values {
index, cmp := a.binSearch(value, false)
if a.unique.Val() && cmp == 0 {
if a.unique && cmp == 0 {
continue
}
if index < 0 {
@ -118,39 +127,46 @@ func (a *SortedStrArray) Add(values ...string) *SortedStrArray {
return a
}
// Get returns the value of the specified index,
// the caller should notice the boundary of the array.
func (a *SortedStrArray) Get(index int) string {
// 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 *SortedStrArray) Get(index int) (value string, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
value := a.array[index]
return value
if index < 0 || index >= len(a.array) {
return "", false
}
return a.array[index], true
}
// Remove removes an item by index.
// Note that if the index is out of range of array, it returns an empty string.
func (a *SortedStrArray) Remove(index int) string {
// If the given <index> is out of range of the array, the <found> is false.
func (a *SortedStrArray) 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 *SortedStrArray) doRemoveWithoutLock(index int) (value string, found bool) {
if index < 0 || index >= len(a.array) {
return ""
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
return value, true
} else if index == len(a.array)-1 {
value := a.array[index]
a.array = a.array[:index]
return value
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]
value = a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
return value
return value, true
}
// RemoveValue removes an item by value.
@ -164,89 +180,92 @@ func (a *SortedStrArray) RemoveValue(value string) bool {
}
// PopLeft pops and returns an item from the beginning of array.
// Note that if the array is empty, it returns an empty string.
// Be very careful when use this function in loop statement.
// You can use IsEmpty() of Len() == 0 checks if this array empty.
func (a *SortedStrArray) PopLeft() string {
// Note that if the array is empty, the <found> is false.
func (a *SortedStrArray) PopLeft() (value string, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.array) == 0 {
return ""
return "", false
}
value := a.array[0]
value = a.array[0]
a.array = a.array[1:]
return value
return value, true
}
// PopRight pops and returns an item from the end of array.
// Note that if the array is empty, it returns an empty string.
// Be very careful when use this function in loop statement.
// You can use IsEmpty() of Len() == 0 checks if this array empty.
func (a *SortedStrArray) PopRight() string {
// Note that if the array is empty, the <found> is false.
func (a *SortedStrArray) PopRight() (value string, found bool) {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
if index <= 0 {
return ""
if index < 0 {
return "", false
}
value := a.array[index]
value = a.array[index]
a.array = a.array[:index]
return value
return value, true
}
// PopRand randomly pops and return an item out of array.
// Note that if the array is empty, it returns an empty string.
// Be very careful when use this function in loop statement.
// You can use IsEmpty() of Len() == 0 checks if this array empty.
func (a *SortedStrArray) PopRand() string {
return a.Remove(grand.Intn(len(a.array)))
// Note that if the array is empty, the <found> is false.
func (a *SortedStrArray) PopRand() (value string, found bool) {
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.
// 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 *SortedStrArray) PopRands(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
if size == 0 {
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++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[:index], a.array[index+1:]...)
array[i], _ = a.doRemoveWithoutLock(grand.Intn(len(a.array)))
}
return array
}
// 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 *SortedStrArray) PopLefts(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
if size > length {
size = length
}
if size == 0 {
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.
// 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 *SortedStrArray) PopRights(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
if len(a.array) == 0 {
if size <= 0 || len(a.array) == 0 {
return nil
}
index := len(a.array) - size
if index < 0 {
index = 0
if index <= 0 {
array := a.array
a.array = a.array[:0]
return array
}
value := a.array[index:]
a.array = a.array[:index]
@ -383,6 +402,22 @@ func (a *SortedStrArray) Contains(value string) bool {
return a.Search(value) != -1
}
// 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 *SortedStrArray) ContainsI(value string) bool {
a.mu.RLock()
defer a.mu.RUnlock()
if len(a.array) == 0 {
return false
}
for _, v := range a.array {
if strings.EqualFold(v, value) {
return true
}
}
return false
}
// Search searches array by <value>, returns the index of <value>,
// or returns -1 if not exists.
func (a *SortedStrArray) Search(value string) (index int) {
@ -398,20 +433,20 @@ func (a *SortedStrArray) Search(value string) (index int) {
// If <result> lesser than 0, it means the value at <index> is lesser than <value>.
// If <result> greater than 0, it means the value at <index> is greater than <value>.
func (a *SortedStrArray) binSearch(value string, lock bool) (index int, result int) {
if len(a.array) == 0 {
return -1, -2
}
if lock {
a.mu.RLock()
defer a.mu.RUnlock()
}
if len(a.array) == 0 {
return -1, -2
}
min := 0
max := len(a.array) - 1
mid := 0
cmp := -2
for min <= max {
mid = int((min + max) / 2)
cmp = a.comparator(value, a.array[mid])
mid = (min + max) / 2
cmp = a.getComparator()(value, a.array[mid])
switch {
case cmp < 0:
max = mid - 1
@ -428,8 +463,8 @@ func (a *SortedStrArray) binSearch(value string, lock bool) (index int, result i
// which means it does not contain any repeated items.
// It also do unique check, remove all repeated items.
func (a *SortedStrArray) SetUnique(unique bool) *SortedStrArray {
oldUnique := a.unique.Val()
a.unique.Set(unique)
oldUnique := a.unique
a.unique = unique
if unique && oldUnique != unique {
a.Unique()
}
@ -448,7 +483,7 @@ func (a *SortedStrArray) Unique() *SortedStrArray {
if i == len(a.array)-1 {
break
}
if a.comparator(a.array[i], a.array[i+1]) == 0 {
if a.getComparator()(a.array[i], a.array[i+1]) == 0 {
a.array = append(a.array[:i+1], a.array[i+1+1:]...)
} else {
i++
@ -497,23 +532,7 @@ func (a *SortedStrArray) RLockFunc(f func(array []string)) *SortedStrArray {
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more parameter types.
func (a *SortedStrArray) Merge(array interface{}) *SortedStrArray {
switch v := array.(type) {
case *Array:
a.Add(gconv.Strings(v.Slice())...)
case *IntArray:
a.Add(gconv.Strings(v.Slice())...)
case *StrArray:
a.Add(gconv.Strings(v.Slice())...)
case *SortedArray:
a.Add(gconv.Strings(v.Slice())...)
case *SortedIntArray:
a.Add(gconv.Strings(v.Slice())...)
case *SortedStrArray:
a.Add(gconv.Strings(v.Slice())...)
default:
a.Add(gconv.Strings(array)...)
}
return a
return a.Add(gconv.Strings(array)...)
}
// Chunk splits an array into multiple arrays,
@ -540,33 +559,36 @@ func (a *SortedStrArray) Chunk(size int) [][]string {
}
// Rand randomly returns one item from array(no deleting).
func (a *SortedStrArray) Rand() string {
func (a *SortedStrArray) Rand() (value string, found bool) {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
if len(a.array) == 0 {
return "", false
}
return a.array[grand.Intn(len(a.array))], true
}
// Rands randomly returns <size> items from array(no deleting).
func (a *SortedStrArray) Rands(size int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
size = len(a.array)
if size <= 0 || len(a.array) == 0 {
return nil
}
n := make([]string, size)
for i, v := range grand.Perm(len(a.array)) {
n[i] = a.array[v]
if i == size-1 {
break
}
array := make([]string, size)
for i := 0; i < size; i++ {
array[i] = a.array[grand.Intn(len(a.array))]
}
return n
return array
}
// Join joins array elements with a string <glue>.
func (a *SortedStrArray) 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)
@ -593,7 +615,7 @@ func (a *SortedStrArray) Iterator(f func(k int, v string) bool) {
a.IteratorAsc(f)
}
// IteratorAsc iterates the array in ascending order with given callback function <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 *SortedStrArray) IteratorAsc(f func(k int, v string) bool) {
a.mu.RLock()
@ -605,7 +627,7 @@ func (a *SortedStrArray) IteratorAsc(f func(k int, v string) bool) {
}
}
// IteratorDesc iterates the array in descending order with given callback function <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 *SortedStrArray) IteratorDesc(f func(k int, v string) bool) {
a.mu.RLock()
@ -634,7 +656,8 @@ func (a *SortedStrArray) String() string {
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (a *SortedStrArray) MarshalJSON() ([]byte, error) {
// Note that do not use pointer as its receiver here.
func (a SortedStrArray) MarshalJSON() ([]byte, error) {
a.mu.RLock()
defer a.mu.RUnlock()
return json.Marshal(a.array)
@ -642,10 +665,8 @@ func (a *SortedStrArray) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (a *SortedStrArray) UnmarshalJSON(b []byte) error {
if a.mu == nil {
a.mu = rwmutex.New()
if a.comparator == nil {
a.array = make([]string, 0)
a.unique = gtype.NewBool()
a.comparator = defaultComparatorStr
}
a.mu.Lock()
@ -661,10 +682,7 @@ func (a *SortedStrArray) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for array.
func (a *SortedStrArray) UnmarshalValue(value interface{}) (err error) {
if a.mu == nil {
a.mu = rwmutex.New()
a.unique = gtype.NewBool()
// Note that the comparator is string comparator in default.
if a.comparator == nil {
a.comparator = defaultComparatorStr
}
a.mu.Lock()
@ -702,7 +720,30 @@ func (a *SortedStrArray) FilterEmpty() *SortedStrArray {
return a
}
// Walk applies a user supplied function <f> to every item of array.
func (a *SortedStrArray) Walk(f func(value string) string) *SortedStrArray {
a.mu.Lock()
defer a.mu.Unlock()
// Keep the array always sorted.
defer quickSortStr(a.array, a.getComparator())
for i, v := range a.array {
a.array[i] = f(v)
}
return a
}
// IsEmpty checks whether the array is empty.
func (a *SortedStrArray) IsEmpty() bool {
return a.Len() == 0
}
// getComparator returns the comparator if it's previously set,
// or else it returns a default comparator.
func (a *SortedStrArray) getComparator() func(a, b string) int {
if a.comparator == nil {
return defaultComparatorStr
}
return a.comparator
}

View File

@ -61,7 +61,7 @@ func Example_basic() {
// Output:
// 10
// [0 1 2 3 4 5 6 7 8 9]
// 6
// 6 true
// true
// false
// [0 1 2 3 4 5 6 7 8 9 10 11]
@ -72,8 +72,50 @@ func Example_basic() {
// []
}
func Example_iterate() {
array := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"})
// Iterator is alias of IteratorAsc, which iterates the array readonly in ascending order
// with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
array.Iterator(func(k int, v string) bool {
fmt.Println(k, v)
return true
})
// 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.
array.IteratorDesc(func(k int, v string) bool {
fmt.Println(k, v)
return true
})
// Output:
// 0 a
// 1 b
// 2 c
// 2 c
// 1 b
// 0 a
}
func Example_reverse() {
array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
// Reverse makes array with elements in reverse order.
fmt.Println(array.Reverse().Slice())
// Output:
// [9 8 7 6 5 4 3 2 1]
}
func Example_shuffle() {
array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
// Shuffle randomly shuffles the array.
fmt.Println(array.Shuffle().Slice())
}
func Example_rand() {
array := garray.NewFrom([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9})
array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
// Randomly retrieve and return 2 items from the array.
// It does not delete the items from array.
@ -84,6 +126,26 @@ func Example_rand() {
fmt.Println(array.PopRand())
}
func Example_join() {
array := garray.NewFrom(g.Slice{"a", "b", "c", "d"})
fmt.Println(array.Join(","))
// Output:
// a,b,c,d
}
func Example_chunk() {
array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
// 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.
fmt.Println(array.Chunk(2))
// Output:
// [[1 2] [3 4] [5 6] [7 8] [9]]
}
func Example_popItem() {
array := garray.NewFrom([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9})
@ -95,16 +157,44 @@ func Example_popItem() {
fmt.Println(array.PopRights(2))
// Output:
// 1
// 1 true
// [2 3]
// 9
// 9 true
// [7 8]
}
func Example_walk() {
var array garray.StrArray
tables := g.SliceStr{"user", "user_detail"}
prefix := "gf_"
array.Append(tables...)
// Add prefix for given table names.
array.Walk(func(value string) string {
return prefix + value
})
fmt.Println(array.Slice())
// Output:
// [gf_user gf_user_detail]
}
func Example_contains() {
var array garray.StrArray
array.Append("a")
fmt.Println(array.Contains("a"))
fmt.Println(array.Contains("A"))
fmt.Println(array.ContainsI("A"))
// Output:
// true
// false
// true
}
func Example_mergeArray() {
array1 := garray.NewFrom([]interface{}{1, 2})
array2 := garray.NewFrom([]interface{}{3, 4})
slice1 := []interface{}{5, 6}
array1 := garray.NewFrom(g.Slice{1, 2})
array2 := garray.NewFrom(g.Slice{3, 4})
slice1 := g.Slice{5, 6}
slice2 := []int{7, 8}
slice3 := []string{"9", "0"}
fmt.Println(array1.Slice())

View File

@ -9,6 +9,7 @@
package garray_test
import (
"github.com/gogf/gf/util/gutil"
"strings"
"testing"
@ -17,6 +18,55 @@ import (
"github.com/gogf/gf/util/gconv"
)
func Test_Array_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var array garray.Array
expect := []int{2, 3, 1}
array.Append(2, 3, 1)
t.Assert(array.Slice(), expect)
})
gtest.C(t, func(t *gtest.T) {
var array garray.IntArray
expect := []int{2, 3, 1}
array.Append(2, 3, 1)
t.Assert(array.Slice(), expect)
})
gtest.C(t, func(t *gtest.T) {
var array garray.StrArray
expect := []string{"b", "a"}
array.Append("b", "a")
t.Assert(array.Slice(), expect)
})
gtest.C(t, func(t *gtest.T) {
var array garray.SortedArray
array.SetComparator(gutil.ComparatorInt)
expect := []int{1, 2, 3}
array.Add(2, 3, 1)
t.Assert(array.Slice(), expect)
})
gtest.C(t, func(t *gtest.T) {
var array garray.SortedIntArray
expect := []int{1, 2, 3}
array.Add(2, 3, 1)
t.Assert(array.Slice(), expect)
})
gtest.C(t, func(t *gtest.T) {
var array garray.SortedStrArray
expect := []string{"a", "b", "c"}
array.Add("c", "a", "b")
t.Assert(array.Slice(), expect)
})
}
func Test_SortedIntArray_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var array garray.SortedIntArray
expect := []int{1, 2, 3}
array.Add(2, 3, 1)
t.Assert(array.Slice(), expect)
})
}
func Test_IntArray_Unique(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
expect := []int{1, 2, 3, 4, 5, 6}

View File

@ -28,17 +28,38 @@ func Test_Array_Basic(t *testing.T) {
t.Assert(array.Slice(), expect)
t.Assert(array.Interfaces(), expect)
array.Set(0, 100)
t.Assert(array.Get(0), 100)
t.Assert(array.Get(1), 1)
v, ok := array.Get(0)
t.Assert(v, 100)
t.Assert(ok, true)
v, ok = array.Get(1)
t.Assert(v, 1)
t.Assert(ok, true)
t.Assert(array.Search(100), 0)
t.Assert(array3.Search(100), -1)
t.Assert(array.Contains(100), true)
t.Assert(array.Remove(0), 100)
t.Assert(array.Remove(-1), nil)
t.Assert(array.Remove(100000), nil)
t.Assert(array2.Remove(3), 3)
t.Assert(array2.Remove(1), 1)
v, ok = array.Remove(0)
t.Assert(v, 100)
t.Assert(ok, true)
v, ok = array.Remove(-1)
t.Assert(v, nil)
t.Assert(ok, false)
v, ok = array.Remove(100000)
t.Assert(v, nil)
t.Assert(ok, false)
v, ok = array2.Remove(3)
t.Assert(v, 3)
t.Assert(ok, true)
v, ok = array2.Remove(1)
t.Assert(v, 1)
t.Assert(ok, true)
t.Assert(array.Contains(100), false)
array.Append(4)
@ -85,10 +106,23 @@ func TestArray_PushAndPop(t *testing.T) {
expect := []interface{}{0, 1, 2, 3}
array := garray.NewArrayFrom(expect)
t.Assert(array.Slice(), expect)
t.Assert(array.PopLeft(), 0)
t.Assert(array.PopRight(), 3)
t.AssertIN(array.PopRand(), []interface{}{1, 2})
t.AssertIN(array.PopRand(), []interface{}{1, 2})
v, ok := array.PopLeft()
t.Assert(v, 0)
t.Assert(ok, true)
v, ok = array.PopRight()
t.Assert(v, 3)
t.Assert(ok, true)
v, ok = array.PopRand()
t.AssertIN(v, []interface{}{1, 2})
t.Assert(ok, true)
v, ok = array.PopRand()
t.AssertIN(v, []interface{}{1, 2})
t.Assert(ok, true)
t.Assert(array.Len(), 0)
array.PushLeft(1).PushRight(2)
t.Assert(array.Slice(), []interface{}{1, 2})
@ -103,14 +137,81 @@ func TestArray_PopRands(t *testing.T) {
})
}
func TestArray_PopLeft(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewFrom(g.Slice{1, 2, 3})
v, ok := array.PopLeft()
t.Assert(v, 1)
t.Assert(ok, true)
t.Assert(array.Len(), 2)
v, ok = array.PopLeft()
t.Assert(v, 2)
t.Assert(ok, true)
t.Assert(array.Len(), 1)
v, ok = array.PopLeft()
t.Assert(v, 3)
t.Assert(ok, true)
t.Assert(array.Len(), 0)
})
}
func TestArray_PopRight(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewFrom(g.Slice{1, 2, 3})
v, ok := array.PopRight()
t.Assert(v, 3)
t.Assert(ok, true)
t.Assert(array.Len(), 2)
v, ok = array.PopRight()
t.Assert(v, 2)
t.Assert(ok, true)
t.Assert(array.Len(), 1)
v, ok = array.PopRight()
t.Assert(v, 1)
t.Assert(ok, true)
t.Assert(array.Len(), 0)
})
}
func TestArray_PopLefts(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewFrom(g.Slice{1, 2, 3})
t.Assert(array.PopLefts(2), g.Slice{1, 2})
t.Assert(array.Len(), 1)
t.Assert(array.PopLefts(2), g.Slice{3})
t.Assert(array.Len(), 0)
})
}
func TestArray_PopRights(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewFrom(g.Slice{1, 2, 3})
t.Assert(array.PopRights(2), g.Slice{2, 3})
t.Assert(array.Len(), 1)
t.Assert(array.PopLefts(2), g.Slice{1})
t.Assert(array.Len(), 0)
})
}
func TestArray_PopLeftsAndPopRights(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.New()
t.Assert(array.PopLeft(), nil)
v, ok := array.PopLeft()
t.Assert(v, nil)
t.Assert(ok, false)
t.Assert(array.PopLefts(10), nil)
t.Assert(array.PopRight(), nil)
v, ok = array.PopRight()
t.Assert(v, nil)
t.Assert(ok, false)
t.Assert(array.PopRights(10), nil)
t.Assert(array.PopRand(), nil)
v, ok = array.PopRand()
t.Assert(v, nil)
t.Assert(ok, false)
t.Assert(array.PopRands(10), nil)
})
@ -185,9 +286,15 @@ func TestArray_Fill(t *testing.T) {
a2 := []interface{}{0}
array1 := garray.NewArrayFrom(a1)
array2 := garray.NewArrayFrom(a2, true)
t.Assert(array1.Fill(1, 2, 100).Slice(), []interface{}{0, 100, 100})
t.Assert(array2.Fill(0, 2, 100).Slice(), []interface{}{100, 100})
t.Assert(array2.Fill(-1, 2, 100).Slice(), []interface{}{100, 100})
t.Assert(array1.Fill(1, 2, 100), nil)
t.Assert(array1.Slice(), []interface{}{0, 100, 100})
t.Assert(array2.Fill(0, 2, 100), nil)
t.Assert(array2.Slice(), []interface{}{100, 100})
t.AssertNE(array2.Fill(-1, 2, 100), nil)
t.Assert(array2.Slice(), []interface{}{100, 100})
})
}
@ -263,14 +370,15 @@ func TestArray_Rand(t *testing.T) {
a1 := []interface{}{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewArrayFrom(a1)
t.Assert(len(array1.Rands(2)), 2)
t.Assert(len(array1.Rands(10)), 7)
t.Assert(len(array1.Rands(10)), 10)
t.AssertIN(array1.Rands(1)[0], a1)
})
gtest.C(t, func(t *gtest.T) {
s1 := []interface{}{"a", "b", "c", "d"}
a1 := garray.NewArrayFrom(s1)
i1 := a1.Rand()
i1, ok := a1.Rand()
t.Assert(ok, true)
t.Assert(a1.Contains(i1), true)
t.Assert(a1.Len(), 4)
})
@ -452,6 +560,7 @@ func TestArray_RLockFunc(t *testing.T) {
}
func TestArray_Json(t *testing.T) {
// pointer
gtest.C(t, func(t *gtest.T) {
s1 := []interface{}{"a", "b", "d", "c"}
a1 := garray.NewArrayFrom(s1)
@ -470,7 +579,26 @@ func TestArray_Json(t *testing.T) {
t.Assert(err, nil)
t.Assert(a3.Slice(), s1)
})
// value.
gtest.C(t, func(t *gtest.T) {
s1 := []interface{}{"a", "b", "d", "c"}
a1 := *garray.NewArrayFrom(s1)
b1, err1 := json.Marshal(a1)
b2, err2 := json.Marshal(s1)
t.Assert(b1, b2)
t.Assert(err1, err2)
a2 := garray.New()
err2 = json.Unmarshal(b2, &a2)
t.Assert(err2, nil)
t.Assert(a2.Slice(), s1)
var a3 garray.Array
err := json.Unmarshal(b2, &a3)
t.Assert(err, nil)
t.Assert(a3.Slice(), s1)
})
// pointer
gtest.C(t, func(t *gtest.T) {
type User struct {
Name string
@ -483,6 +611,25 @@ func TestArray_Json(t *testing.T) {
b, err := json.Marshal(data)
t.Assert(err, nil)
user := new(User)
err = json.Unmarshal(b, user)
t.Assert(err, nil)
t.Assert(user.Name, data["Name"])
t.Assert(user.Scores, data["Scores"])
})
// value
gtest.C(t, func(t *gtest.T) {
type User struct {
Name string
Scores garray.Array
}
data := g.Map{
"Name": "john",
"Scores": []int{99, 100, 98},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
user := new(User)
err = json.Unmarshal(b, user)
t.Assert(err, nil)
@ -601,3 +748,12 @@ func TestArray_FilterEmpty(t *testing.T) {
t.Assert(array.FilterEmpty(), g.Slice{1, 2, 3, 4})
})
}
func TestArray_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewArrayFrom(g.Slice{"1", "2"})
t.Assert(array.Walk(func(value interface{}) interface{} {
return "key-" + gconv.String(value)
}), g.Slice{"key-1", "key-2"})
})
}

View File

@ -29,14 +29,31 @@ func Test_IntArray_Basic(t *testing.T) {
t.Assert(array.Slice(), expect)
t.Assert(array.Interfaces(), expect)
array.Set(0, 100)
t.Assert(array.Get(0), 100)
t.Assert(array.Get(1), 1)
v, ok := array.Get(0)
t.Assert(v, 100)
t.Assert(ok, true)
v, ok = array.Get(1)
t.Assert(v, 1)
t.Assert(ok, true)
t.Assert(array.Search(100), 0)
t.Assert(array2.Search(100), -1)
t.Assert(array.Contains(100), true)
t.Assert(array.Remove(0), 100)
t.Assert(array.Remove(-1), 0)
t.Assert(array.Remove(100000), 0)
v, ok = array.Remove(0)
t.Assert(v, 100)
t.Assert(ok, true)
v, ok = array.Remove(-1)
t.Assert(v, 0)
t.Assert(ok, false)
v, ok = array.Remove(100000)
t.Assert(v, 0)
t.Assert(ok, false)
t.Assert(array.Contains(100), false)
array.Append(4)
t.Assert(array.Len(), 4)
@ -81,10 +98,27 @@ func TestIntArray_PushAndPop(t *testing.T) {
expect := []int{0, 1, 2, 3}
array := garray.NewIntArrayFrom(expect)
t.Assert(array.Slice(), expect)
t.Assert(array.PopLeft(), 0)
t.Assert(array.PopRight(), 3)
t.AssertIN(array.PopRand(), []int{1, 2})
t.AssertIN(array.PopRand(), []int{1, 2})
v, ok := array.PopLeft()
t.Assert(v, 0)
t.Assert(ok, true)
v, ok = array.PopRight()
t.Assert(v, 3)
t.Assert(ok, true)
v, ok = array.PopRand()
t.AssertIN(v, []int{1, 2})
t.Assert(ok, true)
v, ok = array.PopRand()
t.AssertIN(v, []int{1, 2})
t.Assert(ok, true)
v, ok = array.PopRand()
t.Assert(v, 0)
t.Assert(ok, false)
t.Assert(array.Len(), 0)
array.PushLeft(1).PushRight(2)
t.Assert(array.Slice(), []int{1, 2})
@ -94,11 +128,23 @@ func TestIntArray_PushAndPop(t *testing.T) {
func TestIntArray_PopLeftsAndPopRights(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewIntArray()
t.Assert(array.PopLeft(), 0)
v, ok := array.PopLeft()
t.Assert(v, 0)
t.Assert(ok, false)
t.Assert(array.PopLefts(10), nil)
t.Assert(array.PopRight(), 0)
v, ok = array.PopRight()
t.Assert(v, 0)
t.Assert(ok, false)
t.Assert(array.PopRights(10), nil)
t.Assert(array.PopRand(), 0)
v, ok = array.PopRand()
t.Assert(v, 0)
t.Assert(ok, false)
t.Assert(array.PopRands(10), nil)
})
@ -173,9 +219,73 @@ func TestIntArray_Fill(t *testing.T) {
a2 := []int{0}
array1 := garray.NewIntArrayFrom(a1)
array2 := garray.NewIntArrayFrom(a2)
t.Assert(array1.Fill(1, 2, 100).Slice(), []int{0, 100, 100})
t.Assert(array2.Fill(0, 2, 100).Slice(), []int{100, 100})
t.Assert(array2.Fill(-1, 2, 100).Slice(), []int{100, 100})
t.Assert(array1.Fill(1, 2, 100), nil)
t.Assert(array1.Slice(), []int{0, 100, 100})
t.Assert(array2.Fill(0, 2, 100), nil)
t.Assert(array2.Slice(), []int{100, 100})
t.AssertNE(array2.Fill(-1, 2, 100), nil)
t.Assert(array2.Slice(), []int{100, 100})
})
}
func TestIntArray_PopLeft(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewIntArrayFrom(g.SliceInt{1, 2, 3})
v, ok := array.PopLeft()
t.Assert(v, 1)
t.Assert(ok, true)
t.Assert(array.Len(), 2)
v, ok = array.PopLeft()
t.Assert(v, 2)
t.Assert(ok, true)
t.Assert(array.Len(), 1)
v, ok = array.PopLeft()
t.Assert(v, 3)
t.Assert(ok, true)
t.Assert(array.Len(), 0)
})
}
func TestIntArray_PopRight(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewIntArrayFrom(g.SliceInt{1, 2, 3})
v, ok := array.PopRight()
t.Assert(v, 3)
t.Assert(ok, true)
t.Assert(array.Len(), 2)
v, ok = array.PopRight()
t.Assert(v, 2)
t.Assert(ok, true)
t.Assert(array.Len(), 1)
v, ok = array.PopRight()
t.Assert(v, 1)
t.Assert(ok, true)
t.Assert(array.Len(), 0)
})
}
func TestIntArray_PopLefts(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewIntArrayFrom(g.SliceInt{1, 2, 3})
t.Assert(array.PopLefts(2), g.Slice{1, 2})
t.Assert(array.Len(), 1)
t.Assert(array.PopLefts(2), g.Slice{3})
t.Assert(array.Len(), 0)
})
}
func TestIntArray_PopRights(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewIntArrayFrom(g.SliceInt{1, 2, 3})
t.Assert(array.PopRights(2), g.Slice{2, 3})
t.Assert(array.Len(), 1)
t.Assert(array.PopLefts(2), g.Slice{1})
t.Assert(array.Len(), 0)
})
}
@ -259,9 +369,12 @@ func TestIntArray_Rand(t *testing.T) {
a1 := []int{0, 1, 2, 3, 4, 5, 6}
array1 := garray.NewIntArrayFrom(a1)
t.Assert(len(array1.Rands(2)), 2)
t.Assert(len(array1.Rands(10)), 7)
t.Assert(len(array1.Rands(10)), 10)
t.AssertIN(array1.Rands(1)[0], a1)
t.AssertIN(array1.Rand(), a1)
v, ok := array1.Rand()
t.AssertIN(v, a1)
t.Assert(ok, true)
})
}
@ -358,7 +471,9 @@ func TestArray_Get(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []int{1, 2, 3, 5}
array1 := garray.NewIntArrayFrom(a1)
t.Assert(array1.Get(2), 3)
v, ok := array1.Get(2)
t.Assert(v, 3)
t.Assert(ok, true)
t.Assert(array1.Len(), 4)
})
}
@ -395,16 +510,19 @@ func TestIntArray_Remove(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []int{1, 2, 3, 5, 4}
array1 := garray.NewIntArrayFrom(a1)
n1 := array1.Remove(1)
t.Assert(n1, 2)
v, ok := array1.Remove(1)
t.Assert(v, 2)
t.Assert(ok, true)
t.Assert(array1.Len(), 4)
n1 = array1.Remove(0)
t.Assert(n1, 1)
v, ok = array1.Remove(0)
t.Assert(v, 1)
t.Assert(ok, true)
t.Assert(array1.Len(), 3)
n1 = array1.Remove(2)
t.Assert(n1, 4)
v, ok = array1.Remove(2)
t.Assert(v, 4)
t.Assert(ok, true)
t.Assert(array1.Len(), 2)
})
}
@ -487,6 +605,7 @@ func TestIntArray_RLockFunc(t *testing.T) {
}
func TestIntArray_Json(t *testing.T) {
// array pointer
gtest.C(t, func(t *gtest.T) {
s1 := []int{1, 4, 3, 2}
a1 := garray.NewIntArrayFrom(s1)
@ -504,7 +623,25 @@ func TestIntArray_Json(t *testing.T) {
t.Assert(err, nil)
t.Assert(a3.Slice(), s1)
})
// array value
gtest.C(t, func(t *gtest.T) {
s1 := []int{1, 4, 3, 2}
a1 := *garray.NewIntArrayFrom(s1)
b1, err1 := json.Marshal(a1)
b2, err2 := json.Marshal(s1)
t.Assert(b1, b2)
t.Assert(err1, err2)
a2 := garray.NewIntArray()
err1 = json.Unmarshal(b2, &a2)
t.Assert(a2.Slice(), s1)
var a3 garray.IntArray
err := json.Unmarshal(b2, &a3)
t.Assert(err, nil)
t.Assert(a3.Slice(), s1)
})
// array pointer
gtest.C(t, func(t *gtest.T) {
type User struct {
Name string
@ -517,6 +654,25 @@ func TestIntArray_Json(t *testing.T) {
b, err := json.Marshal(data)
t.Assert(err, nil)
user := new(User)
err = json.Unmarshal(b, user)
t.Assert(err, nil)
t.Assert(user.Name, data["Name"])
t.Assert(user.Scores, data["Scores"])
})
// array value
gtest.C(t, func(t *gtest.T) {
type User struct {
Name string
Scores garray.IntArray
}
data := g.Map{
"Name": "john",
"Scores": []int{99, 100, 98},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
user := new(User)
err = json.Unmarshal(b, user)
t.Assert(err, nil)
@ -624,3 +780,12 @@ func TestIntArray_FilterEmpty(t *testing.T) {
t.Assert(array.FilterEmpty(), g.SliceInt{1, 2, 3, 4})
})
}
func TestIntArray_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewIntArrayFrom(g.SliceInt{1, 2})
t.Assert(array.Walk(func(value int) int {
return 10 + value
}), g.Slice{11, 12})
})
}

View File

@ -29,13 +29,26 @@ func Test_StrArray_Basic(t *testing.T) {
t.Assert(array.Slice(), expect)
t.Assert(array.Interfaces(), expect)
array.Set(0, "100")
t.Assert(array.Get(0), 100)
t.Assert(array.Get(1), 1)
v, ok := array.Get(0)
t.Assert(v, 100)
t.Assert(ok, true)
t.Assert(array.Search("100"), 0)
t.Assert(array.Contains("100"), true)
t.Assert(array.Remove(0), 100)
t.Assert(array.Remove(-1), "")
t.Assert(array.Remove(100000), "")
v, ok = array.Remove(0)
t.Assert(v, 100)
t.Assert(ok, true)
v, ok = array.Remove(-1)
t.Assert(v, "")
t.Assert(ok, false)
v, ok = array.Remove(100000)
t.Assert(v, "")
t.Assert(ok, false)
t.Assert(array.Contains("100"), false)
array.Append("4")
t.Assert(array.Len(), 4)
@ -51,6 +64,16 @@ func Test_StrArray_Basic(t *testing.T) {
})
}
func TestStrArray_ContainsI(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := garray.NewStrArray()
s.Append("a", "b", "C")
t.Assert(s.Contains("A"), false)
t.Assert(s.Contains("a"), true)
t.Assert(s.ContainsI("A"), true)
})
}
func TestStrArray_Sort(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
expect1 := []string{"0", "1", "2", "3"}
@ -79,24 +102,108 @@ func TestStrArray_PushAndPop(t *testing.T) {
expect := []string{"0", "1", "2", "3"}
array := garray.NewStrArrayFrom(expect)
t.Assert(array.Slice(), expect)
t.Assert(array.PopLeft(), "0")
t.Assert(array.PopRight(), "3")
t.AssertIN(array.PopRand(), []string{"1", "2"})
t.AssertIN(array.PopRand(), []string{"1", "2"})
v, ok := array.PopLeft()
t.Assert(v, "0")
t.Assert(ok, true)
v, ok = array.PopRight()
t.Assert(v, "3")
t.Assert(ok, true)
v, ok = array.PopRand()
t.AssertIN(v, []string{"1", "2"})
t.Assert(ok, true)
v, ok = array.PopRand()
t.AssertIN(v, []string{"1", "2"})
t.Assert(ok, true)
v, ok = array.PopRand()
t.Assert(v, "")
t.Assert(ok, false)
t.Assert(array.Len(), 0)
array.PushLeft("1").PushRight("2")
t.Assert(array.Slice(), []string{"1", "2"})
})
}
func TestStrArray_PopLeft(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewStrArrayFrom(g.SliceStr{"1", "2", "3"})
v, ok := array.PopLeft()
t.Assert(v, 1)
t.Assert(ok, true)
t.Assert(array.Len(), 2)
v, ok = array.PopLeft()
t.Assert(v, 2)
t.Assert(ok, true)
t.Assert(array.Len(), 1)
v, ok = array.PopLeft()
t.Assert(v, 3)
t.Assert(ok, true)
t.Assert(array.Len(), 0)
})
}
func TestStrArray_PopRight(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewStrArrayFrom(g.SliceStr{"1", "2", "3"})
v, ok := array.PopRight()
t.Assert(v, 3)
t.Assert(ok, true)
t.Assert(array.Len(), 2)
v, ok = array.PopRight()
t.Assert(v, 2)
t.Assert(ok, true)
t.Assert(array.Len(), 1)
v, ok = array.PopRight()
t.Assert(v, 1)
t.Assert(ok, true)
t.Assert(array.Len(), 0)
})
}
func TestStrArray_PopLefts(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewStrArrayFrom(g.SliceStr{"1", "2", "3"})
t.Assert(array.PopLefts(2), g.Slice{"1", "2"})
t.Assert(array.Len(), 1)
t.Assert(array.PopLefts(2), g.Slice{"3"})
t.Assert(array.Len(), 0)
})
}
func TestStrArray_PopRights(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewStrArrayFrom(g.SliceStr{"1", "2", "3"})
t.Assert(array.PopRights(2), g.Slice{"2", "3"})
t.Assert(array.Len(), 1)
t.Assert(array.PopLefts(2), g.Slice{"1"})
t.Assert(array.Len(), 0)
})
}
func TestStrArray_PopLeftsAndPopRights(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewStrArray()
t.Assert(array.PopLeft(), nil)
v, ok := array.PopLeft()
t.Assert(v, "")
t.Assert(ok, false)
t.Assert(array.PopLefts(10), nil)
t.Assert(array.PopRight(), nil)
v, ok = array.PopRight()
t.Assert(v, "")
t.Assert(ok, false)
t.Assert(array.PopRights(10), nil)
t.Assert(array.PopRand(), nil)
v, ok = array.PopRand()
t.Assert(v, "")
t.Assert(ok, false)
t.Assert(array.PopRands(10), nil)
})
@ -171,10 +278,12 @@ func TestStrArray_Fill(t *testing.T) {
a2 := []string{"0"}
array1 := garray.NewStrArrayFrom(a1)
array2 := garray.NewStrArrayFrom(a2)
t.Assert(array1.Fill(1, 2, "100").Slice(), []string{"0", "100", "100"})
t.Assert(array2.Fill(0, 2, "100").Slice(), []string{"100", "100"})
s1 := array2.Fill(-1, 2, "100")
t.Assert(s1.Len(), 2)
t.Assert(array1.Fill(1, 2, "100"), nil)
t.Assert(array1.Slice(), []string{"0", "100", "100"})
t.Assert(array2.Fill(0, 2, "100"), nil)
t.Assert(array2.Slice(), []string{"100", "100"})
t.AssertNE(array2.Fill(-1, 2, "100"), nil)
t.Assert(array2.Len(), 2)
})
}
@ -250,10 +359,11 @@ func TestStrArray_Rand(t *testing.T) {
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
array1 := garray.NewStrArrayFrom(a1)
t.Assert(len(array1.Rands(2)), "2")
t.Assert(len(array1.Rands(10)), "7")
t.Assert(len(array1.Rands(10)), 10)
t.AssertIN(array1.Rands(1)[0], a1)
t.Assert(len(array1.Rand()), 1)
t.AssertIN(array1.Rand(), a1)
v, ok := array1.Rand()
t.Assert(ok, true)
t.AssertIN(v, a1)
})
}
@ -373,9 +483,10 @@ func TestStrArray_PopRand(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []string{"0", "1", "2", "3", "4", "5", "6"}
array1 := garray.NewStrArrayFrom(a1)
str1 := array1.PopRand()
str1, ok := array1.PopRand()
t.Assert(strings.Contains("0,1,2,3,4,5,6", str1), true)
t.Assert(array1.Len(), 6)
t.Assert(ok, true)
})
}
@ -405,11 +516,13 @@ func TestStrArray_Remove(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []string{"e", "a", "d", "a", "c"}
array1 := garray.NewStrArrayFrom(a1)
s1 := array1.Remove(1)
s1, ok := array1.Remove(1)
t.Assert(s1, "a")
t.Assert(ok, true)
t.Assert(array1.Len(), 4)
s1 = array1.Remove(3)
s1, ok = array1.Remove(3)
t.Assert(s1, "c")
t.Assert(ok, true)
t.Assert(array1.Len(), 3)
})
}
@ -491,6 +604,7 @@ func TestStrArray_LockFunc(t *testing.T) {
}
func TestStrArray_Json(t *testing.T) {
// array pointer
gtest.C(t, func(t *gtest.T) {
s1 := []string{"a", "b", "d", "c"}
a1 := garray.NewStrArrayFrom(s1)
@ -508,7 +622,25 @@ func TestStrArray_Json(t *testing.T) {
t.Assert(err, nil)
t.Assert(a3.Slice(), s1)
})
// array value
gtest.C(t, func(t *gtest.T) {
s1 := []string{"a", "b", "d", "c"}
a1 := *garray.NewStrArrayFrom(s1)
b1, err1 := json.Marshal(a1)
b2, err2 := json.Marshal(s1)
t.Assert(b1, b2)
t.Assert(err1, err2)
a2 := garray.NewStrArray()
err1 = json.Unmarshal(b2, &a2)
t.Assert(a2.Slice(), s1)
var a3 garray.StrArray
err := json.Unmarshal(b2, &a3)
t.Assert(err, nil)
t.Assert(a3.Slice(), s1)
})
// array pointer
gtest.C(t, func(t *gtest.T) {
type User struct {
Name string
@ -521,6 +653,25 @@ func TestStrArray_Json(t *testing.T) {
b, err := json.Marshal(data)
t.Assert(err, nil)
user := new(User)
err = json.Unmarshal(b, user)
t.Assert(err, nil)
t.Assert(user.Name, data["Name"])
t.Assert(user.Scores, data["Scores"])
})
// array value
gtest.C(t, func(t *gtest.T) {
type User struct {
Name string
Scores garray.StrArray
}
data := g.Map{
"Name": "john",
"Scores": []string{"A+", "A", "A"},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
user := new(User)
err = json.Unmarshal(b, user)
t.Assert(err, nil)
@ -627,3 +778,12 @@ func TestStrArray_FilterEmpty(t *testing.T) {
t.Assert(array.FilterEmpty(), g.SliceStr{"1", "2"})
})
}
func TestStrArray_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewStrArrayFrom(g.SliceStr{"1", "2"})
t.Assert(array.Walk(func(value string) string {
return "key-" + value
}), g.Slice{"key-1", "key-2"})
})
}

View File

@ -99,8 +99,13 @@ func TestSortedArray_Get(t *testing.T) {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
t.Assert(array1.Get(2), "f")
t.Assert(array1.Get(1), "c")
v, ok := array1.Get(2)
t.Assert(v, "f")
t.Assert(ok, true)
v, ok = array1.Get(1)
t.Assert(v, "c")
t.Assert(ok, true)
})
}
@ -112,20 +117,28 @@ func TestSortedArray_Remove(t *testing.T) {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
i1 := array1.Remove(1)
i1, ok := array1.Remove(1)
t.Assert(ok, true)
t.Assert(gconv.String(i1), "b")
t.Assert(array1.Len(), 3)
t.Assert(array1.Contains("b"), false)
t.Assert(array1.Remove(-1), nil)
t.Assert(array1.Remove(100000), nil)
v, ok := array1.Remove(-1)
t.Assert(v, nil)
t.Assert(ok, false)
i2 := array1.Remove(0)
v, ok = array1.Remove(100000)
t.Assert(v, nil)
t.Assert(ok, false)
i2, ok := array1.Remove(0)
t.Assert(ok, true)
t.Assert(gconv.String(i2), "a")
t.Assert(array1.Len(), 2)
t.Assert(array1.Contains("a"), false)
i3 := array1.Remove(1)
i3, ok := array1.Remove(1)
t.Assert(ok, true)
t.Assert(gconv.String(i3), "d")
t.Assert(array1.Len(), 1)
t.Assert(array1.Contains("d"), false)
@ -135,32 +148,62 @@ func TestSortedArray_Remove(t *testing.T) {
func TestSortedArray_PopLeft(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []interface{}{"a", "d", "c", "b"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
i1 := array1.PopLeft()
array1 := garray.NewSortedArrayFrom(
[]interface{}{"a", "d", "c", "b"},
gutil.ComparatorString,
)
i1, ok := array1.PopLeft()
t.Assert(ok, true)
t.Assert(gconv.String(i1), "a")
t.Assert(array1.Len(), 3)
t.Assert(array1, []interface{}{"b", "c", "d"})
})
gtest.C(t, func(t *gtest.T) {
array := garray.NewSortedArrayFrom(g.Slice{1, 2, 3}, gutil.ComparatorInt)
v, ok := array.PopLeft()
t.Assert(v, 1)
t.Assert(ok, true)
t.Assert(array.Len(), 2)
v, ok = array.PopLeft()
t.Assert(v, 2)
t.Assert(ok, true)
t.Assert(array.Len(), 1)
v, ok = array.PopLeft()
t.Assert(v, 3)
t.Assert(ok, true)
t.Assert(array.Len(), 0)
})
}
func TestSortedArray_PopRight(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []interface{}{"a", "d", "c", "b"}
func1 := func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
i1 := array1.PopRight()
array1 := garray.NewSortedArrayFrom(
[]interface{}{"a", "d", "c", "b"},
gutil.ComparatorString,
)
i1, ok := array1.PopRight()
t.Assert(ok, true)
t.Assert(gconv.String(i1), "d")
t.Assert(array1.Len(), 3)
t.Assert(array1, []interface{}{"a", "b", "c"})
})
gtest.C(t, func(t *gtest.T) {
array := garray.NewSortedArrayFrom(g.Slice{1, 2, 3}, gutil.ComparatorInt)
v, ok := array.PopRight()
t.Assert(v, 3)
t.Assert(ok, true)
t.Assert(array.Len(), 2)
v, ok = array.PopRight()
t.Assert(v, 2)
t.Assert(ok, true)
t.Assert(array.Len(), 1)
v, ok = array.PopRight()
t.Assert(v, 1)
t.Assert(ok, true)
t.Assert(array.Len(), 0)
})
}
func TestSortedArray_PopRand(t *testing.T) {
@ -170,7 +213,8 @@ func TestSortedArray_PopRand(t *testing.T) {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
i1 := array1.PopRand()
i1, ok := array1.PopRand()
t.Assert(ok, true)
t.AssertIN(i1, []interface{}{"a", "d", "c", "b"})
t.Assert(array1.Len(), 3)
@ -200,11 +244,19 @@ func TestSortedArray_PopRands(t *testing.T) {
func TestSortedArray_Empty(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewSortedArray(gutil.ComparatorInt)
t.Assert(array.PopLeft(), nil)
v, ok := array.PopLeft()
t.Assert(v, nil)
t.Assert(ok, false)
t.Assert(array.PopLefts(10), nil)
t.Assert(array.PopRight(), nil)
v, ok = array.PopRight()
t.Assert(v, nil)
t.Assert(ok, false)
t.Assert(array.PopRights(10), nil)
t.Assert(array.PopRand(), nil)
v, ok = array.PopRand()
t.Assert(v, nil)
t.Assert(ok, false)
t.Assert(array.PopRands(10), nil)
})
}
@ -225,7 +277,6 @@ func TestSortedArray_PopLefts(t *testing.T) {
t.Assert(len(i2), 4)
t.AssertIN(i1, []interface{}{"a", "d", "c", "b", "e", "f"})
t.Assert(array1.Len(), 0)
})
}
@ -243,7 +294,7 @@ func TestSortedArray_PopRights(t *testing.T) {
i2 := array1.PopRights(10)
t.Assert(len(i2), 4)
t.Assert(array1.Len(), 0)
})
}
@ -406,7 +457,8 @@ func TestSortedArray_Rand(t *testing.T) {
return strings.Compare(gconv.String(v1), gconv.String(v2))
}
array1 := garray.NewSortedArrayFrom(a1, func1)
i1 := array1.Rand()
i1, ok := array1.Rand()
t.Assert(ok, true)
t.AssertIN(i1, []interface{}{"a", "d", "c"})
t.Assert(array1.Len(), 3)
})
@ -426,7 +478,7 @@ func TestSortedArray_Rands(t *testing.T) {
t.Assert(array1.Len(), 3)
i1 = array1.Rands(4)
t.Assert(len(i1), 3)
t.Assert(len(i1), 4)
})
}
@ -587,6 +639,7 @@ func TestSortedArray_Merge(t *testing.T) {
}
func TestSortedArray_Json(t *testing.T) {
// array pointer
gtest.C(t, func(t *gtest.T) {
s1 := []interface{}{"a", "b", "d", "c"}
s2 := []interface{}{"a", "b", "c", "d"}
@ -606,7 +659,27 @@ func TestSortedArray_Json(t *testing.T) {
t.Assert(a3.Slice(), s1)
t.Assert(a3.Interfaces(), s1)
})
// array value
gtest.C(t, func(t *gtest.T) {
s1 := []interface{}{"a", "b", "d", "c"}
s2 := []interface{}{"a", "b", "c", "d"}
a1 := *garray.NewSortedArrayFrom(s1, gutil.ComparatorString)
b1, err1 := json.Marshal(a1)
b2, err2 := json.Marshal(s1)
t.Assert(b1, b2)
t.Assert(err1, err2)
a2 := garray.NewSortedArray(gutil.ComparatorString)
err1 = json.Unmarshal(b2, &a2)
t.Assert(a2.Slice(), s2)
var a3 garray.SortedArray
err := json.Unmarshal(b2, &a3)
t.Assert(err, nil)
t.Assert(a3.Slice(), s1)
t.Assert(a3.Interfaces(), s1)
})
// array pointer
gtest.C(t, func(t *gtest.T) {
type User struct {
Name string
@ -625,9 +698,58 @@ func TestSortedArray_Json(t *testing.T) {
t.Assert(user.Name, data["Name"])
t.AssertNE(user.Scores, nil)
t.Assert(user.Scores.Len(), 3)
t.AssertIN(user.Scores.PopLeft(), data["Scores"])
t.AssertIN(user.Scores.PopLeft(), data["Scores"])
t.AssertIN(user.Scores.PopLeft(), data["Scores"])
v, ok := user.Scores.PopLeft()
t.AssertIN(v, data["Scores"])
t.Assert(ok, true)
v, ok = user.Scores.PopLeft()
t.AssertIN(v, data["Scores"])
t.Assert(ok, true)
v, ok = user.Scores.PopLeft()
t.AssertIN(v, data["Scores"])
t.Assert(ok, true)
v, ok = user.Scores.PopLeft()
t.Assert(v, nil)
t.Assert(ok, false)
})
// array value
gtest.C(t, func(t *gtest.T) {
type User struct {
Name string
Scores garray.SortedArray
}
data := g.Map{
"Name": "john",
"Scores": []int{99, 100, 98},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
user := new(User)
err = json.Unmarshal(b, user)
t.Assert(err, nil)
t.Assert(user.Name, data["Name"])
t.AssertNE(user.Scores, nil)
t.Assert(user.Scores.Len(), 3)
v, ok := user.Scores.PopLeft()
t.AssertIN(v, data["Scores"])
t.Assert(ok, true)
v, ok = user.Scores.PopLeft()
t.AssertIN(v, data["Scores"])
t.Assert(ok, true)
v, ok = user.Scores.PopLeft()
t.AssertIN(v, data["Scores"])
t.Assert(ok, true)
v, ok = user.Scores.PopLeft()
t.Assert(v, nil)
t.Assert(ok, false)
})
}
@ -741,3 +863,12 @@ func TestSortedArray_FilterEmpty(t *testing.T) {
t.Assert(array.FilterEmpty(), g.Slice{1, 2, 3, 4})
})
}
func TestSortedArray_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewSortedArrayFrom(g.Slice{"1", "2"}, gutil.ComparatorString)
t.Assert(array.Walk(func(value interface{}) interface{} {
return "key-" + gconv.String(value)
}), g.Slice{"key-1", "key-2"})
})
}

View File

@ -68,9 +68,17 @@ func TestSortedIntArray_Get(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []int{1, 3, 5, 0}
array1 := garray.NewSortedIntArrayFrom(a1)
t.Assert(array1.Get(0), 0)
t.Assert(array1.Get(1), 1)
t.Assert(array1.Get(3), 5)
v, ok := array1.Get(0)
t.Assert(v, 0)
t.Assert(ok, true)
v, ok = array1.Get(1)
t.Assert(v, 1)
t.Assert(ok, true)
v, ok = array1.Get(3)
t.Assert(v, 5)
t.Assert(ok, true)
})
}
@ -79,26 +87,39 @@ func TestSortedIntArray_Remove(t *testing.T) {
a1 := []int{1, 3, 5, 0}
array1 := garray.NewSortedIntArrayFrom(a1)
t.Assert(array1.Remove(-1), 0)
t.Assert(array1.Remove(100000), 0)
v, ok := array1.Remove(-1)
t.Assert(v, 0)
t.Assert(ok, false)
v, ok = array1.Remove(-100000)
t.Assert(v, 0)
t.Assert(ok, false)
v, ok = array1.Remove(2)
t.Assert(v, 3)
t.Assert(ok, true)
i1 := array1.Remove(2)
t.Assert(i1, 3)
t.Assert(array1.Search(5), 2)
// 再次删除剩下的数组中的第一个
i2 := array1.Remove(0)
t.Assert(i2, 0)
v, ok = array1.Remove(0)
t.Assert(v, 0)
t.Assert(ok, true)
t.Assert(array1.Search(5), 1)
a2 := []int{1, 3, 4}
array2 := garray.NewSortedIntArrayFrom(a2)
i3 := array2.Remove(1)
v, ok = array2.Remove(1)
t.Assert(v, 3)
t.Assert(ok, true)
t.Assert(array2.Search(1), 0)
t.Assert(i3, 3)
i3 = array2.Remove(1)
v, ok = array2.Remove(1)
t.Assert(v, 4)
t.Assert(ok, true)
t.Assert(array2.Search(4), -1)
t.Assert(i3, 4)
})
}
@ -106,29 +127,64 @@ func TestSortedIntArray_PopLeft(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []int{1, 3, 5, 2}
array1 := garray.NewSortedIntArrayFrom(a1)
i1 := array1.PopLeft()
t.Assert(i1, 1)
v, ok := array1.PopLeft()
t.Assert(v, 1)
t.Assert(ok, true)
t.Assert(array1.Len(), 3)
t.Assert(array1.Search(1), -1)
})
gtest.C(t, func(t *gtest.T) {
array := garray.NewSortedIntArrayFrom(g.SliceInt{1, 2, 3})
v, ok := array.PopLeft()
t.Assert(v, 1)
t.Assert(ok, true)
t.Assert(array.Len(), 2)
v, ok = array.PopLeft()
t.Assert(v, 2)
t.Assert(ok, true)
t.Assert(array.Len(), 1)
v, ok = array.PopLeft()
t.Assert(v, 3)
t.Assert(ok, true)
t.Assert(array.Len(), 0)
})
}
func TestSortedIntArray_PopRight(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []int{1, 3, 5, 2}
array1 := garray.NewSortedIntArrayFrom(a1)
i1 := array1.PopRight()
t.Assert(i1, 5)
v, ok := array1.PopRight()
t.Assert(v, 5)
t.Assert(ok, true)
t.Assert(array1.Len(), 3)
t.Assert(array1.Search(5), -1)
})
gtest.C(t, func(t *gtest.T) {
array := garray.NewSortedIntArrayFrom(g.SliceInt{1, 2, 3})
v, ok := array.PopRight()
t.Assert(v, 3)
t.Assert(ok, true)
t.Assert(array.Len(), 2)
v, ok = array.PopRight()
t.Assert(v, 2)
t.Assert(ok, true)
t.Assert(array.Len(), 1)
v, ok = array.PopRight()
t.Assert(v, 1)
t.Assert(ok, true)
t.Assert(array.Len(), 0)
})
}
func TestSortedIntArray_PopRand(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []int{1, 3, 5, 2}
array1 := garray.NewSortedIntArrayFrom(a1)
i1 := array1.PopRand()
i1, ok := array1.PopRand()
t.Assert(ok, true)
t.Assert(array1.Len(), 3)
t.Assert(array1.Search(i1), -1)
t.AssertIN(i1, []int{1, 3, 5, 2})
@ -155,11 +211,19 @@ func TestSortedIntArray_PopRands(t *testing.T) {
func TestSortedIntArray_Empty(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewSortedIntArray()
t.Assert(array.PopLeft(), 0)
v, ok := array.PopLeft()
t.Assert(v, 0)
t.Assert(ok, false)
t.Assert(array.PopLefts(10), nil)
t.Assert(array.PopRight(), 0)
v, ok = array.PopRight()
t.Assert(v, 0)
t.Assert(ok, false)
t.Assert(array.PopRights(10), nil)
t.Assert(array.PopRand(), 0)
v, ok = array.PopRand()
t.Assert(v, 0)
t.Assert(ok, false)
t.Assert(array.PopRands(10), nil)
})
}
@ -340,8 +404,9 @@ func TestSortedIntArray_Rand(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []int{1, 2, 3, 4, 5}
array1 := garray.NewSortedIntArrayFrom(a1)
ns1 := array1.Rand() //按每几个元素切成一个数组
ns1, ok := array1.Rand()
t.AssertIN(ns1, a1)
t.Assert(ok, true)
})
}
@ -349,13 +414,12 @@ func TestSortedIntArray_Rands(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []int{1, 2, 3, 4, 5}
array1 := garray.NewSortedIntArrayFrom(a1)
ns1 := array1.Rands(2) //按每几个元素切成一个数组
ns1 := array1.Rands(2)
t.AssertIN(ns1, a1)
t.Assert(len(ns1), 2)
ns2 := array1.Rands(6) //按每几个元素切成一个数组
t.AssertIN(ns2, a1)
t.Assert(len(ns2), 5)
ns2 := array1.Rands(6)
t.Assert(len(ns2), 6)
})
}
@ -472,6 +536,7 @@ func TestSortedIntArray_Merge(t *testing.T) {
}
func TestSortedIntArray_Json(t *testing.T) {
// array pointer
gtest.C(t, func(t *gtest.T) {
s1 := []int{1, 4, 3, 2}
s2 := []int{1, 2, 3, 4}
@ -490,7 +555,26 @@ func TestSortedIntArray_Json(t *testing.T) {
t.Assert(err, nil)
t.Assert(a3.Slice(), s1)
})
// array value
gtest.C(t, func(t *gtest.T) {
s1 := []int{1, 4, 3, 2}
s2 := []int{1, 2, 3, 4}
a1 := *garray.NewSortedIntArrayFrom(s1)
b1, err1 := json.Marshal(a1)
b2, err2 := json.Marshal(s1)
t.Assert(b1, b2)
t.Assert(err1, err2)
a2 := garray.NewSortedIntArray()
err1 = json.Unmarshal(b2, &a2)
t.Assert(a2.Slice(), s2)
var a3 garray.SortedIntArray
err := json.Unmarshal(b2, &a3)
t.Assert(err, nil)
t.Assert(a3.Slice(), s1)
})
// array pointer
gtest.C(t, func(t *gtest.T) {
type User struct {
Name string
@ -503,6 +587,25 @@ func TestSortedIntArray_Json(t *testing.T) {
b, err := json.Marshal(data)
t.Assert(err, nil)
user := new(User)
err = json.Unmarshal(b, user)
t.Assert(err, nil)
t.Assert(user.Name, data["Name"])
t.Assert(user.Scores, []int{98, 99, 100})
})
// array value
gtest.C(t, func(t *gtest.T) {
type User struct {
Name string
Scores garray.SortedIntArray
}
data := g.Map{
"Name": "john",
"Scores": []int{99, 100, 98},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
user := new(User)
err = json.Unmarshal(b, user)
t.Assert(err, nil)
@ -610,3 +713,12 @@ func TestSortedIntArray_FilterEmpty(t *testing.T) {
t.Assert(array.FilterEmpty(), g.SliceInt{1, 2, 3, 4})
})
}
func TestSortedIntArray_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewSortedIntArrayFrom(g.SliceInt{1, 2})
t.Assert(array.Walk(func(value int) int {
return 10 + value
}), g.Slice{11, 12})
})
}

View File

@ -51,6 +51,16 @@ func TestSortedStrArray_SetArray(t *testing.T) {
})
}
func TestSortedStrArray_ContainsI(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := garray.NewSortedStrArray()
s.Append("a", "b", "C")
t.Assert(s.Contains("A"), false)
t.Assert(s.Contains("a"), true)
t.Assert(s.ContainsI("A"), true)
})
}
func TestSortedStrArray_Sort(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []string{"a", "d", "c", "b"}
@ -68,8 +78,13 @@ func TestSortedStrArray_Get(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []string{"a", "d", "c", "b"}
array1 := garray.NewSortedStrArrayFrom(a1)
t.Assert(array1.Get(2), "c")
t.Assert(array1.Get(0), "a")
v, ok := array1.Get(2)
t.Assert(v, "c")
t.Assert(ok, true)
v, ok = array1.Get(0)
t.Assert(v, "a")
t.Assert(ok, true)
})
}
@ -78,20 +93,36 @@ func TestSortedStrArray_Remove(t *testing.T) {
a1 := []string{"a", "d", "c", "b"}
array1 := garray.NewSortedStrArrayFrom(a1)
t.Assert(array1.Remove(-1), "")
t.Assert(array1.Remove(100000), "")
v, ok := array1.Remove(-1)
t.Assert(v, "")
t.Assert(ok, false)
v, ok = array1.Remove(100000)
t.Assert(v, "")
t.Assert(ok, false)
v, ok = array1.Remove(2)
t.Assert(v, "c")
t.Assert(ok, true)
v, ok = array1.Get(2)
t.Assert(v, "d")
t.Assert(ok, true)
t.Assert(array1.Remove(2), "c")
t.Assert(array1.Get(2), "d")
t.Assert(array1.Len(), 3)
t.Assert(array1.Contains("c"), false)
t.Assert(array1.Remove(0), "a")
v, ok = array1.Remove(0)
t.Assert(v, "a")
t.Assert(ok, true)
t.Assert(array1.Len(), 2)
t.Assert(array1.Contains("a"), false)
// 此时array1里的元素只剩下2个
t.Assert(array1.Remove(1), "d")
v, ok = array1.Remove(1)
t.Assert(v, "d")
t.Assert(ok, true)
t.Assert(array1.Len(), 1)
})
}
@ -100,29 +131,64 @@ func TestSortedStrArray_PopLeft(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []string{"e", "a", "d", "c", "b"}
array1 := garray.NewSortedStrArrayFrom(a1)
s1 := array1.PopLeft()
t.Assert(s1, "a")
v, ok := array1.PopLeft()
t.Assert(v, "a")
t.Assert(ok, true)
t.Assert(array1.Len(), 4)
t.Assert(array1.Contains("a"), false)
})
gtest.C(t, func(t *gtest.T) {
array := garray.NewSortedStrArrayFrom(g.SliceStr{"1", "2", "3"})
v, ok := array.PopLeft()
t.Assert(v, 1)
t.Assert(ok, true)
t.Assert(array.Len(), 2)
v, ok = array.PopLeft()
t.Assert(v, 2)
t.Assert(ok, true)
t.Assert(array.Len(), 1)
v, ok = array.PopLeft()
t.Assert(v, 3)
t.Assert(ok, true)
t.Assert(array.Len(), 0)
})
}
func TestSortedStrArray_PopRight(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []string{"e", "a", "d", "c", "b"}
array1 := garray.NewSortedStrArrayFrom(a1)
s1 := array1.PopRight()
t.Assert(s1, "e")
v, ok := array1.PopRight()
t.Assert(v, "e")
t.Assert(ok, ok)
t.Assert(array1.Len(), 4)
t.Assert(array1.Contains("e"), false)
})
gtest.C(t, func(t *gtest.T) {
array := garray.NewSortedStrArrayFrom(g.SliceStr{"1", "2", "3"})
v, ok := array.PopRight()
t.Assert(v, 3)
t.Assert(ok, true)
t.Assert(array.Len(), 2)
v, ok = array.PopRight()
t.Assert(v, 2)
t.Assert(ok, true)
t.Assert(array.Len(), 1)
v, ok = array.PopRight()
t.Assert(v, 1)
t.Assert(ok, true)
t.Assert(array.Len(), 0)
})
}
func TestSortedStrArray_PopRand(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []string{"e", "a", "d", "c", "b"}
array1 := garray.NewSortedStrArrayFrom(a1)
s1 := array1.PopRand()
s1, ok := array1.PopRand()
t.Assert(ok, true)
t.AssertIN(s1, []string{"e", "a", "d", "c", "b"})
t.Assert(array1.Len(), 4)
t.Assert(array1.Contains(s1), false)
@ -147,11 +213,19 @@ func TestSortedStrArray_PopRands(t *testing.T) {
func TestSortedStrArray_Empty(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewSortedStrArray()
t.Assert(array.PopLeft(), nil)
v, ok := array.PopLeft()
t.Assert(v, "")
t.Assert(ok, false)
t.Assert(array.PopLefts(10), nil)
t.Assert(array.PopRight(), nil)
v, ok = array.PopRight()
t.Assert(v, "")
t.Assert(ok, false)
t.Assert(array.PopRights(10), nil)
t.Assert(array.PopRand(), nil)
v, ok = array.PopRand()
t.Assert(v, "")
t.Assert(ok, false)
t.Assert(array.PopRands(10), nil)
})
}
@ -168,6 +242,7 @@ func TestSortedStrArray_PopLefts(t *testing.T) {
s1 = array1.PopLefts(4)
t.Assert(len(s1), 3)
t.Assert(s1, []string{"c", "d", "e"})
t.Assert(array1.Len(), 0)
})
}
@ -283,7 +358,9 @@ func TestSortedStrArray_Rand(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
a1 := []string{"e", "a", "d"}
array1 := garray.NewSortedStrArrayFrom(a1)
t.AssertIN(array1.Rand(), []string{"e", "a", "d"})
v, ok := array1.Rand()
t.AssertIN(v, []string{"e", "a", "d"})
t.Assert(ok, true)
})
}
@ -297,8 +374,7 @@ func TestSortedStrArray_Rands(t *testing.T) {
t.Assert(len(s1), 2)
s1 = array1.Rands(4)
t.AssertIN(s1, []string{"e", "a", "d"})
t.Assert(len(s1), 3)
t.Assert(len(s1), 4)
})
}
@ -480,6 +556,7 @@ func TestSortedStrArray_Merge(t *testing.T) {
}
func TestSortedStrArray_Json(t *testing.T) {
// array pointer
gtest.C(t, func(t *gtest.T) {
s1 := []string{"a", "b", "d", "c"}
s2 := []string{"a", "b", "c", "d"}
@ -500,7 +577,28 @@ func TestSortedStrArray_Json(t *testing.T) {
t.Assert(a3.Slice(), s1)
t.Assert(a3.Interfaces(), s1)
})
// array value
gtest.C(t, func(t *gtest.T) {
s1 := []string{"a", "b", "d", "c"}
s2 := []string{"a", "b", "c", "d"}
a1 := *garray.NewSortedStrArrayFrom(s1)
b1, err1 := json.Marshal(a1)
b2, err2 := json.Marshal(s1)
t.Assert(b1, b2)
t.Assert(err1, err2)
a2 := garray.NewSortedStrArray()
err1 = json.Unmarshal(b2, &a2)
t.Assert(a2.Slice(), s2)
t.Assert(a2.Interfaces(), s2)
var a3 garray.SortedStrArray
err := json.Unmarshal(b2, &a3)
t.Assert(err, nil)
t.Assert(a3.Slice(), s1)
t.Assert(a3.Interfaces(), s1)
})
// array pointer
gtest.C(t, func(t *gtest.T) {
type User struct {
Name string
@ -513,6 +611,25 @@ func TestSortedStrArray_Json(t *testing.T) {
b, err := json.Marshal(data)
t.Assert(err, nil)
user := new(User)
err = json.Unmarshal(b, user)
t.Assert(err, nil)
t.Assert(user.Name, data["Name"])
t.Assert(user.Scores, []string{"A", "A", "A+"})
})
// array value
gtest.C(t, func(t *gtest.T) {
type User struct {
Name string
Scores garray.SortedStrArray
}
data := g.Map{
"Name": "john",
"Scores": []string{"A+", "A", "A"},
}
b, err := json.Marshal(data)
t.Assert(err, nil)
user := new(User)
err = json.Unmarshal(b, user)
t.Assert(err, nil)
@ -619,3 +736,12 @@ func TestSortedStrArray_FilterEmpty(t *testing.T) {
t.Assert(array.FilterEmpty(), g.SliceStr{"1", "2"})
})
}
func TestSortedStrArray_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
array := garray.NewSortedStrArrayFrom(g.SliceStr{"1", "2"})
t.Assert(array.Walk(func(value string) string {
return "key-" + value
}), g.Slice{"key-1", "key-2"})
})
}

View File

@ -1,70 +0,0 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). 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 gchan provides graceful channel for no panic operations.
//
// It's safe to call Chan.Push/Close functions repeatedly.
package gchan
import (
"errors"
"github.com/gogf/gf/container/gtype"
)
// Graceful channel.
type Chan struct {
channel chan interface{}
closed *gtype.Bool
}
// New creates a graceful channel with given <limit>.
func New(limit int) *Chan {
return &Chan{
channel: make(chan interface{}, limit),
closed: gtype.NewBool(),
}
}
// Push pushes <value> to channel.
// It is safe to be called repeatedly.
func (c *Chan) Push(value interface{}) error {
if c.closed.Val() {
return errors.New("channel is closed")
}
c.channel <- value
return nil
}
// Pop pops value from channel.
// If there's no value in channel, it would block to wait.
// If the channel is closed, it will return a nil value immediately.
func (c *Chan) Pop() interface{} {
return <-c.channel
}
// Close closes the channel.
// It is safe to be called repeatedly.
func (c *Chan) Close() {
if !c.closed.Set(true) {
close(c.channel)
}
}
// See Len.
func (c *Chan) Size() int {
return c.Len()
}
// Len returns the length of the channel.
func (c *Chan) Len() int {
return len(c.channel)
}
// Cap returns the capacity of the channel.
func (c *Chan) Cap() int {
return cap(c.channel)
}

View File

@ -1,30 +0,0 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). 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 gchan_test
import (
"fmt"
"github.com/gogf/gf/container/gchan"
)
func Example_basic() {
n := 10
c := gchan.New(n)
for i := 0; i < n; i++ {
c.Push(i)
}
fmt.Println(c.Len(), c.Cap())
for i := 0; i < n; i++ {
fmt.Print(c.Pop())
}
c.Close()
// Output:
//10 10
//0123456789
}

View File

@ -1,47 +0,0 @@
package gchan_test
import (
"errors"
"testing"
"github.com/gogf/gf/container/gchan"
"github.com/gogf/gf/test/gtest"
)
func Test_Gchan(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
ch := gchan.New(10)
t.Assert(ch.Cap(), 10)
t.Assert(ch.Push(1), nil)
t.Assert(ch.Len(), 1)
t.Assert(ch.Size(), 1)
ch.Pop()
t.Assert(ch.Len(), 0)
t.Assert(ch.Size(), 0)
ch.Close()
t.Assert(ch.Push(1), errors.New("channel is closed"))
ch = gchan.New(0)
ch1 := gchan.New(0)
go func() {
var i = 0
for {
v := ch.Pop()
if v == nil {
ch1.Push(i)
break
}
t.Assert(v, i)
i++
}
}()
for index := 0; index < 10; index++ {
ch.Push(index)
}
ch.Close()
t.Assert(ch1.Pop(), 10)
ch1.Close()
})
}

View File

@ -5,32 +5,33 @@
// You can obtain one at https://github.com/gogf/gf.
//
// Package glist provides a concurrent-safe/unsafe doubly linked list.
// Package glist provides most commonly used doubly linked list container which also supports concurrent-safe/unsafe switch feature.
package glist
import (
"bytes"
"container/list"
"encoding/json"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/internal/rwmutex"
)
type (
// List is a doubly linked list containing a concurrent-safe/unsafe switch.
// The switch should be set when its initialization and cannot be changed then.
List struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
list *list.List
}
// Element the item type of the list.
Element = list.Element
)
// New creates and returns a new empty doubly linked list.
func New(safe ...bool) *List {
return &List{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
list: list.New(),
}
}
@ -44,7 +45,7 @@ func NewFrom(array []interface{}, safe ...bool) *List {
l.PushBack(v)
}
return &List{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
list: l,
}
}
@ -52,6 +53,9 @@ func NewFrom(array []interface{}, safe ...bool) *List {
// PushFront inserts a new element <e> with value <v> at the front of list <l> and returns <e>.
func (l *List) PushFront(v interface{}) (e *Element) {
l.mu.Lock()
if l.list == nil {
l.list = list.New()
}
e = l.list.PushFront(v)
l.mu.Unlock()
return
@ -60,6 +64,9 @@ func (l *List) PushFront(v interface{}) (e *Element) {
// PushBack inserts a new element <e> with value <v> at the back of list <l> and returns <e>.
func (l *List) PushBack(v interface{}) (e *Element) {
l.mu.Lock()
if l.list == nil {
l.list = list.New()
}
e = l.list.PushBack(v)
l.mu.Unlock()
return
@ -68,6 +75,9 @@ func (l *List) PushBack(v interface{}) (e *Element) {
// PushFronts inserts multiple new elements with values <values> at the front of list <l>.
func (l *List) PushFronts(values []interface{}) {
l.mu.Lock()
if l.list == nil {
l.list = list.New()
}
for _, v := range values {
l.list.PushFront(v)
}
@ -77,6 +87,9 @@ func (l *List) PushFronts(values []interface{}) {
// PushBacks inserts multiple new elements with values <values> at the back of list <l>.
func (l *List) PushBacks(values []interface{}) {
l.mu.Lock()
if l.list == nil {
l.list = list.New()
}
for _, v := range values {
l.list.PushBack(v)
}
@ -86,20 +99,28 @@ func (l *List) PushBacks(values []interface{}) {
// PopBack removes the element from back of <l> and returns the value of the element.
func (l *List) PopBack() (value interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
return
}
if e := l.list.Back(); e != nil {
value = l.list.Remove(e)
}
l.mu.Unlock()
return
}
// PopFront removes the element from front of <l> and returns the value of the element.
func (l *List) PopFront() (value interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
return
}
if e := l.list.Front(); e != nil {
value = l.list.Remove(e)
}
l.mu.Unlock()
return
}
@ -107,6 +128,11 @@ func (l *List) PopFront() (value interface{}) {
// and returns values of the removed elements as slice.
func (l *List) PopBacks(max int) (values []interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
return
}
length := l.list.Len()
if length > 0 {
if max > 0 && max < length {
@ -117,7 +143,6 @@ func (l *List) PopBacks(max int) (values []interface{}) {
values[i] = l.list.Remove(l.list.Back())
}
}
l.mu.Unlock()
return
}
@ -125,6 +150,11 @@ func (l *List) PopBacks(max int) (values []interface{}) {
// and returns values of the removed elements as slice.
func (l *List) PopFronts(max int) (values []interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
return
}
length := l.list.Len()
if length > 0 {
if max > 0 && max < length {
@ -135,7 +165,6 @@ func (l *List) PopFronts(max int) (values []interface{}) {
values[i] = l.list.Remove(l.list.Front())
}
}
l.mu.Unlock()
return
}
@ -154,6 +183,10 @@ func (l *List) PopFrontAll() []interface{} {
// FrontAll copies and returns values of all elements from front of <l> as slice.
func (l *List) FrontAll() (values []interface{}) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
length := l.list.Len()
if length > 0 {
values = make([]interface{}, length)
@ -161,13 +194,16 @@ func (l *List) FrontAll() (values []interface{}) {
values[i] = e.Value
}
}
l.mu.RUnlock()
return
}
// BackAll copies and returns values of all elements from back of <l> as slice.
func (l *List) BackAll() (values []interface{}) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
length := l.list.Len()
if length > 0 {
values = make([]interface{}, length)
@ -175,43 +211,54 @@ func (l *List) BackAll() (values []interface{}) {
values[i] = e.Value
}
}
l.mu.RUnlock()
return
}
// FrontValue returns value of the first element of <l> or nil if the list is empty.
func (l *List) FrontValue() (value interface{}) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
if e := l.list.Front(); e != nil {
value = e.Value
}
l.mu.RUnlock()
return
}
// BackValue returns value of the last element of <l> or nil if the list is empty.
func (l *List) BackValue() (value interface{}) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
if e := l.list.Back(); e != nil {
value = e.Value
}
l.mu.RUnlock()
return
}
// Front returns the first element of list <l> or nil if the list is empty.
func (l *List) Front() (e *Element) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
e = l.list.Front()
l.mu.RUnlock()
return
}
// Back returns the last element of list <l> or nil if the list is empty.
func (l *List) Back() (e *Element) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
e = l.list.Back()
l.mu.RUnlock()
return
}
@ -219,8 +266,11 @@ func (l *List) Back() (e *Element) {
// The complexity is O(1).
func (l *List) Len() (length int) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
length = l.list.Len()
l.mu.RUnlock()
return
}
@ -234,8 +284,11 @@ func (l *List) Size() int {
// The element and <p> must not be nil.
func (l *List) MoveBefore(e, p *Element) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
l.list.MoveBefore(e, p)
l.mu.Unlock()
}
// MoveAfter moves element <e> to its new position after <p>.
@ -243,8 +296,11 @@ func (l *List) MoveBefore(e, p *Element) {
// The element and <p> must not be nil.
func (l *List) MoveAfter(e, p *Element) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
l.list.MoveAfter(e, p)
l.mu.Unlock()
}
// MoveToFront moves element <e> to the front of list <l>.
@ -252,8 +308,11 @@ func (l *List) MoveAfter(e, p *Element) {
// The element must not be nil.
func (l *List) MoveToFront(e *Element) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
l.list.MoveToFront(e)
l.mu.Unlock()
}
// MoveToBack moves element <e> to the back of list <l>.
@ -261,8 +320,11 @@ func (l *List) MoveToFront(e *Element) {
// The element must not be nil.
func (l *List) MoveToBack(e *Element) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
l.list.MoveToBack(e)
l.mu.Unlock()
}
// PushBackList inserts a copy of an other list at the back of list <l>.
@ -273,8 +335,11 @@ func (l *List) PushBackList(other *List) {
defer other.mu.RUnlock()
}
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
l.list.PushBackList(other.list)
l.mu.Unlock()
}
// PushFrontList inserts a copy of an other list at the front of list <l>.
@ -285,8 +350,11 @@ func (l *List) PushFrontList(other *List) {
defer other.mu.RUnlock()
}
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
l.list.PushFrontList(other.list)
l.mu.Unlock()
}
// InsertAfter inserts a new element <e> with value <v> immediately after <p> and returns <e>.
@ -294,8 +362,11 @@ func (l *List) PushFrontList(other *List) {
// The <p> must not be nil.
func (l *List) InsertAfter(p *Element, v interface{}) (e *Element) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
e = l.list.InsertAfter(v, p)
l.mu.Unlock()
return
}
@ -304,8 +375,11 @@ func (l *List) InsertAfter(p *Element, v interface{}) (e *Element) {
// The <p> must not be nil.
func (l *List) InsertBefore(p *Element, v interface{}) (e *Element) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
e = l.list.InsertBefore(v, p)
l.mu.Unlock()
return
}
@ -314,18 +388,24 @@ func (l *List) InsertBefore(p *Element, v interface{}) (e *Element) {
// The element must not be nil.
func (l *List) Remove(e *Element) (value interface{}) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
value = l.list.Remove(e)
l.mu.Unlock()
return
}
// Removes removes multiple elements <es> from <l> if <es> are elements of list <l>.
func (l *List) Removes(es []*Element) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
for _, e := range es {
l.list.Remove(e)
}
l.mu.Unlock()
return
}
@ -345,13 +425,18 @@ func (l *List) Clear() {
func (l *List) RLockFunc(f func(list *list.List)) {
l.mu.RLock()
defer l.mu.RUnlock()
f(l.list)
if l.list != nil {
f(l.list)
}
}
// LockFunc locks writing with given callback function <f> within RWMutex.Lock.
func (l *List) LockFunc(f func(list *list.List)) {
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
f(l.list)
}
@ -360,11 +445,14 @@ func (l *List) Iterator(f func(e *Element) bool) {
l.IteratorAsc(f)
}
// IteratorAsc iterates the list in ascending order with given callback function <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 *List) IteratorAsc(f func(e *Element) bool) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
length := l.list.Len()
if length > 0 {
for i, e := 0, l.list.Front(); i < length; i, e = i+1, e.Next() {
@ -375,11 +463,14 @@ func (l *List) IteratorAsc(f func(e *Element) bool) {
}
}
// IteratorDesc iterates the list in descending order with given callback function <f>.
// 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 *List) IteratorDesc(f func(e *Element) bool) {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return
}
length := l.list.Len()
if length > 0 {
for i, e := 0, l.list.Back(); i < length; i, e = i+1, e.Prev() {
@ -394,17 +485,14 @@ func (l *List) IteratorDesc(f func(e *Element) bool) {
func (l *List) Join(glue string) string {
l.mu.RLock()
defer l.mu.RUnlock()
if l.list == nil {
return ""
}
buffer := bytes.NewBuffer(nil)
length := l.list.Len()
if length > 0 {
s := ""
for i, e := 0, l.list.Front(); i < length; i, e = i+1, e.Next() {
s = gconv.String(e.Value)
if gstr.IsNumeric(s) {
buffer.WriteString(s)
} else {
buffer.WriteString(`"` + gstr.QuoteMeta(s, `"\`) + `"`)
}
buffer.WriteString(gconv.String(e.Value))
if i != length-1 {
buffer.WriteString(glue)
}
@ -425,12 +513,11 @@ func (l *List) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (l *List) UnmarshalJSON(b []byte) error {
if l.mu == nil {
l.mu = rwmutex.New()
l.list = list.New()
}
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
var array []interface{}
if err := json.Unmarshal(b, &array); err != nil {
return err
@ -441,12 +528,11 @@ func (l *List) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for list.
func (l *List) UnmarshalValue(value interface{}) (err error) {
if l.mu == nil {
l.mu = rwmutex.New()
l.list = list.New()
}
l.mu.Lock()
defer l.mu.Unlock()
if l.list == nil {
l.list = list.New()
}
var array []interface{}
switch value.(type) {
case string, []byte:

View File

@ -10,6 +10,7 @@ import (
"container/list"
"fmt"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/container/glist"
)
@ -96,5 +97,30 @@ func Example_iterate() {
//10987654321
//12345678910
//10987654321
//[1,2,3,4,5,"M",7,8,9,10]
//[1,2,3,4,5,M,7,8,9,10]
}
func Example_popItem() {
l := glist.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9})
fmt.Println(l.PopBack())
fmt.Println(l.PopBacks(2))
fmt.Println(l.PopFront())
fmt.Println(l.PopFronts(2))
// Output:
// 9
// [8 7]
// 1
// [2 3]
}
func Example_join() {
var l glist.List
l.PushBacks(g.Slice{"a", "b", "c", "d"})
fmt.Println(l.Join(","))
// Output:
// a,b,c,d
}

View File

@ -15,7 +15,6 @@ import (
"testing"
)
// 检查链表长度
func checkListLen(t *gtest.T, l *List, len int) bool {
if n := l.Len(); n != len {
t.Errorf("l.Len() = %d, want %d", n, len)
@ -24,7 +23,6 @@ func checkListLen(t *gtest.T, l *List, len int) bool {
return true
}
// 检查指针地址
func checkListPointers(t *gtest.T, l *List, es []*Element) {
if !checkListLen(t, l, len(es)) {
return
@ -41,6 +39,44 @@ func checkListPointers(t *gtest.T, l *List, es []*Element) {
})
}
func TestVar(t *testing.T) {
var l List
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 TestBasic(t *testing.T) {
l := New()
l.PushFront(1)
@ -567,6 +603,17 @@ func TestList_Removes(t *testing.T) {
})
}
func TestList_Pop(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewFrom([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9})
t.Assert(l.PopBack(), 9)
t.Assert(l.PopBacks(2), []interface{}{8, 7})
t.Assert(l.PopFront(), 1)
t.Assert(l.PopFronts(2), []interface{}{2, 3})
})
}
func TestList_Clear(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := New()
@ -634,15 +681,15 @@ func TestList_Iterator(t *testing.T) {
func TestList_Join(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewFrom([]interface{}{1, 2, "a", `"b"`, `\c`})
t.Assert(l.Join(","), `1,2,"a","\"b\"","\\c"`)
t.Assert(l.Join("."), `1.2."a"."\"b\""."\\c"`)
t.Assert(l.Join(","), `1,2,a,"b",\c`)
t.Assert(l.Join("."), `1.2.a."b".\c`)
})
}
func TestList_String(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
l := NewFrom([]interface{}{1, 2, "a", `"b"`, `\c`})
t.Assert(l.String(), `[1,2,"a","\"b\"","\\c"]`)
t.Assert(l.String(), `[1,2,a,"b",\c]`)
})
}

View File

@ -4,12 +4,13 @@
// If a copy of the MIT was not distributed with gm file,
// You can obtain one at https://github.com/gogf/gf.
// Package gmap provides concurrent-safe/unsafe map containers.
// Package gmap provides most commonly used map container which also support concurrent-safe/unsafe switch feature.
package gmap
// Map based on hash table, alias of AnyAnyMap.
type Map = AnyAnyMap
type HashMap = AnyAnyMap
type (
Map = AnyAnyMap // Map is alias of AnyAnyMap.
HashMap = AnyAnyMap // HashMap is alias of AnyAnyMap.
)
// New creates and returns an empty hash map.
// The parameter <safe> is used to specify whether using map in concurrent-safety,

View File

@ -18,7 +18,7 @@ import (
)
type AnyAnyMap struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[interface{}]interface{}
}
@ -27,7 +27,7 @@ type AnyAnyMap struct {
// which is false in default.
func NewAnyAnyMap(safe ...bool) *AnyAnyMap {
return &AnyAnyMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[interface{}]interface{}),
}
}
@ -37,12 +37,12 @@ func NewAnyAnyMap(safe ...bool) *AnyAnyMap {
// there might be some concurrent-safe issues when changing the map outside.
func NewAnyAnyMapFrom(data map[interface{}]interface{}, safe ...bool) *AnyAnyMap {
return &AnyAnyMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// Iterator iterates the hash map readonly with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *AnyAnyMap) Iterator(f func(k interface{}, v interface{}) bool) {
m.mu.RLock()
@ -89,37 +89,56 @@ func (m *AnyAnyMap) MapCopy() map[interface{}]interface{} {
// MapStrAny returns a copy of the underlying data of the map as map[string]interface{}.
func (m *AnyAnyMap) MapStrAny() map[string]interface{} {
m.mu.RLock()
defer m.mu.RUnlock()
data := make(map[string]interface{}, len(m.data))
for k, v := range m.data {
data[gconv.String(k)] = v
}
m.mu.RUnlock()
return data
}
// FilterEmpty deletes all key-value pair of which the value is empty.
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
func (m *AnyAnyMap) FilterEmpty() {
m.mu.Lock()
defer m.mu.Unlock()
for k, v := range m.data {
if empty.IsEmpty(v) {
delete(m.data, k)
}
}
m.mu.Unlock()
}
// FilterNil deletes all key-value pair of which the value is nil.
func (m *AnyAnyMap) FilterNil() {
m.mu.Lock()
defer m.mu.Unlock()
for k, v := range m.data {
if empty.IsNil(v) {
delete(m.data, k)
}
}
}
// Set sets key-value to the hash map.
func (m *AnyAnyMap) Set(key interface{}, val interface{}) {
func (m *AnyAnyMap) Set(key interface{}, value interface{}) {
m.mu.Lock()
m.data[key] = val
if m.data == nil {
m.data = make(map[interface{}]interface{})
}
m.data[key] = value
m.mu.Unlock()
}
// Sets batch sets key-values to the hash map.
func (m *AnyAnyMap) Sets(data map[interface{}]interface{}) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
if m.data == nil {
m.data = data
} else {
for k, v := range data {
m.data[k] = v
}
}
m.mu.Unlock()
}
@ -128,17 +147,21 @@ func (m *AnyAnyMap) Sets(data map[interface{}]interface{}) {
// Second return parameter <found> is true if key was found, otherwise false.
func (m *AnyAnyMap) Search(key interface{}) (value interface{}, found bool) {
m.mu.RLock()
value, found = m.data[key]
if m.data != nil {
value, found = m.data[key]
}
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *AnyAnyMap) Get(key interface{}) interface{} {
func (m *AnyAnyMap) Get(key interface{}) (value interface{}) {
m.mu.RLock()
val, _ := m.data[key]
if m.data != nil {
value, _ = m.data[key]
}
m.mu.RUnlock()
return val
return
}
// Pop retrieves and deletes an item from the map.
@ -163,8 +186,10 @@ func (m *AnyAnyMap) Pops(size int) map[interface{}]interface{} {
if size == 0 {
return nil
}
index := 0
newMap := make(map[interface{}]interface{}, size)
var (
index = 0
newMap = make(map[interface{}]interface{}, size)
)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
@ -188,6 +213,9 @@ func (m *AnyAnyMap) Pops(size int) map[interface{}]interface{} {
func (m *AnyAnyMap) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[interface{}]interface{})
}
if v, ok := m.data[key]; ok {
return v
}
@ -235,26 +263,26 @@ func (m *AnyAnyMap) GetOrSetFuncLock(key interface{}, f func() interface{}) inte
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
// GetVar returns a Var with the value by given <key>.
// The returned Var is un-concurrent safe.
func (m *AnyAnyMap) GetVar(key interface{}) *gvar.Var {
return gvar.New(m.Get(key))
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSet returns a Var with result from GetVarOrSet.
// The returned Var is un-concurrent safe.
func (m *AnyAnyMap) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
return gvar.New(m.GetOrSet(key, value))
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
// The returned Var is un-concurrent safe.
func (m *AnyAnyMap) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFunc(key, f))
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
// The returned Var is un-concurrent safe.
func (m *AnyAnyMap) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFuncLock(key, f))
}
@ -293,21 +321,25 @@ func (m *AnyAnyMap) SetIfNotExistFuncLock(key interface{}, f func() interface{})
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *AnyAnyMap) Remove(key interface{}) interface{} {
func (m *AnyAnyMap) Remove(key interface{}) (value interface{}) {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
if m.data != nil {
var ok bool
if value, ok = m.data[key]; ok {
delete(m.data, key)
}
}
m.mu.Unlock()
return val
return
}
// Removes batch deletes values of the map by keys.
func (m *AnyAnyMap) Removes(keys []interface{}) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
@ -315,36 +347,43 @@ func (m *AnyAnyMap) Removes(keys []interface{}) {
// Keys returns all keys of the map as a slice.
func (m *AnyAnyMap) Keys() []interface{} {
m.mu.RLock()
keys := make([]interface{}, len(m.data))
index := 0
defer m.mu.RUnlock()
var (
keys = make([]interface{}, len(m.data))
index = 0
)
for key := range m.data {
keys[index] = key
index++
}
m.mu.RUnlock()
return keys
}
// Values returns all values of the map as a slice.
func (m *AnyAnyMap) Values() []interface{} {
m.mu.RLock()
values := make([]interface{}, len(m.data))
index := 0
defer m.mu.RUnlock()
var (
values = make([]interface{}, len(m.data))
index = 0
)
for _, value := range m.data {
values[index] = value
index++
}
m.mu.RUnlock()
return values
}
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *AnyAnyMap) Contains(key interface{}) bool {
var ok bool
m.mu.RLock()
_, exists := m.data[key]
if m.data != nil {
_, ok = m.data[key]
}
m.mu.RUnlock()
return exists
return ok
}
// Size returns the size of the map.
@ -405,6 +444,10 @@ func (m *AnyAnyMap) Flip() {
func (m *AnyAnyMap) Merge(other *AnyAnyMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = other.MapCopy()
return
}
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
@ -421,12 +464,11 @@ func (m *AnyAnyMap) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *AnyAnyMap) UnmarshalJSON(b []byte) error {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[interface{}]interface{})
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[interface{}]interface{})
}
var data map[string]interface{}
if err := json.Unmarshal(b, &data); err != nil {
return err
@ -439,12 +481,11 @@ func (m *AnyAnyMap) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *AnyAnyMap) UnmarshalValue(value interface{}) (err error) {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[interface{}]interface{})
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[interface{}]interface{})
}
for k, v := range gconv.Map(value) {
m.data[k] = v
}

View File

@ -18,7 +18,7 @@ import (
)
type IntAnyMap struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[int]interface{}
}
@ -27,7 +27,7 @@ type IntAnyMap struct {
// which is false in default.
func NewIntAnyMap(safe ...bool) *IntAnyMap {
return &IntAnyMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[int]interface{}),
}
}
@ -37,12 +37,12 @@ func NewIntAnyMap(safe ...bool) *IntAnyMap {
// there might be some concurrent-safe issues when changing the map outside.
func NewIntAnyMapFrom(data map[int]interface{}, safe ...bool) *IntAnyMap {
return &IntAnyMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// Iterator iterates the hash map readonly with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *IntAnyMap) Iterator(f func(k int, v interface{}) bool) {
m.mu.RLock()
@ -98,6 +98,7 @@ func (m *IntAnyMap) MapCopy() map[int]interface{} {
}
// FilterEmpty deletes all key-value pair of which the value is empty.
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
func (m *IntAnyMap) FilterEmpty() {
m.mu.Lock()
for k, v := range m.data {
@ -108,9 +109,23 @@ func (m *IntAnyMap) FilterEmpty() {
m.mu.Unlock()
}
// FilterNil deletes all key-value pair of which the value is nil.
func (m *IntAnyMap) FilterNil() {
m.mu.Lock()
defer m.mu.Unlock()
for k, v := range m.data {
if empty.IsNil(v) {
delete(m.data, k)
}
}
}
// Set sets key-value to the hash map.
func (m *IntAnyMap) Set(key int, val interface{}) {
m.mu.Lock()
if m.data == nil {
m.data = make(map[int]interface{})
}
m.data[key] = val
m.mu.Unlock()
}
@ -118,8 +133,12 @@ func (m *IntAnyMap) Set(key int, val interface{}) {
// Sets batch sets key-values to the hash map.
func (m *IntAnyMap) Sets(data map[int]interface{}) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
if m.data == nil {
m.data = data
} else {
for k, v := range data {
m.data[k] = v
}
}
m.mu.Unlock()
}
@ -128,17 +147,21 @@ func (m *IntAnyMap) Sets(data map[int]interface{}) {
// Second return parameter <found> is true if key was found, otherwise false.
func (m *IntAnyMap) Search(key int) (value interface{}, found bool) {
m.mu.RLock()
value, found = m.data[key]
if m.data != nil {
value, found = m.data[key]
}
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *IntAnyMap) Get(key int) interface{} {
func (m *IntAnyMap) Get(key int) (value interface{}) {
m.mu.RLock()
val, _ := m.data[key]
if m.data != nil {
value, _ = m.data[key]
}
m.mu.RUnlock()
return val
return
}
// Pop retrieves and deletes an item from the map.
@ -163,8 +186,10 @@ func (m *IntAnyMap) Pops(size int) map[int]interface{} {
if size == 0 {
return nil
}
index := 0
newMap := make(map[int]interface{}, size)
var (
index = 0
newMap = make(map[int]interface{}, size)
)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
@ -188,6 +213,9 @@ func (m *IntAnyMap) Pops(size int) map[int]interface{} {
func (m *IntAnyMap) doSetWithLockCheck(key int, value interface{}) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]interface{})
}
if v, ok := m.data[key]; ok {
return v
}
@ -233,26 +261,26 @@ func (m *IntAnyMap) GetOrSetFuncLock(key int, f func() interface{}) interface{}
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
// GetVar returns a Var with the value by given <key>.
// The returned Var is un-concurrent safe.
func (m *IntAnyMap) GetVar(key int) *gvar.Var {
return gvar.New(m.Get(key))
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSet returns a Var with result from GetVarOrSet.
// The returned Var is un-concurrent safe.
func (m *IntAnyMap) GetVarOrSet(key int, value interface{}) *gvar.Var {
return gvar.New(m.GetOrSet(key, value))
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
// The returned Var is un-concurrent safe.
func (m *IntAnyMap) GetVarOrSetFunc(key int, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFunc(key, f))
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
// The returned Var is un-concurrent safe.
func (m *IntAnyMap) GetVarOrSetFuncLock(key int, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFuncLock(key, f))
}
@ -293,28 +321,34 @@ func (m *IntAnyMap) SetIfNotExistFuncLock(key int, f func() interface{}) bool {
// Removes batch deletes values of the map by keys.
func (m *IntAnyMap) Removes(keys []int) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *IntAnyMap) Remove(key int) interface{} {
func (m *IntAnyMap) Remove(key int) (value interface{}) {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
if m.data != nil {
var ok bool
if value, ok = m.data[key]; ok {
delete(m.data, key)
}
}
m.mu.Unlock()
return val
return
}
// Keys returns all keys of the map as a slice.
func (m *IntAnyMap) Keys() []int {
m.mu.RLock()
keys := make([]int, len(m.data))
index := 0
var (
keys = make([]int, len(m.data))
index = 0
)
for key := range m.data {
keys[index] = key
index++
@ -326,8 +360,10 @@ func (m *IntAnyMap) Keys() []int {
// Values returns all values of the map as a slice.
func (m *IntAnyMap) Values() []interface{} {
m.mu.RLock()
values := make([]interface{}, len(m.data))
index := 0
var (
values = make([]interface{}, len(m.data))
index = 0
)
for _, value := range m.data {
values[index] = value
index++
@ -339,10 +375,13 @@ func (m *IntAnyMap) Values() []interface{} {
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *IntAnyMap) Contains(key int) bool {
var ok bool
m.mu.RLock()
_, exists := m.data[key]
if m.data != nil {
_, ok = m.data[key]
}
m.mu.RUnlock()
return exists
return ok
}
// Size returns the size of the map.
@ -403,6 +442,10 @@ func (m *IntAnyMap) Flip() {
func (m *IntAnyMap) Merge(other *IntAnyMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = other.MapCopy()
return
}
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
@ -421,12 +464,11 @@ func (m *IntAnyMap) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *IntAnyMap) UnmarshalJSON(b []byte) error {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[int]interface{})
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]interface{})
}
if err := json.Unmarshal(b, &m.data); err != nil {
return err
}
@ -435,12 +477,11 @@ func (m *IntAnyMap) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *IntAnyMap) UnmarshalValue(value interface{}) (err error) {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[int]interface{})
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]interface{})
}
switch value.(type) {
case string, []byte:
return json.Unmarshal(gconv.Bytes(value), &m.data)

View File

@ -16,7 +16,7 @@ import (
)
type IntIntMap struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[int]int
}
@ -25,7 +25,7 @@ type IntIntMap struct {
// which is false in default.
func NewIntIntMap(safe ...bool) *IntIntMap {
return &IntIntMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[int]int),
}
}
@ -35,12 +35,12 @@ func NewIntIntMap(safe ...bool) *IntIntMap {
// there might be some concurrent-safe issues when changing the map outside.
func NewIntIntMapFrom(data map[int]int, safe ...bool) *IntIntMap {
return &IntIntMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// Iterator iterates the hash map readonly with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *IntIntMap) Iterator(f func(k int, v int) bool) {
m.mu.RLock()
@ -96,6 +96,7 @@ func (m *IntIntMap) MapCopy() map[int]int {
}
// FilterEmpty deletes all key-value pair of which the value is empty.
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
func (m *IntIntMap) FilterEmpty() {
m.mu.Lock()
for k, v := range m.data {
@ -109,6 +110,9 @@ func (m *IntIntMap) FilterEmpty() {
// Set sets key-value to the hash map.
func (m *IntIntMap) Set(key int, val int) {
m.mu.Lock()
if m.data == nil {
m.data = make(map[int]int)
}
m.data[key] = val
m.mu.Unlock()
}
@ -116,8 +120,12 @@ func (m *IntIntMap) Set(key int, val int) {
// Sets batch sets key-values to the hash map.
func (m *IntIntMap) Sets(data map[int]int) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
if m.data == nil {
m.data = data
} else {
for k, v := range data {
m.data[k] = v
}
}
m.mu.Unlock()
}
@ -126,17 +134,21 @@ func (m *IntIntMap) Sets(data map[int]int) {
// Second return parameter <found> is true if key was found, otherwise false.
func (m *IntIntMap) Search(key int) (value int, found bool) {
m.mu.RLock()
value, found = m.data[key]
if m.data != nil {
value, found = m.data[key]
}
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *IntIntMap) Get(key int) int {
func (m *IntIntMap) Get(key int) (value int) {
m.mu.RLock()
val, _ := m.data[key]
if m.data != nil {
value, _ = m.data[key]
}
m.mu.RUnlock()
return val
return
}
// Pop retrieves and deletes an item from the map.
@ -161,8 +173,10 @@ func (m *IntIntMap) Pops(size int) map[int]int {
if size == 0 {
return nil
}
index := 0
newMap := make(map[int]int, size)
var (
index = 0
newMap = make(map[int]int, size)
)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
@ -181,12 +195,14 @@ func (m *IntIntMap) Pops(size int) map[int]int {
// It returns value with given <key>.
func (m *IntIntMap) doSetWithLockCheck(key int, value int) int {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]int)
}
if v, ok := m.data[key]; ok {
m.mu.Unlock()
return v
}
m.data[key] = value
m.mu.Unlock()
return value
}
@ -219,6 +235,9 @@ func (m *IntIntMap) GetOrSetFuncLock(key int, f func() int) int {
if v, ok := m.Search(key); !ok {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]int)
}
if v, ok = m.data[key]; ok {
return v
}
@ -259,6 +278,9 @@ func (m *IntIntMap) SetIfNotExistFuncLock(key int, f func() int) bool {
if !m.Contains(key) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]int)
}
if _, ok := m.data[key]; !ok {
m.data[key] = f()
}
@ -270,28 +292,34 @@ func (m *IntIntMap) SetIfNotExistFuncLock(key int, f func() int) bool {
// Removes batch deletes values of the map by keys.
func (m *IntIntMap) Removes(keys []int) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *IntIntMap) Remove(key int) int {
func (m *IntIntMap) Remove(key int) (value int) {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
if m.data != nil {
var ok bool
if value, ok = m.data[key]; ok {
delete(m.data, key)
}
}
m.mu.Unlock()
return val
return
}
// Keys returns all keys of the map as a slice.
func (m *IntIntMap) Keys() []int {
m.mu.RLock()
keys := make([]int, len(m.data))
index := 0
var (
keys = make([]int, len(m.data))
index = 0
)
for key := range m.data {
keys[index] = key
index++
@ -303,8 +331,10 @@ func (m *IntIntMap) Keys() []int {
// Values returns all values of the map as a slice.
func (m *IntIntMap) Values() []int {
m.mu.RLock()
values := make([]int, len(m.data))
index := 0
var (
values = make([]int, len(m.data))
index = 0
)
for _, value := range m.data {
values[index] = value
index++
@ -316,10 +346,13 @@ func (m *IntIntMap) Values() []int {
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *IntIntMap) Contains(key int) bool {
var ok bool
m.mu.RLock()
_, exists := m.data[key]
if m.data != nil {
_, ok = m.data[key]
}
m.mu.RUnlock()
return exists
return ok
}
// Size returns the size of the map.
@ -380,6 +413,10 @@ func (m *IntIntMap) Flip() {
func (m *IntIntMap) Merge(other *IntIntMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = other.MapCopy()
return
}
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
@ -398,12 +435,11 @@ func (m *IntIntMap) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *IntIntMap) UnmarshalJSON(b []byte) error {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[int]int)
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]int)
}
if err := json.Unmarshal(b, &m.data); err != nil {
return err
}
@ -412,12 +448,11 @@ func (m *IntIntMap) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *IntIntMap) UnmarshalValue(value interface{}) (err error) {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[int]int)
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]int)
}
switch value.(type) {
case string, []byte:
return json.Unmarshal(gconv.Bytes(value), &m.data)

View File

@ -16,7 +16,7 @@ import (
)
type IntStrMap struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[int]string
}
@ -25,7 +25,7 @@ type IntStrMap struct {
// which is false in default.
func NewIntStrMap(safe ...bool) *IntStrMap {
return &IntStrMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[int]string),
}
}
@ -35,12 +35,12 @@ func NewIntStrMap(safe ...bool) *IntStrMap {
// there might be some concurrent-safe issues when changing the map outside.
func NewIntStrMapFrom(data map[int]string, safe ...bool) *IntStrMap {
return &IntStrMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// Iterator iterates the hash map readonly with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *IntStrMap) Iterator(f func(k int, v string) bool) {
m.mu.RLock()
@ -96,6 +96,7 @@ func (m *IntStrMap) MapCopy() map[int]string {
}
// FilterEmpty deletes all key-value pair of which the value is empty.
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
func (m *IntStrMap) FilterEmpty() {
m.mu.Lock()
for k, v := range m.data {
@ -109,6 +110,9 @@ func (m *IntStrMap) FilterEmpty() {
// Set sets key-value to the hash map.
func (m *IntStrMap) Set(key int, val string) {
m.mu.Lock()
if m.data == nil {
m.data = make(map[int]string)
}
m.data[key] = val
m.mu.Unlock()
}
@ -116,8 +120,12 @@ func (m *IntStrMap) Set(key int, val string) {
// Sets batch sets key-values to the hash map.
func (m *IntStrMap) Sets(data map[int]string) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
if m.data == nil {
m.data = data
} else {
for k, v := range data {
m.data[k] = v
}
}
m.mu.Unlock()
}
@ -126,17 +134,21 @@ func (m *IntStrMap) Sets(data map[int]string) {
// Second return parameter <found> is true if key was found, otherwise false.
func (m *IntStrMap) Search(key int) (value string, found bool) {
m.mu.RLock()
value, found = m.data[key]
if m.data != nil {
value, found = m.data[key]
}
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *IntStrMap) Get(key int) string {
func (m *IntStrMap) Get(key int) (value string) {
m.mu.RLock()
val, _ := m.data[key]
if m.data != nil {
value, _ = m.data[key]
}
m.mu.RUnlock()
return val
return
}
// Pop retrieves and deletes an item from the map.
@ -161,8 +173,10 @@ func (m *IntStrMap) Pops(size int) map[int]string {
if size == 0 {
return nil
}
index := 0
newMap := make(map[int]string, size)
var (
index = 0
newMap = make(map[int]string, size)
)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
@ -182,6 +196,9 @@ func (m *IntStrMap) Pops(size int) map[int]string {
func (m *IntStrMap) doSetWithLockCheck(key int, value string) string {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]string)
}
if v, ok := m.data[key]; ok {
return v
}
@ -218,13 +235,14 @@ func (m *IntStrMap) GetOrSetFuncLock(key int, f func() string) string {
if v, ok := m.Search(key); !ok {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]string)
}
if v, ok = m.data[key]; ok {
return v
}
v = f()
if v != "" {
m.data[key] = v
}
m.data[key] = v
return v
} else {
return v
@ -260,6 +278,9 @@ func (m *IntStrMap) SetIfNotExistFuncLock(key int, f func() string) bool {
if !m.Contains(key) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]string)
}
if _, ok := m.data[key]; !ok {
m.data[key] = f()
}
@ -271,28 +292,34 @@ func (m *IntStrMap) SetIfNotExistFuncLock(key int, f func() string) bool {
// Removes batch deletes values of the map by keys.
func (m *IntStrMap) Removes(keys []int) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *IntStrMap) Remove(key int) string {
func (m *IntStrMap) Remove(key int) (value string) {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
if m.data != nil {
var ok bool
if value, ok = m.data[key]; ok {
delete(m.data, key)
}
}
m.mu.Unlock()
return val
return
}
// Keys returns all keys of the map as a slice.
func (m *IntStrMap) Keys() []int {
m.mu.RLock()
keys := make([]int, len(m.data))
index := 0
var (
keys = make([]int, len(m.data))
index = 0
)
for key := range m.data {
keys[index] = key
index++
@ -304,8 +331,10 @@ func (m *IntStrMap) Keys() []int {
// Values returns all values of the map as a slice.
func (m *IntStrMap) Values() []string {
m.mu.RLock()
values := make([]string, len(m.data))
index := 0
var (
values = make([]string, len(m.data))
index = 0
)
for _, value := range m.data {
values[index] = value
index++
@ -317,10 +346,13 @@ func (m *IntStrMap) Values() []string {
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *IntStrMap) Contains(key int) bool {
var ok bool
m.mu.RLock()
_, exists := m.data[key]
if m.data != nil {
_, ok = m.data[key]
}
m.mu.RUnlock()
return exists
return ok
}
// Size returns the size of the map.
@ -381,6 +413,10 @@ func (m *IntStrMap) Flip() {
func (m *IntStrMap) Merge(other *IntStrMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = other.MapCopy()
return
}
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
@ -399,12 +435,11 @@ func (m *IntStrMap) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *IntStrMap) UnmarshalJSON(b []byte) error {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[int]string)
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]string)
}
if err := json.Unmarshal(b, &m.data); err != nil {
return err
}
@ -413,12 +448,11 @@ func (m *IntStrMap) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *IntStrMap) UnmarshalValue(value interface{}) (err error) {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[int]string)
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[int]string)
}
switch value.(type) {
case string, []byte:
return json.Unmarshal(gconv.Bytes(value), &m.data)

View File

@ -18,7 +18,7 @@ import (
)
type StrAnyMap struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[string]interface{}
}
@ -27,7 +27,7 @@ type StrAnyMap struct {
// which is false in default.
func NewStrAnyMap(safe ...bool) *StrAnyMap {
return &StrAnyMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[string]interface{}),
}
}
@ -37,12 +37,12 @@ func NewStrAnyMap(safe ...bool) *StrAnyMap {
// there might be some concurrent-safe issues when changing the map outside.
func NewStrAnyMapFrom(data map[string]interface{}, safe ...bool) *StrAnyMap {
return &StrAnyMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// Iterator iterates the hash map readonly with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *StrAnyMap) Iterator(f func(k string, v interface{}) bool) {
m.mu.RLock()
@ -92,6 +92,7 @@ func (m *StrAnyMap) MapCopy() map[string]interface{} {
}
// FilterEmpty deletes all key-value pair of which the value is empty.
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
func (m *StrAnyMap) FilterEmpty() {
m.mu.Lock()
for k, v := range m.data {
@ -102,9 +103,23 @@ func (m *StrAnyMap) FilterEmpty() {
m.mu.Unlock()
}
// FilterNil deletes all key-value pair of which the value is nil.
func (m *StrAnyMap) FilterNil() {
m.mu.Lock()
defer m.mu.Unlock()
for k, v := range m.data {
if empty.IsNil(v) {
delete(m.data, k)
}
}
}
// Set sets key-value to the hash map.
func (m *StrAnyMap) Set(key string, val interface{}) {
m.mu.Lock()
if m.data == nil {
m.data = make(map[string]interface{})
}
m.data[key] = val
m.mu.Unlock()
}
@ -112,8 +127,12 @@ func (m *StrAnyMap) Set(key string, val interface{}) {
// Sets batch sets key-values to the hash map.
func (m *StrAnyMap) Sets(data map[string]interface{}) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
if m.data == nil {
m.data = data
} else {
for k, v := range data {
m.data[k] = v
}
}
m.mu.Unlock()
}
@ -122,17 +141,21 @@ func (m *StrAnyMap) Sets(data map[string]interface{}) {
// Second return parameter <found> is true if key was found, otherwise false.
func (m *StrAnyMap) Search(key string) (value interface{}, found bool) {
m.mu.RLock()
value, found = m.data[key]
if m.data != nil {
value, found = m.data[key]
}
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *StrAnyMap) Get(key string) interface{} {
func (m *StrAnyMap) Get(key string) (value interface{}) {
m.mu.RLock()
val, _ := m.data[key]
if m.data != nil {
value, _ = m.data[key]
}
m.mu.RUnlock()
return val
return
}
// Pop retrieves and deletes an item from the map.
@ -157,8 +180,10 @@ func (m *StrAnyMap) Pops(size int) map[string]interface{} {
if size == 0 {
return nil
}
index := 0
newMap := make(map[string]interface{}, size)
var (
index = 0
newMap = make(map[string]interface{}, size)
)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
@ -182,6 +207,9 @@ func (m *StrAnyMap) Pops(size int) map[string]interface{} {
func (m *StrAnyMap) doSetWithLockCheck(key string, value interface{}) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]interface{})
}
if v, ok := m.data[key]; ok {
return v
}
@ -229,26 +257,26 @@ func (m *StrAnyMap) GetOrSetFuncLock(key string, f func() interface{}) interface
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
// GetVar returns a Var with the value by given <key>.
// The returned Var is un-concurrent safe.
func (m *StrAnyMap) GetVar(key string) *gvar.Var {
return gvar.New(m.Get(key))
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSet returns a Var with result from GetVarOrSet.
// The returned Var is un-concurrent safe.
func (m *StrAnyMap) GetVarOrSet(key string, value interface{}) *gvar.Var {
return gvar.New(m.GetOrSet(key, value))
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
// The returned Var is un-concurrent safe.
func (m *StrAnyMap) GetVarOrSetFunc(key string, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFunc(key, f))
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
// The returned Var is un-concurrent safe.
func (m *StrAnyMap) GetVarOrSetFuncLock(key string, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFuncLock(key, f))
}
@ -289,28 +317,34 @@ func (m *StrAnyMap) SetIfNotExistFuncLock(key string, f func() interface{}) bool
// Removes batch deletes values of the map by keys.
func (m *StrAnyMap) Removes(keys []string) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *StrAnyMap) Remove(key string) interface{} {
func (m *StrAnyMap) Remove(key string) (value interface{}) {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
if m.data != nil {
var ok bool
if value, ok = m.data[key]; ok {
delete(m.data, key)
}
}
m.mu.Unlock()
return val
return
}
// Keys returns all keys of the map as a slice.
func (m *StrAnyMap) Keys() []string {
m.mu.RLock()
keys := make([]string, len(m.data))
index := 0
var (
keys = make([]string, len(m.data))
index = 0
)
for key := range m.data {
keys[index] = key
index++
@ -322,8 +356,10 @@ func (m *StrAnyMap) Keys() []string {
// Values returns all values of the map as a slice.
func (m *StrAnyMap) Values() []interface{} {
m.mu.RLock()
values := make([]interface{}, len(m.data))
index := 0
var (
values = make([]interface{}, len(m.data))
index = 0
)
for _, value := range m.data {
values[index] = value
index++
@ -335,10 +371,13 @@ func (m *StrAnyMap) Values() []interface{} {
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *StrAnyMap) Contains(key string) bool {
var ok bool
m.mu.RLock()
_, exists := m.data[key]
if m.data != nil {
_, ok = m.data[key]
}
m.mu.RUnlock()
return exists
return ok
}
// Size returns the size of the map.
@ -399,6 +438,10 @@ func (m *StrAnyMap) Flip() {
func (m *StrAnyMap) Merge(other *StrAnyMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = other.MapCopy()
return
}
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
@ -417,12 +460,11 @@ func (m *StrAnyMap) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *StrAnyMap) UnmarshalJSON(b []byte) error {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[string]interface{})
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]interface{})
}
if err := json.Unmarshal(b, &m.data); err != nil {
return err
}
@ -431,10 +473,6 @@ func (m *StrAnyMap) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *StrAnyMap) UnmarshalValue(value interface{}) (err error) {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[string]interface{})
}
m.mu.Lock()
defer m.mu.Unlock()
m.data = gconv.Map(value)

View File

@ -16,7 +16,7 @@ import (
)
type StrIntMap struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[string]int
}
@ -25,7 +25,7 @@ type StrIntMap struct {
// which is false in default.
func NewStrIntMap(safe ...bool) *StrIntMap {
return &StrIntMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[string]int),
}
}
@ -35,12 +35,12 @@ func NewStrIntMap(safe ...bool) *StrIntMap {
// there might be some concurrent-safe issues when changing the map outside.
func NewStrIntMapFrom(data map[string]int, safe ...bool) *StrIntMap {
return &StrIntMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// Iterator iterates the hash map readonly with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *StrIntMap) Iterator(f func(k string, v int) bool) {
m.mu.RLock()
@ -76,11 +76,11 @@ func (m *StrIntMap) Map() map[string]int {
// MapStrAny returns a copy of the underlying data of the map as map[string]interface{}.
func (m *StrIntMap) MapStrAny() map[string]interface{} {
m.mu.RLock()
defer m.mu.RUnlock()
data := make(map[string]interface{}, len(m.data))
for k, v := range m.data {
data[k] = v
}
m.mu.RUnlock()
return data
}
@ -96,6 +96,7 @@ func (m *StrIntMap) MapCopy() map[string]int {
}
// FilterEmpty deletes all key-value pair of which the value is empty.
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
func (m *StrIntMap) FilterEmpty() {
m.mu.Lock()
for k, v := range m.data {
@ -109,6 +110,9 @@ func (m *StrIntMap) FilterEmpty() {
// Set sets key-value to the hash map.
func (m *StrIntMap) Set(key string, val int) {
m.mu.Lock()
if m.data == nil {
m.data = make(map[string]int)
}
m.data[key] = val
m.mu.Unlock()
}
@ -116,8 +120,12 @@ func (m *StrIntMap) Set(key string, val int) {
// Sets batch sets key-values to the hash map.
func (m *StrIntMap) Sets(data map[string]int) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
if m.data == nil {
m.data = data
} else {
for k, v := range data {
m.data[k] = v
}
}
m.mu.Unlock()
}
@ -126,17 +134,21 @@ func (m *StrIntMap) Sets(data map[string]int) {
// Second return parameter <found> is true if key was found, otherwise false.
func (m *StrIntMap) Search(key string) (value int, found bool) {
m.mu.RLock()
value, found = m.data[key]
if m.data != nil {
value, found = m.data[key]
}
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *StrIntMap) Get(key string) int {
func (m *StrIntMap) Get(key string) (value int) {
m.mu.RLock()
val, _ := m.data[key]
if m.data != nil {
value, _ = m.data[key]
}
m.mu.RUnlock()
return val
return
}
// Pop retrieves and deletes an item from the map.
@ -161,8 +173,10 @@ func (m *StrIntMap) Pops(size int) map[string]int {
if size == 0 {
return nil
}
index := 0
newMap := make(map[string]int, size)
var (
index = 0
newMap = make(map[string]int, size)
)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
@ -181,6 +195,9 @@ func (m *StrIntMap) Pops(size int) map[string]int {
// It returns value with given <key>.
func (m *StrIntMap) doSetWithLockCheck(key string, value int) int {
m.mu.Lock()
if m.data == nil {
m.data = make(map[string]int)
}
if v, ok := m.data[key]; ok {
m.mu.Unlock()
return v
@ -221,6 +238,9 @@ func (m *StrIntMap) GetOrSetFuncLock(key string, f func() int) int {
if v, ok := m.Search(key); !ok {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]int)
}
if v, ok = m.data[key]; ok {
return v
}
@ -261,6 +281,9 @@ func (m *StrIntMap) SetIfNotExistFuncLock(key string, f func() int) bool {
if !m.Contains(key) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]int)
}
if _, ok := m.data[key]; !ok {
m.data[key] = f()
}
@ -272,28 +295,34 @@ func (m *StrIntMap) SetIfNotExistFuncLock(key string, f func() int) bool {
// Removes batch deletes values of the map by keys.
func (m *StrIntMap) Removes(keys []string) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *StrIntMap) Remove(key string) int {
func (m *StrIntMap) Remove(key string) (value int) {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
if m.data != nil {
var ok bool
if value, ok = m.data[key]; ok {
delete(m.data, key)
}
}
m.mu.Unlock()
return val
return
}
// Keys returns all keys of the map as a slice.
func (m *StrIntMap) Keys() []string {
m.mu.RLock()
keys := make([]string, len(m.data))
index := 0
var (
keys = make([]string, len(m.data))
index = 0
)
for key := range m.data {
keys[index] = key
index++
@ -305,8 +334,10 @@ func (m *StrIntMap) Keys() []string {
// Values returns all values of the map as a slice.
func (m *StrIntMap) Values() []int {
m.mu.RLock()
values := make([]int, len(m.data))
index := 0
var (
values = make([]int, len(m.data))
index = 0
)
for _, value := range m.data {
values[index] = value
index++
@ -318,10 +349,13 @@ func (m *StrIntMap) Values() []int {
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *StrIntMap) Contains(key string) bool {
var ok bool
m.mu.RLock()
_, exists := m.data[key]
if m.data != nil {
_, ok = m.data[key]
}
m.mu.RUnlock()
return exists
return ok
}
// Size returns the size of the map.
@ -382,6 +416,10 @@ func (m *StrIntMap) Flip() {
func (m *StrIntMap) Merge(other *StrIntMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = other.MapCopy()
return
}
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
@ -400,12 +438,11 @@ func (m *StrIntMap) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *StrIntMap) UnmarshalJSON(b []byte) error {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[string]int)
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]int)
}
if err := json.Unmarshal(b, &m.data); err != nil {
return err
}
@ -414,12 +451,11 @@ func (m *StrIntMap) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *StrIntMap) UnmarshalValue(value interface{}) (err error) {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[string]int)
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]int)
}
switch value.(type) {
case string, []byte:
return json.Unmarshal(gconv.Bytes(value), &m.data)

View File

@ -17,7 +17,7 @@ import (
)
type StrStrMap struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[string]string
}
@ -27,7 +27,7 @@ type StrStrMap struct {
func NewStrStrMap(safe ...bool) *StrStrMap {
return &StrStrMap{
data: make(map[string]string),
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
}
}
@ -36,12 +36,12 @@ func NewStrStrMap(safe ...bool) *StrStrMap {
// there might be some concurrent-safe issues when changing the map outside.
func NewStrStrMapFrom(data map[string]string, safe ...bool) *StrStrMap {
return &StrStrMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: data,
}
}
// Iterator iterates the hash map with custom callback function <f>.
// Iterator iterates the hash map readonly with custom callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *StrStrMap) Iterator(f func(k string, v string) bool) {
m.mu.RLock()
@ -97,6 +97,7 @@ func (m *StrStrMap) MapCopy() map[string]string {
}
// FilterEmpty deletes all key-value pair of which the value is empty.
// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty.
func (m *StrStrMap) FilterEmpty() {
m.mu.Lock()
for k, v := range m.data {
@ -110,6 +111,9 @@ func (m *StrStrMap) FilterEmpty() {
// Set sets key-value to the hash map.
func (m *StrStrMap) Set(key string, val string) {
m.mu.Lock()
if m.data == nil {
m.data = make(map[string]string)
}
m.data[key] = val
m.mu.Unlock()
}
@ -117,8 +121,12 @@ func (m *StrStrMap) Set(key string, val string) {
// Sets batch sets key-values to the hash map.
func (m *StrStrMap) Sets(data map[string]string) {
m.mu.Lock()
for k, v := range data {
m.data[k] = v
if m.data == nil {
m.data = data
} else {
for k, v := range data {
m.data[k] = v
}
}
m.mu.Unlock()
}
@ -127,17 +135,21 @@ func (m *StrStrMap) Sets(data map[string]string) {
// Second return parameter <found> is true if key was found, otherwise false.
func (m *StrStrMap) Search(key string) (value string, found bool) {
m.mu.RLock()
value, found = m.data[key]
if m.data != nil {
value, found = m.data[key]
}
m.mu.RUnlock()
return
}
// Get returns the value by given <key>.
func (m *StrStrMap) Get(key string) string {
func (m *StrStrMap) Get(key string) (value string) {
m.mu.RLock()
val, _ := m.data[key]
if m.data != nil {
value, _ = m.data[key]
}
m.mu.RUnlock()
return val
return
}
// Pop retrieves and deletes an item from the map.
@ -162,8 +174,10 @@ func (m *StrStrMap) Pops(size int) map[string]string {
if size == 0 {
return nil
}
index := 0
newMap := make(map[string]string, size)
var (
index = 0
newMap = make(map[string]string, size)
)
for k, v := range m.data {
delete(m.data, k)
newMap[k] = v
@ -183,6 +197,9 @@ func (m *StrStrMap) Pops(size int) map[string]string {
func (m *StrStrMap) doSetWithLockCheck(key string, value string) string {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]string)
}
if v, ok := m.data[key]; ok {
return v
}
@ -221,13 +238,14 @@ func (m *StrStrMap) GetOrSetFuncLock(key string, f func() string) string {
if v, ok := m.Search(key); !ok {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]string)
}
if v, ok = m.data[key]; ok {
return v
}
v = f()
if v != "" {
m.data[key] = v
}
m.data[key] = v
return v
} else {
return v
@ -263,6 +281,9 @@ func (m *StrStrMap) SetIfNotExistFuncLock(key string, f func() string) bool {
if !m.Contains(key) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]string)
}
if _, ok := m.data[key]; !ok {
m.data[key] = f()
}
@ -274,28 +295,34 @@ func (m *StrStrMap) SetIfNotExistFuncLock(key string, f func() string) bool {
// Removes batch deletes values of the map by keys.
func (m *StrStrMap) Removes(keys []string) {
m.mu.Lock()
for _, key := range keys {
delete(m.data, key)
if m.data != nil {
for _, key := range keys {
delete(m.data, key)
}
}
m.mu.Unlock()
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *StrStrMap) Remove(key string) string {
func (m *StrStrMap) Remove(key string) (value string) {
m.mu.Lock()
val, exists := m.data[key]
if exists {
delete(m.data, key)
if m.data != nil {
var ok bool
if value, ok = m.data[key]; ok {
delete(m.data, key)
}
}
m.mu.Unlock()
return val
return
}
// Keys returns all keys of the map as a slice.
func (m *StrStrMap) Keys() []string {
m.mu.RLock()
keys := make([]string, len(m.data))
index := 0
var (
keys = make([]string, len(m.data))
index = 0
)
for key := range m.data {
keys[index] = key
index++
@ -307,8 +334,10 @@ func (m *StrStrMap) Keys() []string {
// Values returns all values of the map as a slice.
func (m *StrStrMap) Values() []string {
m.mu.RLock()
values := make([]string, len(m.data))
index := 0
var (
values = make([]string, len(m.data))
index = 0
)
for _, value := range m.data {
values[index] = value
index++
@ -320,10 +349,13 @@ func (m *StrStrMap) Values() []string {
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *StrStrMap) Contains(key string) bool {
var ok bool
m.mu.RLock()
_, exists := m.data[key]
if m.data != nil {
_, ok = m.data[key]
}
m.mu.RUnlock()
return exists
return ok
}
// Size returns the size of the map.
@ -384,6 +416,10 @@ func (m *StrStrMap) Flip() {
func (m *StrStrMap) Merge(other *StrStrMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = other.MapCopy()
return
}
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
@ -402,12 +438,11 @@ func (m *StrStrMap) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *StrStrMap) UnmarshalJSON(b []byte) error {
if m.mu == nil {
m.mu = rwmutex.New()
m.data = make(map[string]string)
}
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[string]string)
}
if err := json.Unmarshal(b, &m.data); err != nil {
return err
}
@ -416,9 +451,6 @@ func (m *StrStrMap) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *StrStrMap) UnmarshalValue(value interface{}) (err error) {
if m.mu == nil {
m.mu = rwmutex.New()
}
m.mu.Lock()
defer m.mu.Unlock()
m.data = gconv.MapStrStr(value)

View File

@ -19,7 +19,7 @@ import (
)
type ListMap struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[interface{}]*glist.Element
list *glist.List
}
@ -35,7 +35,7 @@ type gListMapNode struct {
// which is false in default.
func NewListMap(safe ...bool) *ListMap {
return &ListMap{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[interface{}]*glist.Element),
list: glist.New(),
}
@ -55,28 +55,32 @@ func (m *ListMap) Iterator(f func(key, value interface{}) bool) {
m.IteratorAsc(f)
}
// IteratorAsc iterates the map in ascending order with given callback function <f>.
// IteratorAsc iterates the map readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *ListMap) IteratorAsc(f func(key interface{}, value interface{}) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
node := (*gListMapNode)(nil)
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
return f(node.key, node.value)
})
if m.list != nil {
node := (*gListMapNode)(nil)
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
return f(node.key, node.value)
})
}
}
// IteratorDesc iterates the map in descending order with given callback function <f>.
// IteratorDesc iterates the map readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *ListMap) IteratorDesc(f func(key interface{}, value interface{}) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
node := (*gListMapNode)(nil)
m.list.IteratorDesc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
return f(node.key, node.value)
})
if m.list != nil {
node := (*gListMapNode)(nil)
m.list.IteratorDesc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
return f(node.key, node.value)
})
}
}
// Clone returns a new link map with copy of current map data.
@ -110,13 +114,16 @@ func (m *ListMap) Replace(data map[interface{}]interface{}) {
// Map returns a copy of the underlying data of the map.
func (m *ListMap) Map() map[interface{}]interface{} {
m.mu.RLock()
node := (*gListMapNode)(nil)
data := make(map[interface{}]interface{}, len(m.data))
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
data[node.key] = node.value
return true
})
var node *gListMapNode
var data map[interface{}]interface{}
if m.list != nil {
data = make(map[interface{}]interface{}, len(m.data))
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
data[node.key] = node.value
return true
})
}
m.mu.RUnlock()
return data
}
@ -124,13 +131,16 @@ func (m *ListMap) Map() map[interface{}]interface{} {
// MapStrAny returns a copy of the underlying data of the map as map[string]interface{}.
func (m *ListMap) MapStrAny() map[string]interface{} {
m.mu.RLock()
node := (*gListMapNode)(nil)
data := make(map[string]interface{}, len(m.data))
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
data[gconv.String(node.key)] = node.value
return true
})
var node *gListMapNode
var data map[string]interface{}
if m.list != nil {
data = make(map[string]interface{}, len(m.data))
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
data[gconv.String(node.key)] = node.value
return true
})
}
m.mu.RUnlock()
return data
}
@ -138,20 +148,22 @@ func (m *ListMap) MapStrAny() map[string]interface{} {
// FilterEmpty deletes all key-value pair of which the value is empty.
func (m *ListMap) FilterEmpty() {
m.mu.Lock()
keys := make([]interface{}, 0)
node := (*gListMapNode)(nil)
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
if empty.IsEmpty(node.value) {
keys = append(keys, node.key)
}
return true
})
if len(keys) > 0 {
for _, key := range keys {
if e, ok := m.data[key]; ok {
delete(m.data, key)
m.list.Remove(e)
if m.list != nil {
keys := make([]interface{}, 0)
node := (*gListMapNode)(nil)
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gListMapNode)
if empty.IsEmpty(node.value) {
keys = append(keys, node.key)
}
return true
})
if len(keys) > 0 {
for _, key := range keys {
if e, ok := m.data[key]; ok {
delete(m.data, key)
m.list.Remove(e)
}
}
}
}
@ -161,6 +173,10 @@ func (m *ListMap) FilterEmpty() {
// Set sets key-value to the map.
func (m *ListMap) Set(key interface{}, value interface{}) {
m.mu.Lock()
if m.data == nil {
m.data = make(map[interface{}]*glist.Element)
m.list = glist.New()
}
if e, ok := m.data[key]; !ok {
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
} else {
@ -172,6 +188,10 @@ func (m *ListMap) Set(key interface{}, value interface{}) {
// Sets batch sets key-values to the map.
func (m *ListMap) Sets(data map[interface{}]interface{}) {
m.mu.Lock()
if m.data == nil {
m.data = make(map[interface{}]*glist.Element)
m.list = glist.New()
}
for key, value := range data {
if e, ok := m.data[key]; !ok {
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
@ -186,9 +206,11 @@ func (m *ListMap) Sets(data map[interface{}]interface{}) {
// Second return parameter <found> is true if key was found, otherwise false.
func (m *ListMap) Search(key interface{}) (value interface{}, found bool) {
m.mu.RLock()
if e, ok := m.data[key]; ok {
value = e.Value.(*gListMapNode).value
found = ok
if m.data != nil {
if e, ok := m.data[key]; ok {
value = e.Value.(*gListMapNode).value
found = ok
}
}
m.mu.RUnlock()
return
@ -197,8 +219,10 @@ func (m *ListMap) Search(key interface{}) (value interface{}, found bool) {
// Get returns the value by given <key>.
func (m *ListMap) Get(key interface{}) (value interface{}) {
m.mu.RLock()
if e, ok := m.data[key]; ok {
value = e.Value.(*gListMapNode).value
if m.data != nil {
if e, ok := m.data[key]; ok {
value = e.Value.(*gListMapNode).value
}
}
m.mu.RUnlock()
return
@ -255,6 +279,10 @@ func (m *ListMap) Pops(size int) map[interface{}]interface{} {
func (m *ListMap) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[interface{}]*glist.Element)
m.list = glist.New()
}
if e, ok := m.data[key]; ok {
return e.Value.(*gListMapNode).value
}
@ -302,26 +330,26 @@ func (m *ListMap) GetOrSetFuncLock(key interface{}, f func() interface{}) interf
}
}
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
// GetVar returns a Var with the value by given <key>.
// The returned Var is un-concurrent safe.
func (m *ListMap) GetVar(key interface{}) *gvar.Var {
return gvar.New(m.Get(key))
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSet returns a Var with result from GetVarOrSet.
// The returned Var is un-concurrent safe.
func (m *ListMap) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
return gvar.New(m.GetOrSet(key, value))
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSetFunc returns a Var with result from GetOrSetFunc.
// The returned Var is un-concurrent safe.
func (m *ListMap) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFunc(key, f))
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock.
// The returned Var is un-concurrent safe.
func (m *ListMap) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFuncLock(key, f))
}
@ -362,10 +390,12 @@ func (m *ListMap) SetIfNotExistFuncLock(key interface{}, f func() interface{}) b
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *ListMap) Remove(key interface{}) (value interface{}) {
m.mu.Lock()
if e, ok := m.data[key]; ok {
value = e.Value.(*gListMapNode).value
delete(m.data, key)
m.list.Remove(e)
if m.data != nil {
if e, ok := m.data[key]; ok {
value = e.Value.(*gListMapNode).value
delete(m.data, key)
m.list.Remove(e)
}
}
m.mu.Unlock()
return
@ -374,10 +404,12 @@ func (m *ListMap) Remove(key interface{}) (value interface{}) {
// Removes batch deletes values of the map by keys.
func (m *ListMap) Removes(keys []interface{}) {
m.mu.Lock()
for _, key := range keys {
if e, ok := m.data[key]; ok {
delete(m.data, key)
m.list.Remove(e)
if m.data != nil {
for _, key := range keys {
if e, ok := m.data[key]; ok {
delete(m.data, key)
m.list.Remove(e)
}
}
}
m.mu.Unlock()
@ -386,13 +418,17 @@ func (m *ListMap) Removes(keys []interface{}) {
// Keys returns all keys of the map as a slice in ascending order.
func (m *ListMap) Keys() []interface{} {
m.mu.RLock()
keys := make([]interface{}, m.list.Len())
index := 0
m.list.IteratorAsc(func(e *glist.Element) bool {
keys[index] = e.Value.(*gListMapNode).key
index++
return true
})
var (
keys = make([]interface{}, m.list.Len())
index = 0
)
if m.list != nil {
m.list.IteratorAsc(func(e *glist.Element) bool {
keys[index] = e.Value.(*gListMapNode).key
index++
return true
})
}
m.mu.RUnlock()
return keys
}
@ -400,13 +436,17 @@ func (m *ListMap) Keys() []interface{} {
// Values returns all values of the map as a slice.
func (m *ListMap) Values() []interface{} {
m.mu.RLock()
values := make([]interface{}, m.list.Len())
index := 0
m.list.IteratorAsc(func(e *glist.Element) bool {
values[index] = e.Value.(*gListMapNode).value
index++
return true
})
var (
values = make([]interface{}, m.list.Len())
index = 0
)
if m.list != nil {
m.list.IteratorAsc(func(e *glist.Element) bool {
values[index] = e.Value.(*gListMapNode).value
index++
return true
})
}
m.mu.RUnlock()
return values
}
@ -415,7 +455,9 @@ func (m *ListMap) Values() []interface{} {
// It returns true if the <key> exists, or else false.
func (m *ListMap) Contains(key interface{}) (ok bool) {
m.mu.RLock()
_, ok = m.data[key]
if m.data != nil {
_, ok = m.data[key]
}
m.mu.RUnlock()
return
}
@ -448,6 +490,10 @@ func (m *ListMap) Flip() {
func (m *ListMap) Merge(other *ListMap) {
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[interface{}]*glist.Element)
m.list = glist.New()
}
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
@ -471,13 +517,12 @@ func (m *ListMap) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (m *ListMap) UnmarshalJSON(b []byte) error {
if m.mu == nil {
m.mu = rwmutex.New()
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[interface{}]*glist.Element)
m.list = glist.New()
}
m.mu.Lock()
defer m.mu.Unlock()
var data map[string]interface{}
if err := json.Unmarshal(b, &data); err != nil {
return err
@ -494,13 +539,12 @@ func (m *ListMap) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (m *ListMap) UnmarshalValue(value interface{}) (err error) {
if m.mu == nil {
m.mu = rwmutex.New()
m.mu.Lock()
defer m.mu.Unlock()
if m.data == nil {
m.data = make(map[interface{}]*glist.Element)
m.list = glist.New()
}
m.mu.Lock()
defer m.mu.Unlock()
for k, v := range gconv.Map(value) {
if e, ok := m.data[k]; !ok {
m.data[k] = m.list.PushBack(&gListMapNode{k, v})

View File

@ -7,6 +7,7 @@
package gmap_test
import (
"github.com/gogf/gf/util/gutil"
"testing"
"github.com/gogf/gf/container/gmap"
@ -17,6 +18,55 @@ func getValue() interface{} {
return 3
}
func Test_Map_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.Map
m.Set(1, 11)
t.Assert(m.Get(1), 11)
})
gtest.C(t, func(t *gtest.T) {
var m gmap.IntAnyMap
m.Set(1, 11)
t.Assert(m.Get(1), 11)
})
gtest.C(t, func(t *gtest.T) {
var m gmap.IntIntMap
m.Set(1, 11)
t.Assert(m.Get(1), 11)
})
gtest.C(t, func(t *gtest.T) {
var m gmap.IntStrMap
m.Set(1, "11")
t.Assert(m.Get(1), "11")
})
gtest.C(t, func(t *gtest.T) {
var m gmap.StrAnyMap
m.Set("1", "11")
t.Assert(m.Get("1"), "11")
})
gtest.C(t, func(t *gtest.T) {
var m gmap.StrStrMap
m.Set("1", "11")
t.Assert(m.Get("1"), "11")
})
gtest.C(t, func(t *gtest.T) {
var m gmap.StrIntMap
m.Set("1", 11)
t.Assert(m.Get("1"), 11)
})
gtest.C(t, func(t *gtest.T) {
var m gmap.ListMap
m.Set("1", 11)
t.Assert(m.Get("1"), 11)
})
gtest.C(t, func(t *gtest.T) {
var m gmap.TreeMap
m.SetComparator(gutil.ComparatorString)
m.Set("1", 11)
t.Assert(m.Get("1"), 11)
})
}
func Test_Map_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.New()

View File

@ -1,7 +1,14 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). 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,
// You can obtain one at https://github.com/gogf/gf.
package gmap_test
import (
"fmt"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/container/gmap"
)
@ -59,13 +66,97 @@ func Example_normalBasic() {
m.Clear()
fmt.Println(m.Size())
}
func Example_keysValues() {
var m gmap.Map
m.Sets(g.MapAnyAny{
"k1": "v1",
"k2": "v2",
"k3": "v3",
"k4": "v4",
})
fmt.Println(m.Keys())
fmt.Println(m.Values())
// May Output:
// [k1 k2 k3 k4]
// [v2 v3 v4 v1]
}
func Example_flip() {
var m gmap.Map
m.Sets(g.MapAnyAny{
"k1": "v1",
"k2": "v2",
})
m.Flip()
fmt.Println(m.Map())
// May Output:
// map[v1:k1 v2:k2]
}
func Example_pop() {
var m gmap.Map
m.Sets(g.MapAnyAny{
"k1": "v1",
"k2": "v2",
"k3": "v3",
"k4": "v4",
})
fmt.Println(m.Pop())
fmt.Println(m.Pops(2))
fmt.Println(m.Size())
// May Output:
// k1 v1
// map[k2:v2 k4:v4]
// 1
}
func Example_filter() {
m1 := gmap.NewFrom(g.MapAnyAny{
"k1": "",
"k2": nil,
"k3": 0,
"k4": 1,
})
m2 := gmap.NewFrom(g.MapAnyAny{
"k1": "",
"k2": nil,
"k3": 0,
"k4": 1,
})
m1.FilterEmpty()
m2.FilterNil()
fmt.Println(m1.Map())
fmt.Println(m2.Map())
// Output:
// map[k4:1]
// map[k1: k3:0 k4:1]
}
func Example_setIfNotExist() {
var m gmap.Map
fmt.Println(m.SetIfNotExist("k1", "v1"))
fmt.Println(m.SetIfNotExist("k1", "v1"))
fmt.Println(m.Map())
// Output:
// true
// false
// map[k1:v1]
}
func Example_normalMerge() {
m1 := gmap.New()
m2 := gmap.New()
var m1, m2 gmap.Map
m1.Set("key1", "val1")
m2.Set("key2", "val2")
m1.Merge(m2)
m1.Merge(&m2)
fmt.Println(m1.Map())
// May Output:
// map[key1:val1 key2:val2]
}

View File

@ -9,16 +9,42 @@ package gmap_test
import (
"encoding/json"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/test/gtest"
"github.com/gogf/gf/util/gconv"
"testing"
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/test/gtest"
"time"
)
func anyAnyCallBack(int, interface{}) bool {
return true
func Test_AnyAnyMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.AnyAnyMap
m.Set(1, 1)
t.Assert(m.Get(1), 1)
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet(2, "2"), "2")
t.Assert(m.SetIfNotExist(2, "2"), false)
t.Assert(m.SetIfNotExist(3, 3), true)
t.Assert(m.Remove(2), "2")
t.Assert(m.Contains(2), false)
t.AssertIN(3, m.Keys())
t.AssertIN(1, m.Keys())
t.AssertIN(3, m.Values())
t.AssertIN(1, m.Values())
m.Flip()
t.Assert(m.Map(), map[interface{}]int{1: 1, 3: 3})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_AnyAnyMap_Basic(t *testing.T) {
@ -192,6 +218,18 @@ func Test_AnyAnyMap_FilterEmpty(t *testing.T) {
t.Assert(m.Get(1), nil)
t.Assert(m.Get(2), 2)
})
gtest.C(t, func(t *gtest.T) {
m := gmap.NewAnyAnyMap()
m.Set(1, 0)
m.Set("time1", time.Time{})
m.Set("time2", time.Now())
t.Assert(m.Get(1), 0)
t.Assert(m.Get("time1"), time.Time{})
m.FilterEmpty()
t.Assert(m.Get(1), nil)
t.Assert(m.Get("time1"), nil)
t.AssertNE(m.Get("time2"), nil)
})
}
func Test_AnyAnyMap_Json(t *testing.T) {

View File

@ -20,9 +20,37 @@ import (
func getAny() interface{} {
return 123
}
func intAnyCallBack(int, interface{}) bool {
return true
func Test_IntAnyMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.IntAnyMap
m.Set(1, 1)
t.Assert(m.Get(1), 1)
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet(2, "2"), "2")
t.Assert(m.SetIfNotExist(2, "2"), false)
t.Assert(m.SetIfNotExist(3, 3), true)
t.Assert(m.Remove(2), "2")
t.Assert(m.Contains(2), false)
t.AssertIN(3, m.Keys())
t.AssertIN(1, m.Keys())
t.AssertIN(3, m.Values())
t.AssertIN(1, m.Values())
m.Flip()
t.Assert(m.Map(), map[interface{}]int{1: 1, 3: 3})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_IntAnyMap_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewIntAnyMap()
@ -55,6 +83,7 @@ func Test_IntAnyMap_Basic(t *testing.T) {
t.Assert(m2.Map(), map[int]interface{}{1: 1, 2: "2"})
})
}
func Test_IntAnyMap_Set_Fun(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewIntAnyMap()

View File

@ -20,9 +20,41 @@ import (
func getInt() int {
return 123
}
func intIntCallBack(int, int) bool {
return true
}
func Test_IntIntMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.IntIntMap
m.Set(1, 1)
t.Assert(m.Get(1), 1)
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet(2, 2), 2)
t.Assert(m.SetIfNotExist(2, 2), false)
t.Assert(m.SetIfNotExist(3, 3), true)
t.Assert(m.Remove(2), 2)
t.Assert(m.Contains(2), false)
t.AssertIN(3, m.Keys())
t.AssertIN(1, m.Keys())
t.AssertIN(3, m.Values())
t.AssertIN(1, m.Values())
m.Flip()
t.Assert(m.Map(), map[int]int{1: 1, 3: 3})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_IntIntMap_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewIntIntMap()
@ -55,6 +87,7 @@ func Test_IntIntMap_Basic(t *testing.T) {
t.Assert(m2.Map(), map[int]int{1: 1, 2: 2})
})
}
func Test_IntIntMap_Set_Fun(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewIntIntMap()

View File

@ -20,9 +20,40 @@ import (
func getStr() string {
return "z"
}
func intStrCallBack(int, string) bool {
return true
func Test_IntStrMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.IntStrMap
m.Set(1, "a")
t.Assert(m.Get(1), "a")
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet(2, "b"), "b")
t.Assert(m.SetIfNotExist(2, "b"), false)
t.Assert(m.SetIfNotExist(3, "c"), true)
t.Assert(m.Remove(2), "b")
t.Assert(m.Contains(2), false)
t.AssertIN(3, m.Keys())
t.AssertIN(1, m.Keys())
t.AssertIN("a", m.Values())
t.AssertIN("c", m.Values())
m_f := gmap.NewIntStrMap()
m_f.Set(1, "2")
m_f.Flip()
t.Assert(m_f.Map(), map[int]string{2: "1"})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_IntStrMap_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewIntStrMap()
@ -60,6 +91,7 @@ func Test_IntStrMap_Basic(t *testing.T) {
t.Assert(m2.Map(), map[int]string{1: "a", 2: "b"})
})
}
func Test_IntStrMap_Set_Fun(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewIntStrMap()

View File

@ -17,6 +17,38 @@ import (
"github.com/gogf/gf/test/gtest"
)
func Test_ListMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.ListMap
m.Set("key1", "val1")
t.Assert(m.Keys(), []interface{}{"key1"})
t.Assert(m.Get("key1"), "val1")
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet("key2", "val2"), "val2")
t.Assert(m.SetIfNotExist("key2", "val2"), false)
t.Assert(m.SetIfNotExist("key3", "val3"), true)
t.Assert(m.Remove("key2"), "val2")
t.Assert(m.Contains("key2"), false)
t.AssertIN("key3", m.Keys())
t.AssertIN("key1", m.Keys())
t.AssertIN("val3", m.Values())
t.AssertIN("val1", m.Values())
m.Flip()
t.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_ListMap_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewListMap()
@ -51,6 +83,7 @@ func Test_ListMap_Basic(t *testing.T) {
t.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
})
}
func Test_ListMap_Set_Fun(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewListMap()

View File

@ -17,9 +17,37 @@ import (
"github.com/gogf/gf/test/gtest"
)
func stringAnyCallBack(string, interface{}) bool {
return true
func Test_StrAnyMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.StrAnyMap
m.Set("a", 1)
t.Assert(m.Get("a"), 1)
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet("b", "2"), "2")
t.Assert(m.SetIfNotExist("b", "2"), false)
t.Assert(m.SetIfNotExist("c", 3), true)
t.Assert(m.Remove("b"), "2")
t.Assert(m.Contains("b"), false)
t.AssertIN("c", m.Keys())
t.AssertIN("a", m.Keys())
t.AssertIN(3, m.Values())
t.AssertIN(1, m.Values())
m.Flip()
t.Assert(m.Map(), map[string]interface{}{"1": "a", "3": "c"})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_StrAnyMap_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewStrAnyMap()
@ -53,6 +81,7 @@ func Test_StrAnyMap_Basic(t *testing.T) {
t.Assert(m2.Map(), map[string]interface{}{"a": 1, "b": "2"})
})
}
func Test_StrAnyMap_Set_Fun(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewStrAnyMap()

View File

@ -17,9 +17,39 @@ import (
"github.com/gogf/gf/test/gtest"
)
func stringIntCallBack(string, int) bool {
return true
func Test_StrIntMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.StrIntMap
m.Set("a", 1)
t.Assert(m.Get("a"), 1)
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet("b", 2), 2)
t.Assert(m.SetIfNotExist("b", 2), false)
t.Assert(m.SetIfNotExist("c", 3), true)
t.Assert(m.Remove("b"), 2)
t.Assert(m.Contains("b"), false)
t.AssertIN("c", m.Keys())
t.AssertIN("a", m.Keys())
t.AssertIN(3, m.Values())
t.AssertIN(1, m.Values())
m_f := gmap.NewStrIntMap()
m_f.Set("1", 2)
m_f.Flip()
t.Assert(m_f.Map(), map[string]int{"2": 1})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_StrIntMap_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewStrIntMap()
@ -55,6 +85,7 @@ func Test_StrIntMap_Basic(t *testing.T) {
t.Assert(m2.Map(), map[string]int{"a": 1, "b": 2})
})
}
func Test_StrIntMap_Set_Fun(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewStrIntMap()

View File

@ -17,9 +17,38 @@ import (
"github.com/gogf/gf/test/gtest"
)
func stringStrCallBack(string, string) bool {
return true
func Test_StrStrMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.StrStrMap
m.Set("a", "a")
t.Assert(m.Get("a"), "a")
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet("b", "b"), "b")
t.Assert(m.SetIfNotExist("b", "b"), false)
t.Assert(m.SetIfNotExist("c", "c"), true)
t.Assert(m.Remove("b"), "b")
t.Assert(m.Contains("b"), false)
t.AssertIN("c", m.Keys())
t.AssertIN("a", m.Keys())
t.AssertIN("a", m.Values())
t.AssertIN("c", m.Values())
m.Flip()
t.Assert(m.Map(), map[string]string{"a": "a", "c": "c"})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_StrStrMap_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewStrStrMap()
@ -54,6 +83,7 @@ func Test_StrStrMap_Basic(t *testing.T) {
t.Assert(m2.Map(), map[string]string{"a": "a", "b": "b"})
})
}
func Test_StrStrMap_Set_Fun(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewStrStrMap()

View File

@ -17,6 +17,39 @@ import (
"github.com/gogf/gf/util/gutil"
)
func Test_TreeMap_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var m gmap.TreeMap
m.SetComparator(gutil.ComparatorString)
m.Set("key1", "val1")
t.Assert(m.Keys(), []interface{}{"key1"})
t.Assert(m.Get("key1"), "val1")
t.Assert(m.Size(), 1)
t.Assert(m.IsEmpty(), false)
t.Assert(m.GetOrSet("key2", "val2"), "val2")
t.Assert(m.SetIfNotExist("key2", "val2"), false)
t.Assert(m.SetIfNotExist("key3", "val3"), true)
t.Assert(m.Remove("key2"), "val2")
t.Assert(m.Contains("key2"), false)
t.AssertIN("key3", m.Keys())
t.AssertIN("key1", m.Keys())
t.AssertIN("val3", m.Values())
t.AssertIN("val1", m.Values())
m.Flip()
t.Assert(m.Map(), map[interface{}]interface{}{"val3": "key3", "val1": "key1"})
m.Clear()
t.Assert(m.Size(), 0)
t.Assert(m.IsEmpty(), true)
})
}
func Test_TreeMap_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewTreeMap(gutil.ComparatorString)
@ -51,6 +84,7 @@ func Test_TreeMap_Basic(t *testing.T) {
t.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
})
}
func Test_TreeMap_Set_Fun(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := gmap.NewTreeMap(gutil.ComparatorString)

View File

@ -14,6 +14,7 @@ import (
"github.com/gogf/gf/internal/rwmutex"
)
// Ring is a struct of ring structure.
type Ring struct {
mu *rwmutex.RWMutex
ring *ring.Ring // Underlying ring.
@ -22,6 +23,9 @@ type Ring struct {
dirty *gtype.Bool // Dirty, which means the len and cap should be recalculated. It's marked dirty when the size of ring changes.
}
// New creates and returns a Ring structure of <cap> elements.
// The optional parameter <safe> specifies whether using this structure in concurrent safety,
// which is false in default.
func New(cap int, safe ...bool) *Ring {
return &Ring{
mu: rwmutex.New(safe...),

View File

@ -1,3 +1,9 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). 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 gring_test
import (

View File

@ -16,7 +16,7 @@ import (
)
type Set struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[interface{}]struct{}
}
@ -31,7 +31,7 @@ func New(safe ...bool) *Set {
func NewSet(safe ...bool) *Set {
return &Set{
data: make(map[interface{}]struct{}),
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
}
}
@ -44,13 +44,13 @@ func NewFrom(items interface{}, safe ...bool) *Set {
}
return &Set{
data: m,
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
}
}
// Iterator iterates the set with given callback function <f>,
// Iterator iterates the set readonly with given callback function <f>,
// if <f> returns true then continue iterating; or false to stop.
func (set *Set) Iterator(f func(v interface{}) bool) *Set {
func (set *Set) Iterator(f func(v interface{}) bool) {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.data {
@ -58,77 +58,113 @@ func (set *Set) Iterator(f func(v interface{}) bool) *Set {
break
}
}
return set
}
// Add adds one or multiple items to the set.
func (set *Set) Add(item ...interface{}) *Set {
func (set *Set) Add(items ...interface{}) {
set.mu.Lock()
for _, v := range item {
if set.data == nil {
set.data = make(map[interface{}]struct{})
}
for _, v := range items {
set.data[v] = struct{}{}
}
set.mu.Unlock()
return set
}
// AddIfNotExistFunc adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
func (set *Set) AddIfNotExistFunc(item interface{}, f func() interface{}) *Set {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f())
// AddIfNotExist checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set,
// or else it does nothing and returns false.
//
// Note that, if <item> is nil, it does nothing and returns false.
func (set *Set) AddIfNotExist(item interface{}) bool {
if item == nil {
return false
}
return set
}
// AddIfNotExistFuncLock adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
//
// Note that the callback function <f> is executed in the mutex.Lock of the set.
func (set *Set) AddIfNotExistFuncLock(item interface{}, f func() interface{}) *Set {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f)
}
return set
}
// doAddWithLockCheck checks whether item exists with mutex.Lock,
// if not exists, it adds item to the set or else just returns the existing value.
//
// If <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the set,
// and its return value will be added to the set.
//
// It returns item successfully added..
func (set *Set) doAddWithLockCheck(item interface{}, value interface{}) interface{} {
set.mu.Lock()
defer set.mu.Unlock()
if _, ok := set.data[item]; !ok && value != nil {
if f, ok := value.(func() interface{}); ok {
item = f()
} else {
item = value
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[interface{}]struct{})
}
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
if item != nil {
set.data[item] = struct{}{}
return false
}
// AddIfNotExistFunc checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set and
// function <f> returns true, or else it does nothing and returns false.
//
// Note that, if <item> is nil, it does nothing and returns false. The function <f>
// is executed without writing lock.
func (set *Set) AddIfNotExistFunc(item interface{}, f func() bool) bool {
if item == nil {
return false
}
return item
if !set.Contains(item) {
if f() {
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[interface{}]struct{})
}
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
}
return false
}
// AddIfNotExistFunc checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set and
// function <f> returns true, or else it does nothing and returns false.
//
// Note that, if <item> is nil, it does nothing and returns false. The function <f>
// is executed within writing lock.
func (set *Set) AddIfNotExistFuncLock(item interface{}, f func() bool) bool {
if item == nil {
return false
}
if !set.Contains(item) {
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[interface{}]struct{})
}
if f() {
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
}
return false
}
// Contains checks whether the set contains <item>.
func (set *Set) Contains(item interface{}) bool {
var ok bool
set.mu.RLock()
_, exists := set.data[item]
if set.data != nil {
_, ok = set.data[item]
}
set.mu.RUnlock()
return exists
return ok
}
// Remove deletes <item> from set.
func (set *Set) Remove(item interface{}) *Set {
func (set *Set) Remove(item interface{}) {
set.mu.Lock()
delete(set.data, item)
if set.data != nil {
delete(set.data, item)
}
set.mu.Unlock()
return set
}
// Size returns the size of the set.
@ -140,18 +176,19 @@ func (set *Set) Size() int {
}
// Clear deletes all items of the set.
func (set *Set) Clear() *Set {
func (set *Set) Clear() {
set.mu.Lock()
set.data = make(map[interface{}]struct{})
set.mu.Unlock()
return set
}
// Slice returns the a of items of the set as slice.
func (set *Set) Slice() []interface{} {
set.mu.RLock()
i := 0
ret := make([]interface{}, len(set.data))
var (
i = 0
ret = make([]interface{}, len(set.data))
)
for item := range set.data {
ret[i] = item
i++
@ -164,9 +201,14 @@ func (set *Set) Slice() []interface{} {
func (set *Set) Join(glue string) string {
set.mu.RLock()
defer set.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
l := len(set.data)
i := 0
if len(set.data) == 0 {
return ""
}
var (
l = len(set.data)
i = 0
buffer = bytes.NewBuffer(nil)
)
for k, _ := range set.data {
buffer.WriteString(gconv.String(k))
if i != l-1 {
@ -181,11 +223,13 @@ func (set *Set) Join(glue string) string {
func (set *Set) String() string {
set.mu.RLock()
defer set.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
var (
s = ""
l = len(set.data)
i = 0
buffer = bytes.NewBuffer(nil)
)
buffer.WriteByte('[')
s := ""
l := len(set.data)
i := 0
for k, _ := range set.data {
s = gconv.String(k)
if gstr.IsNumeric(s) {
@ -256,7 +300,7 @@ func (set *Set) IsSubsetOf(other *Set) bool {
// Union returns a new set which is the union of <set> and <others>.
// Which means, all the items in <newSet> are in <set> or in <others>.
func (set *Set) Union(others ...*Set) (newSet *Set) {
newSet = NewSet(true)
newSet = NewSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -282,7 +326,7 @@ func (set *Set) Union(others ...*Set) (newSet *Set) {
// Diff returns a new set which is the difference set from <set> to <others>.
// Which means, all the items in <newSet> are in <set> but not in <others>.
func (set *Set) Diff(others ...*Set) (newSet *Set) {
newSet = NewSet(true)
newSet = NewSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -303,7 +347,7 @@ func (set *Set) Diff(others ...*Set) (newSet *Set) {
// Intersect returns a new set which is the intersection from <set> to <others>.
// Which means, all the items in <newSet> are in <set> and also in <others>.
func (set *Set) Intersect(others ...*Set) (newSet *Set) {
newSet = NewSet(true)
newSet = NewSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -328,7 +372,7 @@ func (set *Set) Intersect(others ...*Set) (newSet *Set) {
// It returns the difference between <full> and <set>
// if the given set <full> is not the full set of <set>.
func (set *Set) Complement(full *Set) (newSet *Set) {
newSet = NewSet(true)
newSet = NewSet()
set.mu.RLock()
defer set.mu.RUnlock()
if set != full {
@ -408,6 +452,18 @@ func (set *Set) Pops(size int) []interface{} {
return array
}
// Walk applies a user supplied function <f> to every item of set.
func (set *Set) Walk(f func(item interface{}) interface{}) *Set {
set.mu.Lock()
defer set.mu.Unlock()
m := make(map[interface{}]struct{}, len(set.data))
for k, v := range set.data {
m[f(k)] = v
}
set.data = m
return set
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (set *Set) MarshalJSON() ([]byte, error) {
return json.Marshal(set.Slice())
@ -415,12 +471,11 @@ func (set *Set) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (set *Set) UnmarshalJSON(b []byte) error {
if set.mu == nil {
set.mu = rwmutex.New()
set.data = make(map[interface{}]struct{})
}
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[interface{}]struct{})
}
var array []interface{}
if err := json.Unmarshal(b, &array); err != nil {
return err
@ -433,12 +488,11 @@ func (set *Set) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for set.
func (set *Set) UnmarshalValue(value interface{}) (err error) {
if set.mu == nil {
set.mu = rwmutex.New()
set.data = make(map[interface{}]struct{})
}
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[interface{}]struct{})
}
var array []interface{}
switch value.(type) {
case string, []byte:

View File

@ -15,7 +15,7 @@ import (
)
type IntSet struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[int]struct{}
}
@ -24,7 +24,7 @@ type IntSet struct {
// which is false in default.
func NewIntSet(safe ...bool) *IntSet {
return &IntSet{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[int]struct{}),
}
}
@ -36,14 +36,14 @@ func NewIntSetFrom(items []int, safe ...bool) *IntSet {
m[v] = struct{}{}
}
return &IntSet{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: m,
}
}
// Iterator iterates the set with given callback function <f>,
// Iterator iterates the set readonly with given callback function <f>,
// if <f> returns true then continue iterating; or false to stop.
func (set *IntSet) Iterator(f func(v int) bool) *IntSet {
func (set *IntSet) Iterator(f func(v int) bool) {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.data {
@ -51,75 +51,102 @@ func (set *IntSet) Iterator(f func(v int) bool) *IntSet {
break
}
}
return set
}
// Add adds one or multiple items to the set.
func (set *IntSet) Add(item ...int) *IntSet {
func (set *IntSet) Add(item ...int) {
set.mu.Lock()
if set.data == nil {
set.data = make(map[int]struct{})
}
for _, v := range item {
set.data[v] = struct{}{}
}
set.mu.Unlock()
return set
}
// AddIfNotExistFunc adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
func (set *IntSet) AddIfNotExistFunc(item int, f func() int) *IntSet {
// AddIfNotExist checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set,
// or else it does nothing and returns false.
//
// Note that, if <item> is nil, it does nothing and returns false.
func (set *IntSet) AddIfNotExist(item int) bool {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f())
}
return set
}
// AddIfNotExistFuncLock adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
//
// Note that the callback function <f> is executed in the mutex.Lock of the set.
func (set *IntSet) AddIfNotExistFuncLock(item int, f func() int) *IntSet {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f)
}
return set
}
// doAddWithLockCheck checks whether item exists with mutex.Lock,
// if not exists, it adds item to the set or else just returns the existing value.
//
// If <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the set,
// and its return value will be added to the set.
//
// It returns item successfully added..
func (set *IntSet) doAddWithLockCheck(item int, value interface{}) int {
set.mu.Lock()
defer set.mu.Unlock()
if _, ok := set.data[item]; !ok && value != nil {
if f, ok := value.(func() int); ok {
item = f()
} else {
item = value.(int)
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[int]struct{})
}
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
set.data[item] = struct{}{}
return item
return false
}
// AddIfNotExistFunc checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set and
// function <f> returns true, or else it does nothing and returns false.
//
// Note that, the function <f> is executed without writing lock.
func (set *IntSet) AddIfNotExistFunc(item int, f func() bool) bool {
if !set.Contains(item) {
if f() {
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[int]struct{})
}
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
}
return false
}
// AddIfNotExistFunc checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set and
// function <f> returns true, or else it does nothing and returns false.
//
// Note that, the function <f> is executed without writing lock.
func (set *IntSet) AddIfNotExistFuncLock(item int, f func() bool) bool {
if !set.Contains(item) {
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[int]struct{})
}
if f() {
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
}
return false
}
// Contains checks whether the set contains <item>.
func (set *IntSet) Contains(item int) bool {
var ok bool
set.mu.RLock()
_, exists := set.data[item]
if set.data != nil {
_, ok = set.data[item]
}
set.mu.RUnlock()
return exists
return ok
}
// Remove deletes <item> from set.
func (set *IntSet) Remove(item int) *IntSet {
func (set *IntSet) Remove(item int) {
set.mu.Lock()
delete(set.data, item)
if set.data != nil {
delete(set.data, item)
}
set.mu.Unlock()
return set
}
// Size returns the size of the set.
@ -131,18 +158,19 @@ func (set *IntSet) Size() int {
}
// Clear deletes all items of the set.
func (set *IntSet) Clear() *IntSet {
func (set *IntSet) Clear() {
set.mu.Lock()
set.data = make(map[int]struct{})
set.mu.Unlock()
return set
}
// Slice returns the a of items of the set as slice.
func (set *IntSet) Slice() []int {
set.mu.RLock()
ret := make([]int, len(set.data))
i := 0
var (
i = 0
ret = make([]int, len(set.data))
)
for k, _ := range set.data {
ret[i] = k
i++
@ -155,9 +183,14 @@ func (set *IntSet) Slice() []int {
func (set *IntSet) Join(glue string) string {
set.mu.RLock()
defer set.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
l := len(set.data)
i := 0
if len(set.data) == 0 {
return ""
}
var (
l = len(set.data)
i = 0
buffer = bytes.NewBuffer(nil)
)
for k, _ := range set.data {
buffer.WriteString(gconv.String(k))
if i != l-1 {
@ -227,7 +260,7 @@ func (set *IntSet) IsSubsetOf(other *IntSet) bool {
// Union returns a new set which is the union of <set> and <other>.
// Which means, all the items in <newSet> are in <set> or in <other>.
func (set *IntSet) Union(others ...*IntSet) (newSet *IntSet) {
newSet = NewIntSet(true)
newSet = NewIntSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -253,7 +286,7 @@ func (set *IntSet) Union(others ...*IntSet) (newSet *IntSet) {
// Diff returns a new set which is the difference set from <set> to <other>.
// Which means, all the items in <newSet> are in <set> but not in <other>.
func (set *IntSet) Diff(others ...*IntSet) (newSet *IntSet) {
newSet = NewIntSet(true)
newSet = NewIntSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -274,7 +307,7 @@ func (set *IntSet) Diff(others ...*IntSet) (newSet *IntSet) {
// Intersect returns a new set which is the intersection from <set> to <other>.
// Which means, all the items in <newSet> are in <set> and also in <other>.
func (set *IntSet) Intersect(others ...*IntSet) (newSet *IntSet) {
newSet = NewIntSet(true)
newSet = NewIntSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -299,7 +332,7 @@ func (set *IntSet) Intersect(others ...*IntSet) (newSet *IntSet) {
// It returns the difference between <full> and <set>
// if the given set <full> is not the full set of <set>.
func (set *IntSet) Complement(full *IntSet) (newSet *IntSet) {
newSet = NewIntSet(true)
newSet = NewIntSet()
set.mu.RLock()
defer set.mu.RUnlock()
if set != full {
@ -379,6 +412,18 @@ func (set *IntSet) Pops(size int) []int {
return array
}
// Walk applies a user supplied function <f> to every item of set.
func (set *IntSet) Walk(f func(item int) int) *IntSet {
set.mu.Lock()
defer set.mu.Unlock()
m := make(map[int]struct{}, len(set.data))
for k, v := range set.data {
m[f(k)] = v
}
set.data = m
return set
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (set *IntSet) MarshalJSON() ([]byte, error) {
return json.Marshal(set.Slice())
@ -386,12 +431,11 @@ func (set *IntSet) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (set *IntSet) UnmarshalJSON(b []byte) error {
if set.mu == nil {
set.mu = rwmutex.New()
set.data = make(map[int]struct{})
}
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[int]struct{})
}
var array []int
if err := json.Unmarshal(b, &array); err != nil {
return err
@ -404,12 +448,11 @@ func (set *IntSet) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for set.
func (set *IntSet) UnmarshalValue(value interface{}) (err error) {
if set.mu == nil {
set.mu = rwmutex.New()
set.data = make(map[int]struct{})
}
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[int]struct{})
}
var array []int
switch value.(type) {
case string, []byte:

View File

@ -13,10 +13,11 @@ import (
"github.com/gogf/gf/internal/rwmutex"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"strings"
)
type StrSet struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
data map[string]struct{}
}
@ -25,7 +26,7 @@ type StrSet struct {
// which is false in default.
func NewStrSet(safe ...bool) *StrSet {
return &StrSet{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: make(map[string]struct{}),
}
}
@ -37,14 +38,14 @@ func NewStrSetFrom(items []string, safe ...bool) *StrSet {
m[v] = struct{}{}
}
return &StrSet{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
data: m,
}
}
// Iterator iterates the set with given callback function <f>,
// Iterator iterates the set readonly with given callback function <f>,
// if <f> returns true then continue iterating; or false to stop.
func (set *StrSet) Iterator(f func(v string) bool) *StrSet {
func (set *StrSet) Iterator(f func(v string) bool) {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.data {
@ -52,77 +53,113 @@ func (set *StrSet) Iterator(f func(v string) bool) *StrSet {
break
}
}
return set
}
// Add adds one or multiple items to the set.
func (set *StrSet) Add(item ...string) *StrSet {
func (set *StrSet) Add(item ...string) {
set.mu.Lock()
if set.data == nil {
set.data = make(map[string]struct{})
}
for _, v := range item {
set.data[v] = struct{}{}
}
set.mu.Unlock()
return set
}
// AddIfNotExistFunc adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
func (set *StrSet) AddIfNotExistFunc(item string, f func() string) *StrSet {
// AddIfNotExist checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set,
// or else it does nothing and returns false.
func (set *StrSet) AddIfNotExist(item string) bool {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f())
}
return set
}
// AddIfNotExistFuncLock adds the returned value of callback function <f> to the set
// if <item> does not exit in the set.
//
// Note that the callback function <f> is executed in the mutex.Lock of the set.
func (set *StrSet) AddIfNotExistFuncLock(item string, f func() string) *StrSet {
if !set.Contains(item) {
set.doAddWithLockCheck(item, f)
}
return set
}
// doAddWithLockCheck checks whether item exists with mutex.Lock,
// if not exists, it adds item to the set or else just returns the existing value.
//
// If <value> is type of <func() interface {}>,
// it will be executed with mutex.Lock of the set,
// and its return value will be added to the set.
//
// It returns item successfully added..
func (set *StrSet) doAddWithLockCheck(item string, value interface{}) string {
set.mu.Lock()
defer set.mu.Unlock()
if _, ok := set.data[item]; !ok && value != nil {
if f, ok := value.(func() string); ok {
item = f()
} else {
item = value.(string)
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[string]struct{})
}
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
if item != "" {
set.data[item] = struct{}{}
return false
}
// AddIfNotExistFunc checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set and
// function <f> returns true, or else it does nothing and returns false.
//
// Note that, the function <f> is executed without writing lock.
func (set *StrSet) AddIfNotExistFunc(item string, f func() bool) bool {
if !set.Contains(item) {
if f() {
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[string]struct{})
}
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
}
return item
return false
}
// AddIfNotExistFunc checks whether item exists in the set,
// it adds the item to set and returns true if it does not exists in the set and
// function <f> returns true, or else it does nothing and returns false.
//
// Note that, the function <f> is executed without writing lock.
func (set *StrSet) AddIfNotExistFuncLock(item string, f func() bool) bool {
if !set.Contains(item) {
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[string]struct{})
}
if f() {
if _, ok := set.data[item]; !ok {
set.data[item] = struct{}{}
return true
}
}
}
return false
}
// Contains checks whether the set contains <item>.
func (set *StrSet) Contains(item string) bool {
var ok bool
set.mu.RLock()
_, exists := set.data[item]
if set.data != nil {
_, ok = set.data[item]
}
set.mu.RUnlock()
return exists
return ok
}
// ContainsI checks whether a value exists in the set with case-insensitively.
// Note that it internally iterates the whole set to do the comparison with case-insensitively.
func (set *StrSet) ContainsI(item string) bool {
set.mu.RLock()
defer set.mu.RUnlock()
for k, _ := range set.data {
if strings.EqualFold(k, item) {
return true
}
}
return false
}
// Remove deletes <item> from set.
func (set *StrSet) Remove(item string) *StrSet {
func (set *StrSet) Remove(item string) {
set.mu.Lock()
delete(set.data, item)
if set.data != nil {
delete(set.data, item)
}
set.mu.Unlock()
return set
}
// Size returns the size of the set.
@ -134,18 +171,19 @@ func (set *StrSet) Size() int {
}
// Clear deletes all items of the set.
func (set *StrSet) Clear() *StrSet {
func (set *StrSet) Clear() {
set.mu.Lock()
set.data = make(map[string]struct{})
set.mu.Unlock()
return set
}
// Slice returns the a of items of the set as slice.
func (set *StrSet) Slice() []string {
set.mu.RLock()
ret := make([]string, len(set.data))
i := 0
var (
i = 0
ret = make([]string, len(set.data))
)
for item := range set.data {
ret[i] = item
i++
@ -159,9 +197,14 @@ func (set *StrSet) Slice() []string {
func (set *StrSet) Join(glue string) string {
set.mu.RLock()
defer set.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
l := len(set.data)
i := 0
if len(set.data) == 0 {
return ""
}
var (
l = len(set.data)
i = 0
buffer = bytes.NewBuffer(nil)
)
for k, _ := range set.data {
buffer.WriteString(k)
if i != l-1 {
@ -176,9 +219,11 @@ func (set *StrSet) Join(glue string) string {
func (set *StrSet) String() string {
set.mu.RLock()
defer set.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
l := len(set.data)
i := 0
var (
l = len(set.data)
i = 0
buffer = bytes.NewBuffer(nil)
)
for k, _ := range set.data {
buffer.WriteString(`"` + gstr.QuoteMeta(k, `"\`) + `"`)
if i != l-1 {
@ -243,7 +288,7 @@ func (set *StrSet) IsSubsetOf(other *StrSet) bool {
// Union returns a new set which is the union of <set> and <other>.
// Which means, all the items in <newSet> are in <set> or in <other>.
func (set *StrSet) Union(others ...*StrSet) (newSet *StrSet) {
newSet = NewStrSet(true)
newSet = NewStrSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -269,7 +314,7 @@ func (set *StrSet) Union(others ...*StrSet) (newSet *StrSet) {
// Diff returns a new set which is the difference set from <set> to <other>.
// Which means, all the items in <newSet> are in <set> but not in <other>.
func (set *StrSet) Diff(others ...*StrSet) (newSet *StrSet) {
newSet = NewStrSet(true)
newSet = NewStrSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -290,7 +335,7 @@ func (set *StrSet) Diff(others ...*StrSet) (newSet *StrSet) {
// Intersect returns a new set which is the intersection from <set> to <other>.
// Which means, all the items in <newSet> are in <set> and also in <other>.
func (set *StrSet) Intersect(others ...*StrSet) (newSet *StrSet) {
newSet = NewStrSet(true)
newSet = NewStrSet()
set.mu.RLock()
defer set.mu.RUnlock()
for _, other := range others {
@ -315,7 +360,7 @@ func (set *StrSet) Intersect(others ...*StrSet) (newSet *StrSet) {
// It returns the difference between <full> and <set>
// if the given set <full> is not the full set of <set>.
func (set *StrSet) Complement(full *StrSet) (newSet *StrSet) {
newSet = NewStrSet(true)
newSet = NewStrSet()
set.mu.RLock()
defer set.mu.RUnlock()
if set != full {
@ -395,6 +440,18 @@ func (set *StrSet) Pops(size int) []string {
return array
}
// Walk applies a user supplied function <f> to every item of set.
func (set *StrSet) Walk(f func(item string) string) *StrSet {
set.mu.Lock()
defer set.mu.Unlock()
m := make(map[string]struct{}, len(set.data))
for k, v := range set.data {
m[f(k)] = v
}
set.data = m
return set
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (set *StrSet) MarshalJSON() ([]byte, error) {
return json.Marshal(set.Slice())
@ -402,12 +459,11 @@ func (set *StrSet) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (set *StrSet) UnmarshalJSON(b []byte) error {
if set.mu == nil {
set.mu = rwmutex.New()
set.data = make(map[string]struct{})
}
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[string]struct{})
}
var array []string
if err := json.Unmarshal(b, &array); err != nil {
return err
@ -420,12 +476,11 @@ func (set *StrSet) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for set.
func (set *StrSet) UnmarshalValue(value interface{}) (err error) {
if set.mu == nil {
set.mu = rwmutex.New()
set.data = make(map[string]struct{})
}
set.mu.Lock()
defer set.mu.Unlock()
if set.data == nil {
set.data = make(map[string]struct{})
}
var array []string
switch value.(type) {
case string, []byte:

View File

@ -0,0 +1,119 @@
// Copyright 2020 gf Author(https://github.com/gogf/gf). 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,
// You can obtain one at https://github.com/gogf/gf.
package gset_test
import (
"fmt"
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/frame/g"
)
func Example_intersectDiffUnionComplement() {
s1 := gset.NewFrom(g.Slice{1, 2, 3})
s2 := gset.NewFrom(g.Slice{4, 5, 6})
s3 := gset.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7})
fmt.Println(s3.Intersect(s1).Slice())
fmt.Println(s3.Diff(s1).Slice())
fmt.Println(s1.Union(s2).Slice())
fmt.Println(s1.Complement(s3).Slice())
// May Output:
// [2 3 1]
// [5 6 7 4]
// [6 1 2 3 4 5]
// [4 5 6 7]
}
func Example_isSubsetOf() {
var s1, s2 gset.Set
s1.Add(g.Slice{1, 2, 3}...)
s2.Add(g.Slice{2, 3}...)
fmt.Println(s1.IsSubsetOf(&s2))
fmt.Println(s2.IsSubsetOf(&s1))
// Output:
// false
// true
}
func Example_addIfNotExist() {
var set gset.Set
fmt.Println(set.AddIfNotExist(1))
fmt.Println(set.AddIfNotExist(1))
fmt.Println(set.Slice())
// Output:
// true
// false
// [1]
}
func Example_pop() {
var set gset.Set
set.Add(1, 2, 3, 4)
fmt.Println(set.Pop())
fmt.Println(set.Pops(2))
fmt.Println(set.Size())
// May Output:
// 1
// [2 3]
// 1
}
func Example_join() {
var set gset.Set
set.Add("a", "b", "c", "d")
fmt.Println(set.Join(","))
// May Output:
// a,b,c,d
}
func Example_contains() {
var set gset.StrSet
set.Add("a")
fmt.Println(set.Contains("a"))
fmt.Println(set.Contains("A"))
fmt.Println(set.ContainsI("A"))
// Output:
// true
// false
// true
}
func Example_Contains() {
var set gset.StrSet
set.Add("a")
fmt.Println(set.Contains("a"))
fmt.Println(set.Contains("A"))
fmt.Println(set.ContainsI("A"))
// Output:
// true
// false
// true
}
func Example_walk() {
var (
set gset.StrSet
names = g.SliceStr{"user", "user_detail"}
prefix = "gf_"
)
set.Add(names...)
// Add prefix for given table names.
set.Walk(func(item string) string {
return prefix + item
})
fmt.Println(set.Slice())
// May Output:
// [gf_user gf_user_detail]
}

View File

@ -13,6 +13,8 @@ import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gconv"
"strings"
"sync"
"time"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/container/gset"
@ -21,10 +23,30 @@ import (
"testing"
)
func TestSet_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var s gset.Set
s.Add(1, 1, 2)
s.Add([]interface{}{3, 4}...)
t.Assert(s.Size(), 4)
t.AssertIN(1, s.Slice())
t.AssertIN(2, s.Slice())
t.AssertIN(3, s.Slice())
t.AssertIN(4, s.Slice())
t.AssertNI(0, s.Slice())
t.Assert(s.Contains(4), true)
t.Assert(s.Contains(5), false)
s.Remove(1)
t.Assert(s.Size(), 3)
s.Clear()
t.Assert(s.Size(), 0)
})
}
func TestSet_New(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.New()
s.Add(1).Add(1).Add(2)
s.Add(1, 1, 2)
s.Add([]interface{}{3, 4}...)
t.Assert(s.Size(), 4)
t.AssertIN(1, s.Slice())
@ -44,7 +66,7 @@ func TestSet_New(t *testing.T) {
func TestSet_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewSet()
s.Add(1).Add(1).Add(2)
s.Add(1, 1, 2)
s.Add([]interface{}{3, 4}...)
t.Assert(s.Size(), 4)
t.AssertIN(1, s.Slice())
@ -64,7 +86,7 @@ func TestSet_Basic(t *testing.T) {
func TestSet_Iterator(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewSet()
s.Add(1).Add(2).Add(3)
s.Add(1, 2, 3)
t.Assert(s.Size(), 3)
a1 := garray.New(true)
@ -85,7 +107,7 @@ func TestSet_Iterator(t *testing.T) {
func TestSet_LockFunc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewSet()
s.Add(1).Add(2).Add(3)
s.Add(1, 2, 3)
t.Assert(s.Size(), 3)
s.LockFunc(func(m map[interface{}]struct{}) {
delete(m, 1)
@ -105,9 +127,9 @@ func TestSet_Equal(t *testing.T) {
s1 := gset.NewSet()
s2 := gset.NewSet()
s3 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
s1.Add(1, 2, 3)
s2.Add(1, 2, 3)
s3.Add(1, 2, 3, 4)
t.Assert(s1.Equal(s2), true)
t.Assert(s1.Equal(s3), false)
})
@ -118,9 +140,9 @@ func TestSet_IsSubsetOf(t *testing.T) {
s1 := gset.NewSet()
s2 := gset.NewSet()
s3 := gset.NewSet()
s1.Add(1).Add(2)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
s1.Add(1, 2)
s2.Add(1, 2, 3)
s3.Add(1, 2, 3, 4)
t.Assert(s1.IsSubsetOf(s2), true)
t.Assert(s2.IsSubsetOf(s3), true)
t.Assert(s1.IsSubsetOf(s3), true)
@ -133,8 +155,8 @@ func TestSet_Union(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2)
s2.Add(3).Add(4)
s1.Add(1, 2)
s2.Add(3, 4)
s3 := s1.Union(s2)
t.Assert(s3.Contains(1), true)
t.Assert(s3.Contains(2), true)
@ -147,8 +169,8 @@ func TestSet_Diff(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s1.Add(1, 2, 3)
s2.Add(3, 4, 5)
s3 := s1.Diff(s2)
t.Assert(s3.Contains(1), true)
t.Assert(s3.Contains(2), true)
@ -161,8 +183,8 @@ func TestSet_Intersect(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s1.Add(1, 2, 3)
s2.Add(3, 4, 5)
s3 := s1.Intersect(s2)
t.Assert(s3.Contains(1), false)
t.Assert(s3.Contains(2), false)
@ -175,8 +197,8 @@ func TestSet_Complement(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewSet()
s2 := gset.NewSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s1.Add(1, 2, 3)
s2.Add(3, 4, 5)
s3 := s1.Complement(s2)
t.Assert(s3.Contains(1), false)
t.Assert(s3.Contains(2), false)
@ -203,9 +225,9 @@ func TestNewFrom(t *testing.T) {
func TestNew(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.New()
s1.Add("a").Add(2)
s1.Add("a", 2)
s2 := gset.New(true)
s2.Add("b").Add(3)
s2.Add("b", 3)
t.Assert(s1.Contains("a"), true)
})
@ -214,13 +236,13 @@ func TestNew(t *testing.T) {
func TestSet_Join(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.New(true)
s1.Add("a").Add("a1").Add("b").Add("c")
s1.Add("a", "a1", "b", "c")
str1 := s1.Join(",")
t.Assert(strings.Contains(str1, "a1"), true)
})
gtest.C(t, func(t *gtest.T) {
s1 := gset.New(true)
s1.Add("a").Add(`"b"`).Add(`\c`)
s1.Add("a", `"b"`, `\c`)
str1 := s1.Join(",")
t.Assert(strings.Contains(str1, `"b"`), true)
t.Assert(strings.Contains(str1, `\c`), true)
@ -231,7 +253,7 @@ func TestSet_Join(t *testing.T) {
func TestSet_String(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.New(true)
s1.Add("a").Add("a2").Add("b").Add("c")
s1.Add("a", "a2", "b", "c")
str1 := s1.String()
t.Assert(strings.Contains(str1, "["), true)
t.Assert(strings.Contains(str1, "]"), true)
@ -243,8 +265,8 @@ func TestSet_Merge(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.New(true)
s2 := gset.New(true)
s1.Add("a").Add("a2").Add("b").Add("c")
s2.Add("b").Add("b1").Add("e").Add("f")
s1.Add("a", "a2", "b", "c")
s2.Add("b", "b1", "e", "f")
ss := s1.Merge(s2)
t.Assert(ss.Contains("a2"), true)
t.Assert(ss.Contains("b1"), true)
@ -255,7 +277,7 @@ func TestSet_Merge(t *testing.T) {
func TestSet_Sum(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.New(true)
s1.Add(1).Add(2).Add(3).Add(4)
s1.Add(1, 2, 3, 4)
t.Assert(s1.Sum(), int(10))
})
@ -264,7 +286,7 @@ func TestSet_Sum(t *testing.T) {
func TestSet_Pop(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.New(true)
s.Add(1).Add(2).Add(3).Add(4)
s.Add(1, 2, 3, 4)
t.Assert(s.Size(), 4)
t.AssertIN(s.Pop(), []int{1, 2, 3, 4})
t.Assert(s.Size(), 3)
@ -274,7 +296,7 @@ func TestSet_Pop(t *testing.T) {
func TestSet_Pops(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.New(true)
s.Add(1).Add(2).Add(3).Add(4)
s.Add(1, 2, 3, 4)
t.Assert(s.Size(), 4)
t.Assert(s.Pops(0), nil)
t.AssertIN(s.Pops(1), []int{1, 2, 3, 4})
@ -324,43 +346,84 @@ func TestSet_Json(t *testing.T) {
})
}
func TestSet_AddIfNotExist(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.New(true)
s.Add(1)
t.Assert(s.Contains(1), true)
t.Assert(s.AddIfNotExist(1), false)
t.Assert(s.AddIfNotExist(2), true)
t.Assert(s.Contains(2), true)
t.Assert(s.AddIfNotExist(2), false)
t.Assert(s.Contains(2), true)
})
}
func TestSet_AddIfNotExistFunc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.New(true)
s.Add(1)
t.Assert(s.Contains(1), true)
t.Assert(s.Contains(2), false)
s.AddIfNotExistFunc(2, func() interface{} {
return 3
})
t.Assert(s.AddIfNotExistFunc(2, func() bool { return false }), false)
t.Assert(s.Contains(2), false)
t.Assert(s.Contains(3), true)
s.AddIfNotExistFunc(3, func() interface{} {
return 4
})
t.Assert(s.Contains(3), true)
t.Assert(s.Contains(4), false)
t.Assert(s.AddIfNotExistFunc(2, func() bool { return true }), true)
t.Assert(s.Contains(2), true)
t.Assert(s.AddIfNotExistFunc(2, func() bool { return true }), false)
t.Assert(s.Contains(2), true)
})
gtest.C(t, func(t *gtest.T) {
s := gset.New(true)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
r := s.AddIfNotExistFunc(1, func() bool {
time.Sleep(100 * time.Millisecond)
return true
})
t.Assert(r, false)
}()
s.Add(1)
t.Assert(s.Contains(1), true)
t.Assert(s.Contains(2), false)
wg.Wait()
})
}
s.AddIfNotExistFuncLock(2, func() interface{} {
return 3
func TestSet_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var set gset.Set
set.Add(g.Slice{1, 2}...)
set.Walk(func(item interface{}) interface{} {
return gconv.Int(item) + 10
})
t.Assert(s.Contains(2), false)
t.Assert(s.Contains(3), true)
t.Assert(set.Size(), 2)
t.Assert(set.Contains(11), true)
t.Assert(set.Contains(12), true)
})
}
s.AddIfNotExistFuncLock(3, func() interface{} {
return 4
})
t.Assert(s.Contains(3), true)
t.Assert(s.Contains(4), false)
func TestSet_AddIfNotExistFuncLock(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.New(true)
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
r := s.AddIfNotExistFuncLock(1, func() bool {
time.Sleep(500 * time.Millisecond)
return true
})
t.Assert(r, true)
}()
time.Sleep(100 * time.Millisecond)
go func() {
defer wg.Done()
r := s.AddIfNotExistFuncLock(1, func() bool {
return true
})
t.Assert(r, false)
}()
wg.Wait()
})
}

View File

@ -13,17 +13,39 @@ import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gconv"
"strings"
"sync"
"testing"
"time"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/test/gtest"
)
func TestIntSet_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
var s gset.IntSet
s.Add(1, 1, 2)
s.Add([]int{3, 4}...)
t.Assert(s.Size(), 4)
t.AssertIN(1, s.Slice())
t.AssertIN(2, s.Slice())
t.AssertIN(3, s.Slice())
t.AssertIN(4, s.Slice())
t.AssertNI(0, s.Slice())
t.Assert(s.Contains(4), true)
t.Assert(s.Contains(5), false)
s.Remove(1)
t.Assert(s.Size(), 3)
s.Clear()
t.Assert(s.Size(), 0)
})
}
func TestIntSet_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet()
s.Add(1).Add(1).Add(2)
s.Add(1, 1, 2)
s.Add([]int{3, 4}...)
t.Assert(s.Size(), 4)
t.AssertIN(1, s.Slice())
@ -43,7 +65,7 @@ func TestIntSet_Basic(t *testing.T) {
func TestIntSet_Iterator(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet()
s.Add(1).Add(2).Add(3)
s.Add(1, 2, 3)
t.Assert(s.Size(), 3)
a1 := garray.New(true)
@ -64,7 +86,7 @@ func TestIntSet_Iterator(t *testing.T) {
func TestIntSet_LockFunc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet()
s.Add(1).Add(2).Add(3)
s.Add(1, 2, 3)
t.Assert(s.Size(), 3)
s.LockFunc(func(m map[int]struct{}) {
delete(m, 1)
@ -84,9 +106,9 @@ func TestIntSet_Equal(t *testing.T) {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s3 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
s1.Add(1, 2, 3)
s2.Add(1, 2, 3)
s3.Add(1, 2, 3, 4)
t.Assert(s1.Equal(s2), true)
t.Assert(s1.Equal(s3), false)
})
@ -97,9 +119,9 @@ func TestIntSet_IsSubsetOf(t *testing.T) {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s3 := gset.NewIntSet()
s1.Add(1).Add(2)
s2.Add(1).Add(2).Add(3)
s3.Add(1).Add(2).Add(3).Add(4)
s1.Add(1, 2)
s2.Add(1, 2, 3)
s3.Add(1, 2, 3, 4)
t.Assert(s1.IsSubsetOf(s2), true)
t.Assert(s2.IsSubsetOf(s3), true)
t.Assert(s1.IsSubsetOf(s3), true)
@ -112,8 +134,8 @@ func TestIntSet_Union(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2)
s2.Add(3).Add(4)
s1.Add(1, 2)
s2.Add(3, 4)
s3 := s1.Union(s2)
t.Assert(s3.Contains(1), true)
t.Assert(s3.Contains(2), true)
@ -126,8 +148,8 @@ func TestIntSet_Diff(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s1.Add(1, 2, 3)
s2.Add(3, 4, 5)
s3 := s1.Diff(s2)
t.Assert(s3.Contains(1), true)
t.Assert(s3.Contains(2), true)
@ -140,8 +162,8 @@ func TestIntSet_Intersect(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s1.Add(1, 2, 3)
s2.Add(3, 4, 5)
s3 := s1.Intersect(s2)
t.Assert(s3.Contains(1), false)
t.Assert(s3.Contains(2), false)
@ -154,8 +176,8 @@ func TestIntSet_Complement(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s1.Add(1, 2, 3)
s2.Add(3, 4, 5)
s3 := s1.Complement(s2)
t.Assert(s3.Contains(1), false)
t.Assert(s3.Contains(2), false)
@ -167,7 +189,7 @@ func TestIntSet_Complement(t *testing.T) {
func TestIntSet_Size(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet(true)
s1.Add(1).Add(2).Add(3)
s1.Add(1, 2, 3)
t.Assert(s1.Size(), 3)
})
@ -178,8 +200,8 @@ func TestIntSet_Merge(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet()
s2 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s2.Add(3).Add(4).Add(5)
s1.Add(1, 2, 3)
s2.Add(3, 4, 5)
s3 := s1.Merge(s2)
t.Assert(s3.Contains(1), true)
t.Assert(s3.Contains(5), true)
@ -190,7 +212,7 @@ func TestIntSet_Merge(t *testing.T) {
func TestIntSet_Join(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s1.Add(1, 2, 3)
s3 := s1.Join(",")
t.Assert(strings.Contains(s3, "1"), true)
t.Assert(strings.Contains(s3, "2"), true)
@ -201,7 +223,7 @@ func TestIntSet_Join(t *testing.T) {
func TestIntSet_String(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s1.Add(1, 2, 3)
s3 := s1.String()
t.Assert(strings.Contains(s3, "["), true)
t.Assert(strings.Contains(s3, "]"), true)
@ -214,9 +236,9 @@ func TestIntSet_String(t *testing.T) {
func TestIntSet_Sum(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewIntSet()
s1.Add(1).Add(2).Add(3)
s1.Add(1, 2, 3)
s2 := gset.NewIntSet()
s2.Add(5).Add(6).Add(7)
s2.Add(5, 6, 7)
t.Assert(s2.Sum(), 18)
})
@ -226,7 +248,7 @@ func TestIntSet_Sum(t *testing.T) {
func TestIntSet_Pop(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet()
s.Add(4).Add(2).Add(3)
s.Add(4, 2, 3)
t.Assert(s.Size(), 3)
t.AssertIN(s.Pop(), []int{4, 2, 3})
t.AssertIN(s.Pop(), []int{4, 2, 3})
@ -237,7 +259,7 @@ func TestIntSet_Pop(t *testing.T) {
func TestIntSet_Pops(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet()
s.Add(1).Add(4).Add(2).Add(3)
s.Add(1, 4, 2, 3)
t.Assert(s.Size(), 4)
t.Assert(s.Pops(0), nil)
t.AssertIN(s.Pops(1), []int{1, 4, 2, 3})
@ -258,6 +280,74 @@ func TestIntSet_Pops(t *testing.T) {
})
}
func TestIntSet_AddIfNotExist(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet(true)
s.Add(1)
t.Assert(s.Contains(1), true)
t.Assert(s.AddIfNotExist(1), false)
t.Assert(s.AddIfNotExist(2), true)
t.Assert(s.Contains(2), true)
t.Assert(s.AddIfNotExist(2), false)
t.Assert(s.Contains(2), true)
})
}
func TestIntSet_AddIfNotExistFunc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet(true)
s.Add(1)
t.Assert(s.Contains(1), true)
t.Assert(s.Contains(2), false)
t.Assert(s.AddIfNotExistFunc(2, func() bool { return false }), false)
t.Assert(s.Contains(2), false)
t.Assert(s.AddIfNotExistFunc(2, func() bool { return true }), true)
t.Assert(s.Contains(2), true)
t.Assert(s.AddIfNotExistFunc(2, func() bool { return true }), false)
t.Assert(s.Contains(2), true)
})
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet(true)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
r := s.AddIfNotExistFunc(1, func() bool {
time.Sleep(100 * time.Millisecond)
return true
})
t.Assert(r, false)
}()
s.Add(1)
wg.Wait()
})
}
func TestIntSet_AddIfNotExistFuncLock(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet(true)
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
r := s.AddIfNotExistFuncLock(1, func() bool {
time.Sleep(500 * time.Millisecond)
return true
})
t.Assert(r, true)
}()
time.Sleep(100 * time.Millisecond)
go func() {
defer wg.Done()
r := s.AddIfNotExistFuncLock(1, func() bool {
return true
})
t.Assert(r, false)
}()
wg.Wait()
})
}
func TestIntSet_Json(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := []int{1, 3, 2, 4}
@ -287,43 +377,16 @@ func TestIntSet_Json(t *testing.T) {
})
}
func TestIntSet_AddIfNotExistFunc(t *testing.T) {
func TestIntSet_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet(true)
s.Add(1)
t.Assert(s.Contains(1), true)
t.Assert(s.Contains(2), false)
s.AddIfNotExistFunc(2, func() int {
return 3
var set gset.IntSet
set.Add(g.SliceInt{1, 2}...)
set.Walk(func(item int) int {
return item + 10
})
t.Assert(s.Contains(2), false)
t.Assert(s.Contains(3), true)
s.AddIfNotExistFunc(3, func() int {
return 4
})
t.Assert(s.Contains(3), true)
t.Assert(s.Contains(4), false)
})
gtest.C(t, func(t *gtest.T) {
s := gset.NewIntSet(true)
s.Add(1)
t.Assert(s.Contains(1), true)
t.Assert(s.Contains(2), false)
s.AddIfNotExistFuncLock(2, func() int {
return 3
})
t.Assert(s.Contains(2), false)
t.Assert(s.Contains(3), true)
s.AddIfNotExistFuncLock(3, func() int {
return 4
})
t.Assert(s.Contains(3), true)
t.Assert(s.Contains(4), false)
t.Assert(set.Size(), 2)
t.Assert(set.Contains(11), true)
t.Assert(set.Contains(12), true)
})
}

View File

@ -13,17 +13,19 @@ import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/util/gconv"
"strings"
"sync"
"testing"
"time"
"github.com/gogf/gf/container/garray"
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/test/gtest"
)
func TestStrSet_Basic(t *testing.T) {
func TestStrSet_Var(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet()
s.Add("1").Add("1").Add("2")
var s gset.StrSet
s.Add("1", "1", "2")
s.Add([]string{"3", "4"}...)
t.Assert(s.Size(), 4)
t.AssertIN("1", s.Slice())
@ -40,10 +42,40 @@ func TestStrSet_Basic(t *testing.T) {
})
}
func TestStrSet_Basic(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet()
s.Add("1", "1", "2")
s.Add([]string{"3", "4"}...)
t.Assert(s.Size(), 4)
t.AssertIN("1", s.Slice())
t.AssertIN("2", s.Slice())
t.AssertIN("3", s.Slice())
t.AssertIN("4", s.Slice())
t.AssertNI("0", s.Slice())
t.Assert(s.Contains("4"), true)
t.Assert(s.Contains("5"), false)
s.Remove("1")
t.Assert(s.Size(), 3)
s.Clear()
t.Assert(s.Size(), 0)
})
}
func TestStrSet_ContainsI(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet()
s.Add("a", "b", "C")
t.Assert(s.Contains("A"), false)
t.Assert(s.Contains("a"), true)
t.Assert(s.ContainsI("A"), true)
})
}
func TestStrSet_Iterator(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet()
s.Add("1").Add("2").Add("3")
s.Add("1", "2", "3")
t.Assert(s.Size(), 3)
a1 := garray.New(true)
@ -64,7 +96,7 @@ func TestStrSet_Iterator(t *testing.T) {
func TestStrSet_LockFunc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet()
s.Add("1").Add("2").Add("3")
s.Add("1", "2", "3")
t.Assert(s.Size(), 3)
s.LockFunc(func(m map[string]struct{}) {
delete(m, "1")
@ -84,9 +116,9 @@ func TestStrSet_Equal(t *testing.T) {
s1 := gset.NewStrSet()
s2 := gset.NewStrSet()
s3 := gset.NewStrSet()
s1.Add("1").Add("2").Add("3")
s2.Add("1").Add("2").Add("3")
s3.Add("1").Add("2").Add("3").Add("4")
s1.Add("1", "2", "3")
s2.Add("1", "2", "3")
s3.Add("1", "2", "3", "4")
t.Assert(s1.Equal(s2), true)
t.Assert(s1.Equal(s3), false)
})
@ -97,9 +129,9 @@ func TestStrSet_IsSubsetOf(t *testing.T) {
s1 := gset.NewStrSet()
s2 := gset.NewStrSet()
s3 := gset.NewStrSet()
s1.Add("1").Add("2")
s2.Add("1").Add("2").Add("3")
s3.Add("1").Add("2").Add("3").Add("4")
s1.Add("1", "2")
s2.Add("1", "2", "3")
s3.Add("1", "2", "3", "4")
t.Assert(s1.IsSubsetOf(s2), true)
t.Assert(s2.IsSubsetOf(s3), true)
t.Assert(s1.IsSubsetOf(s3), true)
@ -112,8 +144,8 @@ func TestStrSet_Union(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewStrSet()
s2 := gset.NewStrSet()
s1.Add("1").Add("2")
s2.Add("3").Add("4")
s1.Add("1", "2")
s2.Add("3", "4")
s3 := s1.Union(s2)
t.Assert(s3.Contains("1"), true)
t.Assert(s3.Contains("2"), true)
@ -126,8 +158,8 @@ func TestStrSet_Diff(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewStrSet()
s2 := gset.NewStrSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s1.Add("1", "2", "3")
s2.Add("3", "4", "5")
s3 := s1.Diff(s2)
t.Assert(s3.Contains("1"), true)
t.Assert(s3.Contains("2"), true)
@ -140,8 +172,8 @@ func TestStrSet_Intersect(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewStrSet()
s2 := gset.NewStrSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s1.Add("1", "2", "3")
s2.Add("3", "4", "5")
s3 := s1.Intersect(s2)
t.Assert(s3.Contains("1"), false)
t.Assert(s3.Contains("2"), false)
@ -154,8 +186,8 @@ func TestStrSet_Complement(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewStrSet()
s2 := gset.NewStrSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s1.Add("1", "2", "3")
s2.Add("3", "4", "5")
s3 := s1.Complement(s2)
t.Assert(s3.Contains("1"), false)
t.Assert(s3.Contains("2"), false)
@ -179,8 +211,8 @@ func TestStrSet_Merge(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewStrSet()
s2 := gset.NewStrSet()
s1.Add("1").Add("2").Add("3")
s2.Add("3").Add("4").Add("5")
s1.Add("1", "2", "3")
s2.Add("3", "4", "5")
s3 := s1.Merge(s2)
t.Assert(s3.Contains("1"), true)
t.Assert(s3.Contains("6"), false)
@ -207,7 +239,7 @@ func TestStrSet_Join(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewStrSet()
s1.Add("a").Add(`"b"`).Add(`\c`)
s1.Add("a", `"b"`, `\c`)
str1 := s1.Join(",")
t.Assert(strings.Contains(str1, `"b"`), true)
t.Assert(strings.Contains(str1, `\c`), true)
@ -225,7 +257,7 @@ func TestStrSet_String(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.New(true)
s1.Add("a").Add("a2").Add("b").Add("c")
s1.Add("a", "a2", "b", "c")
str1 := s1.String()
t.Assert(strings.Contains(str1, "["), true)
t.Assert(strings.Contains(str1, "]"), true)
@ -253,7 +285,7 @@ func TestStrSet_Size(t *testing.T) {
func TestStrSet_Remove(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := gset.NewStrSetFrom([]string{"a", "b", "c"}, true)
s1 = s1.Remove("b")
s1.Remove("b")
t.Assert(s1.Contains("b"), false)
t.Assert(s1.Contains("c"), true)
})
@ -294,6 +326,74 @@ func TestStrSet_Pops(t *testing.T) {
})
}
func TestStrSet_AddIfNotExist(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet(true)
s.Add("1")
t.Assert(s.Contains("1"), true)
t.Assert(s.AddIfNotExist("1"), false)
t.Assert(s.AddIfNotExist("2"), true)
t.Assert(s.Contains("2"), true)
t.Assert(s.AddIfNotExist("2"), false)
t.Assert(s.Contains("2"), true)
})
}
func TestStrSet_AddIfNotExistFunc(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet(true)
s.Add("1")
t.Assert(s.Contains("1"), true)
t.Assert(s.Contains("2"), false)
t.Assert(s.AddIfNotExistFunc("2", func() bool { return false }), false)
t.Assert(s.Contains("2"), false)
t.Assert(s.AddIfNotExistFunc("2", func() bool { return true }), true)
t.Assert(s.Contains("2"), true)
t.Assert(s.AddIfNotExistFunc("2", func() bool { return true }), false)
t.Assert(s.Contains("2"), true)
})
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet(true)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
r := s.AddIfNotExistFunc("1", func() bool {
time.Sleep(100 * time.Millisecond)
return true
})
t.Assert(r, false)
}()
s.Add("1")
wg.Wait()
})
}
func TestStrSet_AddIfNotExistFuncLock(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet(true)
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
r := s.AddIfNotExistFuncLock("1", func() bool {
time.Sleep(500 * time.Millisecond)
return true
})
t.Assert(r, true)
}()
time.Sleep(100 * time.Millisecond)
go func() {
defer wg.Done()
r := s.AddIfNotExistFuncLock("1", func() bool {
return true
})
t.Assert(r, false)
}()
wg.Wait()
})
}
func TestStrSet_Json(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s1 := []string{"a", "b", "d", "c"}
@ -323,43 +423,21 @@ func TestStrSet_Json(t *testing.T) {
})
}
func TestStrSet_AddIfNotExistFunc(t *testing.T) {
func TestStrSet_Walk(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet(true)
s.Add("1")
t.Assert(s.Contains("1"), true)
t.Assert(s.Contains("2"), false)
s.AddIfNotExistFunc("2", func() string {
return "3"
var (
set gset.StrSet
names = g.SliceStr{"user", "user_detail"}
prefix = "gf_"
)
set.Add(names...)
// Add prefix for given table names.
set.Walk(func(item string) string {
return prefix + item
})
t.Assert(s.Contains("2"), false)
t.Assert(s.Contains("3"), true)
s.AddIfNotExistFunc("3", func() string {
return "4"
})
t.Assert(s.Contains("3"), true)
t.Assert(s.Contains("4"), false)
})
gtest.C(t, func(t *gtest.T) {
s := gset.NewStrSet(true)
s.Add("1")
t.Assert(s.Contains("1"), true)
t.Assert(s.Contains("2"), false)
s.AddIfNotExistFuncLock("2", func() string {
return "3"
})
t.Assert(s.Contains("2"), false)
t.Assert(s.Contains("3"), true)
s.AddIfNotExistFuncLock("3", func() string {
return "4"
})
t.Assert(s.Contains("3"), true)
t.Assert(s.Contains("4"), false)
t.Assert(set.Size(), 2)
t.Assert(set.Contains("gf_user"), true)
t.Assert(set.Contains("gf_user_detail"), true)
})
}

View File

@ -18,7 +18,7 @@ import (
// AVLTree holds elements of the AVL tree.
type AVLTree struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
root *AVLTreeNode
comparator func(v1, v2 interface{}) int
size int
@ -38,7 +38,7 @@ type AVLTreeNode struct {
// which is false in default.
func NewAVLTree(comparator func(v1, v2 interface{}) int, safe ...bool) *AVLTree {
return &AVLTree{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
comparator: comparator,
}
}
@ -55,7 +55,7 @@ func NewAVLTreeFrom(comparator func(v1, v2 interface{}) int, data map[interface{
}
// Clone returns a new tree with a copy of current tree.
func (tree *AVLTree) Clone(safe ...bool) *AVLTree {
func (tree *AVLTree) Clone() *AVLTree {
newTree := NewAVLTree(tree.comparator, !tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
@ -93,7 +93,7 @@ func (tree *AVLTree) Search(key interface{}) (value interface{}, found bool) {
func (tree *AVLTree) doSearch(key interface{}) (node *AVLTreeNode, found bool) {
node = tree.root
for node != nil {
cmp := tree.comparator(key, node.Key)
cmp := tree.getComparator()(key, node.Key)
switch {
case cmp == 0:
return node, true
@ -331,7 +331,7 @@ func (tree *AVLTree) Floor(key interface{}) (floor *AVLTreeNode, found bool) {
defer tree.mu.RUnlock()
n := tree.root
for n != nil {
c := tree.comparator(key, n.Key)
c := tree.getComparator()(key, n.Key)
switch {
case c == 0:
return n, true
@ -361,7 +361,7 @@ func (tree *AVLTree) Ceiling(key interface{}) (ceiling *AVLTreeNode, found bool)
defer tree.mu.RUnlock()
n := tree.root
for n != nil {
c := tree.comparator(key, n.Key)
c := tree.getComparator()(key, n.Key)
switch {
case c == 0:
return n, true
@ -465,7 +465,7 @@ func (tree *AVLTree) IteratorFrom(key interface{}, match bool, f func(key, value
tree.IteratorAscFrom(key, match, f)
}
// IteratorAsc iterates the tree in ascending order with given callback function <f>.
// IteratorAsc iterates the tree readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *AVLTree) IteratorAsc(f func(key, value interface{}) bool) {
tree.mu.RLock()
@ -473,7 +473,7 @@ func (tree *AVLTree) IteratorAsc(f func(key, value interface{}) bool) {
tree.doIteratorAsc(tree.bottom(0), f)
}
// IteratorAscFrom iterates the tree in ascending order with given callback function <f>.
// IteratorAscFrom iterates the tree readonly in ascending order with given callback function <f>.
// The parameter <key> specifies the start entry for iterating. The <match> specifies whether
// starting iterating if the <key> is fully matched, or else using index searching iterating.
// If <f> returns true, then it continues iterating; or false to stop.
@ -499,7 +499,7 @@ func (tree *AVLTree) doIteratorAsc(node *AVLTreeNode, f func(key, value interfac
}
}
// IteratorDesc iterates the tree in descending order with given callback function <f>.
// IteratorDesc iterates the tree readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *AVLTree) IteratorDesc(f func(key, value interface{}) bool) {
tree.mu.RLock()
@ -507,7 +507,7 @@ func (tree *AVLTree) IteratorDesc(f func(key, value interface{}) bool) {
tree.doIteratorDesc(tree.bottom(1), f)
}
// IteratorDescFrom iterates the tree in descending order with given callback function <f>.
// IteratorDescFrom iterates the tree readonly in descending order with given callback function <f>.
// The parameter <key> specifies the start entry for iterating. The <match> specifies whether
// starting iterating if the <key> is fully matched, or else using index searching iterating.
// If <f> returns true, then it continues iterating; or false to stop.
@ -541,7 +541,7 @@ func (tree *AVLTree) put(key interface{}, value interface{}, p *AVLTreeNode, qp
return true
}
c := tree.comparator(key, q.Key)
c := tree.getComparator()(key, q.Key)
if c == 0 {
q.Key = key
q.Value = value
@ -566,7 +566,7 @@ func (tree *AVLTree) remove(key interface{}, qp **AVLTreeNode) (value interface{
return nil, false
}
c := tree.comparator(key, q.Key)
c := tree.getComparator()(key, q.Key)
if c == 0 {
tree.size--
value = q.Value
@ -784,3 +784,12 @@ func output(node *AVLTreeNode, prefix string, isTail bool, str *string) {
func (tree *AVLTree) MarshalJSON() ([]byte, error) {
return json.Marshal(tree.Map())
}
// getComparator returns the comparator if it's previously set,
// or else it panics.
func (tree *AVLTree) getComparator() func(a, b interface{}) int {
if tree.comparator == nil {
panic("comparator is missing for tree")
}
return tree.comparator
}

View File

@ -20,7 +20,7 @@ import (
// BTree holds elements of the B-tree.
type BTree struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
root *BTreeNode
comparator func(v1, v2 interface{}) int
size int // Total number of keys in the tree
@ -50,7 +50,7 @@ func NewBTree(m int, comparator func(v1, v2 interface{}) int, safe ...bool) *BTr
}
return &BTree{
comparator: comparator,
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
m: m,
}
}
@ -67,7 +67,7 @@ func NewBTreeFrom(m int, comparator func(v1, v2 interface{}) int, data map[inter
}
// Clone returns a new tree with a copy of current tree.
func (tree *BTree) Clone(safe ...bool) *BTree {
func (tree *BTree) Clone() *BTree {
newTree := NewBTree(tree.m, tree.comparator, !tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
@ -406,7 +406,7 @@ func (tree *BTree) IteratorFrom(key interface{}, match bool, f func(key, value i
tree.IteratorAscFrom(key, match, f)
}
// IteratorAsc iterates the tree in ascending order with given callback function <f>.
// IteratorAsc iterates the tree readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *BTree) IteratorAsc(f func(key, value interface{}) bool) {
tree.mu.RLock()
@ -418,7 +418,7 @@ func (tree *BTree) IteratorAsc(f func(key, value interface{}) bool) {
tree.doIteratorAsc(node, node.Entries[0], 0, f)
}
// IteratorAscFrom iterates the tree in ascending order with given callback function <f>.
// IteratorAscFrom iterates the tree readonly in ascending order with given callback function <f>.
// The parameter <key> specifies the start entry for iterating. The <match> specifies whether
// starting iterating if the <key> is fully matched, or else using index searching iterating.
// If <f> returns true, then it continues iterating; or false to stop.
@ -479,7 +479,7 @@ loop:
}
}
// IteratorDesc iterates the tree in descending order with given callback function <f>.
// IteratorDesc iterates the tree readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *BTree) IteratorDesc(f func(key, value interface{}) bool) {
tree.mu.RLock()
@ -493,7 +493,7 @@ func (tree *BTree) IteratorDesc(f func(key, value interface{}) bool) {
tree.doIteratorDesc(node, entry, index, f)
}
// IteratorDescFrom iterates the tree in descending order with given callback function <f>.
// IteratorDescFrom iterates the tree readonly in descending order with given callback function <f>.
// The parameter <key> specifies the start entry for iterating. The <match> specifies whether
// starting iterating if the <key> is fully matched, or else using index searching iterating.
// If <f> returns true, then it continues iterating; or false to stop.
@ -510,7 +510,7 @@ func (tree *BTree) IteratorDescFrom(key interface{}, match bool, f func(key, val
}
}
// IteratorDesc iterates the tree in descending order with given callback function <f>.
// IteratorDesc iterates the tree readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *BTree) doIteratorDesc(node *BTreeNode, entry *BTreeEntry, index int, f func(key, value interface{}) bool) {
first := true
@ -621,7 +621,7 @@ func (tree *BTree) search(node *BTreeNode, key interface{}) (index int, found bo
low, mid, high := 0, 0, len(node.Entries)-1
for low <= high {
mid = (high + low) / 2
compare := tree.comparator(key, node.Entries[mid].Key)
compare := tree.getComparator()(key, node.Entries[mid].Key)
switch {
case compare > 0:
low = mid + 1
@ -934,3 +934,12 @@ func (tree *BTree) deleteChild(node *BTreeNode, index int) {
func (tree *BTree) MarshalJSON() ([]byte, error) {
return json.Marshal(tree.Map())
}
// getComparator returns the comparator if it's previously set,
// or else it panics.
func (tree *BTree) getComparator() func(a, b interface{}) int {
if tree.comparator == nil {
panic("comparator is missing for tree")
}
return tree.comparator
}

View File

@ -24,7 +24,7 @@ const (
// RedBlackTree holds elements of the red-black tree.
type RedBlackTree struct {
mu *rwmutex.RWMutex
mu rwmutex.RWMutex
root *RedBlackTreeNode
size int
comparator func(v1, v2 interface{}) int
@ -45,7 +45,7 @@ type RedBlackTreeNode struct {
// which is false in default.
func NewRedBlackTree(comparator func(v1, v2 interface{}) int, safe ...bool) *RedBlackTree {
return &RedBlackTree{
mu: rwmutex.New(safe...),
mu: rwmutex.Create(safe...),
comparator: comparator,
}
}
@ -82,7 +82,7 @@ func (tree *RedBlackTree) SetComparator(comparator func(a, b interface{}) int) {
}
// Clone returns a new tree with a copy of current tree.
func (tree *RedBlackTree) Clone(safe ...bool) *RedBlackTree {
func (tree *RedBlackTree) Clone() *RedBlackTree {
newTree := NewRedBlackTree(tree.comparator, !tree.mu.IsSafe())
newTree.Sets(tree.Map())
return newTree
@ -109,14 +109,14 @@ func (tree *RedBlackTree) doSet(key interface{}, value interface{}) {
insertedNode := (*RedBlackTreeNode)(nil)
if tree.root == nil {
// Assert key is of comparator's type for initial tree
tree.comparator(key, key)
tree.getComparator()(key, key)
tree.root = &RedBlackTreeNode{Key: key, Value: value, color: red}
insertedNode = tree.root
} else {
node := tree.root
loop := true
for loop {
compare := tree.comparator(key, node.Key)
compare := tree.getComparator()(key, node.Key)
switch {
case compare == 0:
//node.Key = key
@ -337,8 +337,10 @@ func (tree *RedBlackTree) Size() int {
// Keys returns all keys in asc order.
func (tree *RedBlackTree) Keys() []interface{} {
keys := make([]interface{}, tree.Size())
index := 0
var (
keys = make([]interface{}, tree.Size())
index = 0
)
tree.IteratorAsc(func(key, value interface{}) bool {
keys[index] = key
index++
@ -349,8 +351,10 @@ func (tree *RedBlackTree) Keys() []interface{} {
// Values returns all values in asc order based on the key.
func (tree *RedBlackTree) Values() []interface{} {
values := make([]interface{}, tree.Size())
index := 0
var (
values = make([]interface{}, tree.Size())
index = 0
)
tree.IteratorAsc(func(key, value interface{}) bool {
values[index] = value
index++
@ -440,7 +444,7 @@ func (tree *RedBlackTree) Floor(key interface{}) (floor *RedBlackTreeNode, found
defer tree.mu.RUnlock()
n := tree.root
for n != nil {
compare := tree.comparator(key, n.Key)
compare := tree.getComparator()(key, n.Key)
switch {
case compare == 0:
return n, true
@ -468,7 +472,7 @@ func (tree *RedBlackTree) Ceiling(key interface{}) (ceiling *RedBlackTreeNode, f
defer tree.mu.RUnlock()
n := tree.root
for n != nil {
compare := tree.comparator(key, n.Key)
compare := tree.getComparator()(key, n.Key)
switch {
case compare == 0:
return n, true
@ -495,7 +499,7 @@ func (tree *RedBlackTree) IteratorFrom(key interface{}, match bool, f func(key,
tree.IteratorAscFrom(key, match, f)
}
// IteratorAsc iterates the tree in ascending order with given callback function <f>.
// IteratorAsc iterates the tree readonly in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *RedBlackTree) IteratorAsc(f func(key, value interface{}) bool) {
tree.mu.RLock()
@ -503,7 +507,7 @@ func (tree *RedBlackTree) IteratorAsc(f func(key, value interface{}) bool) {
tree.doIteratorAsc(tree.leftNode(), f)
}
// IteratorAscFrom iterates the tree in ascending order with given callback function <f>.
// IteratorAscFrom iterates the tree readonly in ascending order with given callback function <f>.
// The parameter <key> specifies the start entry for iterating. The <match> specifies whether
// starting iterating if the <key> is fully matched, or else using index searching iterating.
// If <f> returns true, then it continues iterating; or false to stop.
@ -539,14 +543,14 @@ loop:
old := node
for node.parent != nil {
node = node.parent
if tree.comparator(old.Key, node.Key) <= 0 {
if tree.getComparator()(old.Key, node.Key) <= 0 {
goto loop
}
}
}
}
// IteratorDesc iterates the tree in descending order with given callback function <f>.
// IteratorDesc iterates the tree readonly in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (tree *RedBlackTree) IteratorDesc(f func(key, value interface{}) bool) {
tree.mu.RLock()
@ -554,7 +558,7 @@ func (tree *RedBlackTree) IteratorDesc(f func(key, value interface{}) bool) {
tree.doIteratorDesc(tree.rightNode(), f)
}
// IteratorDescFrom iterates the tree in descending order with given callback function <f>.
// IteratorDescFrom iterates the tree readonly in descending order with given callback function <f>.
// The parameter <key> specifies the start entry for iterating. The <match> specifies whether
// starting iterating if the <key> is fully matched, or else using index searching iterating.
// If <f> returns true, then it continues iterating; or false to stop.
@ -590,7 +594,7 @@ loop:
old := node
for node.parent != nil {
node = node.parent
if tree.comparator(old.Key, node.Key) >= 0 {
if tree.getComparator()(old.Key, node.Key) >= 0 {
goto loop
}
}
@ -699,7 +703,7 @@ func (tree *RedBlackTree) output(node *RedBlackTreeNode, prefix string, isTail b
func (tree *RedBlackTree) doSearch(key interface{}) (node *RedBlackTreeNode, found bool) {
node = tree.root
for node != nil {
compare := tree.comparator(key, node.Key)
compare := tree.getComparator()(key, node.Key)
switch {
case compare == 0:
return node, true
@ -927,12 +931,11 @@ func (tree *RedBlackTree) MarshalJSON() ([]byte, error) {
// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal.
func (tree *RedBlackTree) UnmarshalJSON(b []byte) error {
if tree.mu == nil {
tree.mu = rwmutex.New()
tree.comparator = gutil.ComparatorString
}
tree.mu.Lock()
defer tree.mu.Unlock()
if tree.comparator == nil {
tree.comparator = gutil.ComparatorString
}
var data map[string]interface{}
if err := json.Unmarshal(b, &data); err != nil {
return err
@ -945,14 +948,22 @@ func (tree *RedBlackTree) UnmarshalJSON(b []byte) error {
// UnmarshalValue is an interface implement which sets any type of value for map.
func (tree *RedBlackTree) UnmarshalValue(value interface{}) (err error) {
if tree.mu == nil {
tree.mu = rwmutex.New()
tree.comparator = gutil.ComparatorString
}
tree.mu.Lock()
defer tree.mu.Unlock()
if tree.comparator == nil {
tree.comparator = gutil.ComparatorString
}
for k, v := range gconv.Map(value) {
tree.doSet(k, v)
}
return
}
// getComparator returns the comparator if it's previously set,
// or else it panics.
func (tree *RedBlackTree) getComparator() func(a, b interface{}) int {
if tree.comparator == nil {
panic("comparator is missing for tree")
}
return tree.comparator
}

View File

@ -54,11 +54,13 @@ func (v *Var) Clone() *Var {
// Set sets <value> to <v>, and returns the old value.
func (v *Var) Set(value interface{}) (old interface{}) {
if v.safe {
old = v.value.(*gtype.Interface).Set(value)
} else {
old = v.value
v.value = value
if t, ok := v.value.(*gtype.Interface); ok {
old = t.Set(value)
return
}
}
old = v.value
v.value = value
return
}
@ -68,7 +70,9 @@ func (v *Var) Val() interface{} {
return nil
}
if v.safe {
return v.value.(*gtype.Interface).Val()
if t, ok := v.value.(*gtype.Interface); ok {
return t.Val()
}
}
return v.value
}
@ -231,100 +235,6 @@ func (v *Var) GTime(format ...string) *gtime.Time {
return gconv.GTime(v.Val(), format...)
}
// Map converts <v> to map[string]interface{}.
func (v *Var) Map(tags ...string) map[string]interface{} {
return gconv.Map(v.Val(), tags...)
}
// MapStrStr converts <v> to map[string]string.
func (v *Var) MapStrStr(tags ...string) map[string]string {
return gconv.MapStrStr(v.Val(), tags...)
}
// MapStrVar converts <v> to map[string]*Var.
func (v *Var) MapStrVar(tags ...string) map[string]*Var {
m := v.Map(tags...)
if len(m) > 0 {
vMap := make(map[string]*Var)
for k, v := range m {
vMap[k] = New(v)
}
return vMap
}
return nil
}
// MapDeep converts <v> to map[string]interface{} recursively.
func (v *Var) MapDeep(tags ...string) map[string]interface{} {
return gconv.MapDeep(v.Val(), tags...)
}
// MapDeep converts <v> to map[string]string recursively.
func (v *Var) MapStrStrDeep(tags ...string) map[string]string {
return gconv.MapStrStrDeep(v.Val(), tags...)
}
// MapStrVarDeep converts <v> to map[string]*Var recursively.
func (v *Var) MapStrVarDeep(tags ...string) map[string]*Var {
m := v.MapDeep(tags...)
if len(m) > 0 {
vMap := make(map[string]*Var)
for k, v := range m {
vMap[k] = New(v)
}
return vMap
}
return nil
}
// Struct maps value of <v> to <pointer>.
// The parameter <pointer> should be a pointer to a struct instance.
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error {
return gconv.Struct(v.Val(), pointer, mapping...)
}
// Struct maps value of <v> to <pointer> recursively.
// The parameter <pointer> should be a pointer to a struct instance.
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
func (v *Var) StructDeep(pointer interface{}, mapping ...map[string]string) error {
return gconv.StructDeep(v.Val(), pointer, mapping...)
}
// Structs converts <v> to given struct slice.
func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.Structs(v.Val(), pointer, mapping...)
}
// StructsDeep converts <v> to given struct slice recursively.
func (v *Var) StructsDeep(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.StructsDeep(v.Val(), pointer, mapping...)
}
// MapToMap converts map type variable <params> to another map type variable <pointer>.
// The elements of <pointer> should be type of struct/*struct.
func (v *Var) MapToMap(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMap(v.Val(), pointer, mapping...)
}
// MapToMapDeep recursively converts map type variable <params> to another map type variable <pointer>.
// The elements of <pointer> should be type of struct/*struct.
func (v *Var) MapToMapDeep(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMapDeep(v.Val(), pointer, mapping...)
}
// MapToMaps converts map type variable <params> to another map type variable <pointer>.
// The elements of <pointer> should be type of []struct/[]*struct.
func (v *Var) MapToMaps(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMaps(v.Val(), pointer, mapping...)
}
// MapToMapsDeep recursively converts map type variable <params> to another map type variable <pointer>.
// The elements of <pointer> should be type of []struct/[]*struct.
func (v *Var) MapToMapsDeep(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMapsDeep(v.Val(), pointer, mapping...)
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (v *Var) MarshalJSON() ([]byte, error) {
return json.Marshal(v.Val())

View File

@ -0,0 +1,87 @@
// Copyright 2020 gf Author(https://github.com/gogf/gf). 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 gvar
import "github.com/gogf/gf/util/gconv"
// Map converts and returns <v> as map[string]interface{}.
func (v *Var) Map(tags ...string) map[string]interface{} {
return gconv.Map(v.Val(), tags...)
}
// MapStrStr converts and returns <v> as map[string]string.
func (v *Var) MapStrStr(tags ...string) map[string]string {
return gconv.MapStrStr(v.Val(), tags...)
}
// MapStrVar converts and returns <v> as map[string]*Var.
func (v *Var) MapStrVar(tags ...string) map[string]*Var {
m := v.Map(tags...)
if len(m) > 0 {
vMap := make(map[string]*Var, len(m))
for k, v := range m {
vMap[k] = New(v)
}
return vMap
}
return nil
}
// MapDeep converts and returns <v> as map[string]interface{} recursively.
func (v *Var) MapDeep(tags ...string) map[string]interface{} {
return gconv.MapDeep(v.Val(), tags...)
}
// MapDeep converts and returns <v> as map[string]string recursively.
func (v *Var) MapStrStrDeep(tags ...string) map[string]string {
return gconv.MapStrStrDeep(v.Val(), tags...)
}
// MapStrVarDeep converts and returns <v> as map[string]*Var recursively.
func (v *Var) MapStrVarDeep(tags ...string) map[string]*Var {
m := v.MapDeep(tags...)
if len(m) > 0 {
vMap := make(map[string]*Var, len(m))
for k, v := range m {
vMap[k] = New(v)
}
return vMap
}
return nil
}
// Maps converts and returns <v> as map[string]string.
// See gconv.Maps.
func (v *Var) Maps(tags ...string) []map[string]interface{} {
return gconv.Maps(v.Val(), tags...)
}
// MapToMap converts any map type variable <params> to another map type variable <pointer>.
// See gconv.MapToMap.
func (v *Var) MapToMap(pointer interface{}) (err error) {
return gconv.MapToMap(v.Val(), pointer)
}
// MapToMapDeep converts any map type variable <params> to another map type variable
// <pointer> recursively.
// See gconv.MapToMapDeep.
func (v *Var) MapToMapDeep(pointer interface{}) (err error) {
return gconv.MapToMapDeep(v.Val(), pointer)
}
// MapToMaps converts any map type variable <params> to another map type variable <pointer>.
// See gconv.MapToMaps.
func (v *Var) MapToMaps(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMaps(v.Val(), pointer, mapping...)
}
// MapToMapsDeep converts any map type variable <params> to another map type variable
// <pointer> recursively.
// See gconv.MapToMapsDeep.
func (v *Var) MapToMapsDeep(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.MapToMapsDeep(v.Val(), pointer, mapping...)
}

View File

@ -0,0 +1,33 @@
// Copyright 2020 gf Author(https://github.com/gogf/gf). 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 gvar
import "github.com/gogf/gf/util/gconv"
// Struct maps value of <v> to <pointer>.
// The parameter <pointer> should be a pointer to a struct instance.
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error {
return gconv.Struct(v.Val(), pointer, mapping...)
}
// Struct maps value of <v> to <pointer> recursively.
// The parameter <pointer> should be a pointer to a struct instance.
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
func (v *Var) StructDeep(pointer interface{}, mapping ...map[string]string) error {
return gconv.StructDeep(v.Val(), pointer, mapping...)
}
// Structs converts and returns <v> as given struct slice.
func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.Structs(v.Val(), pointer, mapping...)
}
// StructsDeep converts and returns <v> as given struct slice recursively.
func (v *Var) StructsDeep(pointer interface{}, mapping ...map[string]string) (err error) {
return gconv.StructsDeep(v.Val(), pointer, mapping...)
}

View File

@ -9,14 +9,10 @@ package gvar_test
import (
"bytes"
"encoding/binary"
"encoding/json"
"github.com/gogf/gf/util/gconv"
"math"
"testing"
"time"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/test/gtest"
)
@ -305,90 +301,6 @@ func Test_Duration(t *testing.T) {
})
}
func Test_Map(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := g.Map{
"k1": "v1",
"k2": "v2",
}
objOne := gvar.New(m, true)
t.Assert(objOne.Map()["k1"], m["k1"])
t.Assert(objOne.Map()["k2"], m["k2"])
})
}
func Test_Struct(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type StTest struct {
Test int
}
Kv := make(map[string]int, 1)
Kv["Test"] = 100
testObj := &StTest{}
objOne := gvar.New(Kv, true)
objOne.Struct(testObj)
t.Assert(testObj.Test, Kv["Test"])
})
gtest.C(t, func(t *gtest.T) {
type StTest struct {
Test int8
}
o := &StTest{}
v := gvar.New(g.Slice{"Test", "-25"})
v.Struct(o)
t.Assert(o.Test, -25)
})
}
func Test_Json(t *testing.T) {
// Marshal
gtest.C(t, func(t *gtest.T) {
s := "i love gf"
v := gvar.New(s)
b1, err1 := json.Marshal(v)
b2, err2 := json.Marshal(s)
t.Assert(err1, err2)
t.Assert(b1, b2)
})
gtest.C(t, func(t *gtest.T) {
s := int64(math.MaxInt64)
v := gvar.New(s)
b1, err1 := json.Marshal(v)
b2, err2 := json.Marshal(s)
t.Assert(err1, err2)
t.Assert(b1, b2)
})
// Unmarshal
gtest.C(t, func(t *gtest.T) {
s := "i love gf"
v := gvar.New(nil)
b, err := json.Marshal(s)
t.Assert(err, nil)
err = json.Unmarshal(b, v)
t.Assert(err, nil)
t.Assert(v.String(), s)
})
gtest.C(t, func(t *gtest.T) {
var v gvar.Var
s := "i love gf"
b, err := json.Marshal(s)
t.Assert(err, nil)
err = json.Unmarshal(b, &v)
t.Assert(err, nil)
t.Assert(v.String(), s)
})
}
func Test_UnmarshalValue(t *testing.T) {
type V struct {
Name string

View File

@ -0,0 +1,59 @@
// Copyright 2020 gf Author(https://github.com/gogf/gf). 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 gvar_test
import (
"encoding/json"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/test/gtest"
"math"
"testing"
)
func Test_Json(t *testing.T) {
// Marshal
gtest.C(t, func(t *gtest.T) {
s := "i love gf"
v := gvar.New(s)
b1, err1 := json.Marshal(v)
b2, err2 := json.Marshal(s)
t.Assert(err1, err2)
t.Assert(b1, b2)
})
gtest.C(t, func(t *gtest.T) {
s := int64(math.MaxInt64)
v := gvar.New(s)
b1, err1 := json.Marshal(v)
b2, err2 := json.Marshal(s)
t.Assert(err1, err2)
t.Assert(b1, b2)
})
// Unmarshal
gtest.C(t, func(t *gtest.T) {
s := "i love gf"
v := gvar.New(nil)
b, err := json.Marshal(s)
t.Assert(err, nil)
err = json.Unmarshal(b, v)
t.Assert(err, nil)
t.Assert(v.String(), s)
})
gtest.C(t, func(t *gtest.T) {
var v gvar.Var
s := "i love gf"
b, err := json.Marshal(s)
t.Assert(err, nil)
err = json.Unmarshal(b, &v)
t.Assert(err, nil)
t.Assert(v.String(), s)
})
}

View File

@ -0,0 +1,26 @@
// Copyright 2020 gf Author(https://github.com/gogf/gf). 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 gvar_test
import (
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/test/gtest"
"testing"
)
func Test_Map(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := g.Map{
"k1": "v1",
"k2": "v2",
}
objOne := gvar.New(m, true)
t.Assert(objOne.Map()["k1"], m["k1"])
t.Assert(objOne.Map()["k2"], m["k2"])
})
}

View File

@ -0,0 +1,69 @@
// Copyright 2020 gf Author(https://github.com/gogf/gf). 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 gvar_test
import (
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/test/gtest"
"testing"
)
func Test_MapToMap(t *testing.T) {
// map[int]int -> map[string]string
// empty original map.
gtest.C(t, func(t *gtest.T) {
m1 := g.MapIntInt{}
m2 := g.MapStrStr{}
t.Assert(gvar.New(m1).MapToMap(&m2), nil)
t.Assert(len(m1), len(m2))
})
// map[int]int -> map[string]string
gtest.C(t, func(t *gtest.T) {
m1 := g.MapIntInt{
1: 100,
2: 200,
}
m2 := g.MapStrStr{}
t.Assert(gvar.New(m1).MapToMap(&m2), nil)
t.Assert(m2["1"], m1[1])
t.Assert(m2["2"], m1[2])
})
// map[string]interface{} -> map[string]string
gtest.C(t, func(t *gtest.T) {
m1 := g.Map{
"k1": "v1",
"k2": "v2",
}
m2 := g.MapStrStr{}
t.Assert(gvar.New(m1).MapToMap(&m2), nil)
t.Assert(m2["k1"], m1["k1"])
t.Assert(m2["k2"], m1["k2"])
})
// map[string]string -> map[string]interface{}
gtest.C(t, func(t *gtest.T) {
m1 := g.MapStrStr{
"k1": "v1",
"k2": "v2",
}
m2 := g.Map{}
t.Assert(gvar.New(m1).MapToMap(&m2), nil)
t.Assert(m2["k1"], m1["k1"])
t.Assert(m2["k2"], m1["k2"])
})
// map[string]interface{} -> map[interface{}]interface{}
gtest.C(t, func(t *gtest.T) {
m1 := g.MapStrStr{
"k1": "v1",
"k2": "v2",
}
m2 := g.MapAnyAny{}
t.Assert(gvar.New(m1).MapToMap(&m2), nil)
t.Assert(m2["k1"], m1["k1"])
t.Assert(m2["k2"], m1["k2"])
})
}

View File

@ -0,0 +1,42 @@
// Copyright 2020 gf Author(https://github.com/gogf/gf). 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 gvar_test
import (
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/test/gtest"
"testing"
)
func Test_Struct(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
type StTest struct {
Test int
}
Kv := make(map[string]int, 1)
Kv["Test"] = 100
testObj := &StTest{}
objOne := gvar.New(Kv, true)
objOne.Struct(testObj)
t.Assert(testObj.Test, Kv["Test"])
})
gtest.C(t, func(t *gtest.T) {
type StTest struct {
Test int8
}
o := &StTest{}
v := gvar.New(g.Slice{"Test", "-25"})
v.Struct(o)
t.Assert(o.Test, -25)
})
}

View File

@ -11,6 +11,7 @@ import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/internal/intlog"
"time"
@ -18,7 +19,6 @@ import (
"github.com/gogf/gf/container/gmap"
"github.com/gogf/gf/container/gtype"
"github.com/gogf/gf/container/gvar"
"github.com/gogf/gf/os/gcache"
"github.com/gogf/gf/util/grand"
)
@ -64,6 +64,7 @@ type DB interface {
// Transaction.
Begin() (*TX, error)
Transaction(f func(tx *TX) error) (err error)
Insert(table string, data interface{}, batch ...int) (sql.Result, error)
InsertIgnore(table string, data interface{}, batch ...int) (sql.Result, error)
@ -78,8 +79,8 @@ type DB interface {
Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error)
// Model creation.
From(tables string) *Model
Table(tables string) *Model
Table(table ...string) *Model
Model(table ...string) *Model
Schema(schema string) *Schema
// Configuration methods.
@ -89,6 +90,9 @@ type DB interface {
SetSchema(schema string)
GetSchema() string
GetPrefix() string
GetGroup() string
SetDryRun(dryrun bool)
GetDryRun() bool
SetLogger(logger *glog.Logger)
GetLogger() *glog.Logger
SetMaxIdleConnCount(n int)
@ -124,6 +128,7 @@ type Core struct {
debug *gtype.Bool // Enable debug mode for the database.
cache *gcache.Cache // Cache manager.
schema *gtype.String // Custom schema for this object.
dryrun *gtype.Bool // Dry run.
prefix string // Table prefix.
logger *glog.Logger // Logger.
maxIdleConnCount int // Max idle connection count.
@ -166,21 +171,23 @@ type Link interface {
Prepare(sql string) (*sql.Stmt, error)
}
// Value is the field value type.
type Value = *gvar.Var
type (
// Value is the field value type.
Value = *gvar.Var
// Record is the row record of the table.
type Record map[string]Value
// Record is the row record of the table.
Record map[string]Value
// Result is the row record array.
type Result []Record
// Result is the row record array.
Result []Record
// Map is alias of map[string]interface{},
// which is the most common usage map type.
type Map = map[string]interface{}
// Map is alias of map[string]interface{},
// which is the most common usage map type.
Map = map[string]interface{}
// List is type of map array.
type List = []Map
// List is type of map array.
List = []Map
)
const (
gINSERT_OPTION_DEFAULT = 0
@ -234,6 +241,7 @@ func New(name ...string) (db DB, err error) {
debug: gtype.NewBool(),
cache: gcache.New(),
schema: gtype.NewString(),
dryrun: gtype.NewBool(),
logger: glog.New(),
prefix: node.Prefix,
maxIdleConnCount: gDEFAULT_CONN_MAX_IDLE_COUNT,
@ -401,5 +409,8 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
if node.Debug {
c.DB.SetDebug(node.Debug)
}
if node.Debug {
c.DB.SetDryRun(node.DryRun)
}
return
}

View File

@ -11,6 +11,7 @@ import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/internal/utils"
"reflect"
"regexp"
"strings"
@ -99,7 +100,11 @@ func (c *Core) DoExec(link Link, sql string, args ...interface{}) (result sql.Re
sql, args = c.DB.HandleSqlBeforeCommit(link, sql, args)
if c.DB.GetDebug() {
mTime1 := gtime.TimestampMilli()
result, err = link.Exec(sql, args...)
if !c.DB.GetDryRun() {
result, err = link.Exec(sql, args...)
} else {
result = new(SqlResult)
}
mTime2 := gtime.TimestampMilli()
s := &Sql{
Sql: sql,
@ -111,7 +116,11 @@ func (c *Core) DoExec(link Link, sql string, args ...interface{}) (result sql.Re
}
c.writeSqlToLogger(s)
} else {
result, err = link.Exec(sql, args...)
if !c.DB.GetDryRun() {
result, err = link.Exec(sql, args...)
} else {
result = new(SqlResult)
}
}
return result, formatError(err, sql, args...)
}
@ -149,7 +158,7 @@ func (c *Core) GetAll(sql string, args ...interface{}) (Result, error) {
return c.DB.DoGetAll(nil, sql, args...)
}
// doGetAll queries and returns data records from database.
// DoGetAll queries and returns data records from database.
func (c *Core) DoGetAll(link Link, sql string, args ...interface{}) (result Result, err error) {
if link == nil {
link, err = c.DB.Slave()
@ -194,9 +203,6 @@ func (c *Core) GetStruct(pointer interface{}, sql string, args ...interface{}) e
if err != nil {
return err
}
if len(one) == 0 {
return ErrNoRows
}
return one.Struct(pointer)
}
@ -207,9 +213,6 @@ func (c *Core) GetStructs(pointer interface{}, sql string, args ...interface{})
if err != nil {
return err
}
if len(all) == 0 {
return ErrNoRows
}
return all.Structs(pointer)
}
@ -301,6 +304,34 @@ func (c *Core) Begin() (*TX, error) {
}
}
// Transaction wraps the transaction logic using function <f>.
// It rollbacks the transaction and returns the error from function <f> if
// it returns non-nil error. It commits the transaction and returns nil if
// function <f> returns nil.
//
// Note that, you should not Commit or Rollback the transaction in function <f>
// as it is automatically handled by this function.
func (c *Core) Transaction(f func(tx *TX) error) (err error) {
var tx *TX
tx, err = c.DB.Begin()
if err != nil {
return err
}
defer func() {
if err != nil {
if e := tx.Rollback(); e != nil {
err = e
}
} else {
if e := tx.Commit(); e != nil {
err = e
}
}
}()
err = f(tx)
return
}
// Insert does "INSERT INTO ..." statement for the table.
// If there's already one unique record of the data in the table, it returns error.
//
@ -371,13 +402,15 @@ func (c *Core) Save(table string, data interface{}, batch ...int) (sql.Result, e
// 2: save: if there's unique/primary key in the data, it updates it or else inserts a new one;
// 3: ignore: if there's unique/primary key in the data, it ignores the inserting;
func (c *Core) DoInsert(link Link, table string, data interface{}, option int, batch ...int) (result sql.Result, err error) {
var fields []string
var values []string
var params []interface{}
var dataMap Map
table = c.DB.QuotePrefixTableName(table)
reflectValue := reflect.ValueOf(data)
reflectKind := reflectValue.Kind()
var (
fields []string
values []string
params []interface{}
dataMap Map
reflectValue = reflect.ValueOf(data)
reflectKind = reflectValue.Kind()
)
if reflectKind == reflect.Ptr {
reflectValue = reflectValue.Elem()
reflectKind = reflectValue.Kind()
@ -393,16 +426,23 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
if len(dataMap) == 0 {
return nil, errors.New("data cannot be empty")
}
charL, charR := c.DB.GetChars()
var (
charL, charR = c.DB.GetChars()
operation = GetInsertOperationByOption(option)
updateStr = ""
)
for k, v := range dataMap {
fields = append(fields, charL+k+charR)
values = append(values, "?")
params = append(params, v)
}
operation := GetInsertOperationByOption(option)
updateStr := ""
if option == gINSERT_OPTION_SAVE {
for k, _ := range dataMap {
// If it's SAVE operation,
// do not automatically update the creating time.
if utils.EqualFoldWithoutChars(k, gSOFT_FIELD_NAME_CREATE) {
continue
}
if len(updateStr) > 0 {
updateStr += ","
}
@ -454,12 +494,15 @@ func (c *Core) BatchSave(table string, list interface{}, batch ...int) (sql.Resu
return c.DB.DoBatchInsert(nil, table, list, gINSERT_OPTION_SAVE, batch...)
}
// doBatchInsert batch inserts/replaces/saves data.
// DoBatchInsert batch inserts/replaces/saves data.
func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
var keys, values []string
var params []interface{}
table = c.DB.QuotePrefixTableName(table)
listMap := (List)(nil)
var (
keys []string
values []string
params []interface{}
listMap List
)
switch v := list.(type) {
case Result:
listMap = v.List()
@ -470,8 +513,10 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
case Map:
listMap = List{v}
default:
rv := reflect.ValueOf(list)
kind := rv.Kind()
var (
rv = reflect.ValueOf(list)
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
@ -484,7 +529,7 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
listMap[i] = DataToMapDeep(rv.Index(i).Interface())
}
case reflect.Map, reflect.Struct:
listMap = List{DataToMapDeep(list)}
listMap = List{DataToMapDeep(v)}
default:
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
}
@ -504,15 +549,21 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
holders = append(holders, "?")
}
// Prepare the batch result pointer.
batchResult := new(SqlResult)
charL, charR := c.DB.GetChars()
keysStr := charL + strings.Join(keys, charR+","+charL) + charR
valueHolderStr := "(" + strings.Join(holders, ",") + ")"
operation := GetInsertOperationByOption(option)
updateStr := ""
var (
charL, charR = c.DB.GetChars()
batchResult = new(SqlResult)
keysStr = charL + strings.Join(keys, charR+","+charL) + charR
valueHolderStr = "(" + strings.Join(holders, ",") + ")"
operation = GetInsertOperationByOption(option)
updateStr = ""
)
if option == gINSERT_OPTION_SAVE {
for _, k := range keys {
// If it's SAVE operation,
// do not automatically update the creating time.
if utils.EqualFoldWithoutChars(k, gSOFT_FIELD_NAME_CREATE) {
continue
}
if len(updateStr) > 0 {
updateStr += ","
}
@ -591,18 +642,25 @@ func (c *Core) Update(table string, data interface{}, condition interface{}, arg
// Also see Update.
func (c *Core) DoUpdate(link Link, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
table = c.DB.QuotePrefixTableName(table)
updates := ""
rv := reflect.ValueOf(data)
kind := rv.Kind()
var (
rv = reflect.ValueOf(data)
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
params := []interface{}(nil)
var (
params []interface{}
updates = ""
)
switch kind {
case reflect.Map, reflect.Struct:
var fields []string
for k, v := range DataToMapDeep(data) {
var (
fields []string
dataMap = DataToMapDeep(data)
)
for k, v := range dataMap {
fields = append(fields, c.DB.QuoteWord(k)+"=?")
params = append(params, v)
}
@ -648,7 +706,7 @@ func (c *Core) Delete(table string, condition interface{}, args ...interface{})
return c.DB.DoDelete(nil, table, newWhere, newArgs...)
}
// doDelete does "DELETE FROM ... " statement for the table.
// DoDelete does "DELETE FROM ... " statement for the table.
// Also see Delete.
func (c *Core) DoDelete(link Link, table string, condition string, args ...interface{}) (result sql.Result, err error) {
if link == nil {
@ -676,9 +734,11 @@ func (c *Core) rowsToResult(rows *sql.Rows) (Result, error) {
columnTypes[k] = v.DatabaseTypeName()
columnNames[k] = v.Name()
}
values := make([]sql.RawBytes, len(columnNames))
records := make(Result, 0)
scanArgs := make([]interface{}, len(values))
var (
values = make([]sql.RawBytes, len(columnNames))
records = make(Result, 0)
scanArgs = make([]interface{}, len(values))
)
for i := range values {
scanArgs[i] = &values[i]
}

View File

@ -36,12 +36,13 @@ type ConfigNode struct {
Role string // (Optional, "master" in default) Node role, used for master-slave mode: master, slave.
Debug bool // (Optional) Debug mode enables debug information logging and output.
Prefix string // (Optional) Table prefix.
DryRun bool // (Optional) Dry run, which does SELECT but no INSERT/UPDATE/DELETE statements.
Weight int // (Optional) Weight for load balance calculating, it's useless if there's just one node.
Charset string // (Optional, "utf8mb4" in default) Custom charset when operating on database.
LinkInfo string // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored.
MaxIdleConnCount int // (Optional) Max idle connection configuration for underlying connection pool.
MaxOpenConnCount int // (Optional) Max open connection configuration for underlying connection pool.
MaxConnLifetime time.Duration // (Optional) Max connection TTL configuration for underlying connection pool.
LinkInfo string `json:"link"` // (Optional) Custom link information, when it is used, configuration Host/Port/User/Pass/Name are ignored.
MaxIdleConnCount int `json:"maxidle"` // (Optional) Max idle connection configuration for underlying connection pool.
MaxOpenConnCount int `json:"maxopen"` // (Optional) Max open connection configuration for underlying connection pool.
MaxConnLifetime time.Duration `json:"maxlifetime"` // (Optional) Max connection TTL configuration for underlying connection pool.
}
// configs is internal used configuration object.
@ -142,24 +143,19 @@ func (c *Core) SetMaxConnLifetime(d time.Duration) {
// String returns the node as string.
func (node *ConfigNode) String() string {
if node.LinkInfo != "" {
return node.LinkInfo
}
return fmt.Sprintf(
`%s@%s:%s,%s,%s,%s,%s,%v,%d-%d-%d`,
`%s@%s:%s,%s,%s,%s,%s,%v,%d-%d-%d#%s`,
node.User, node.Host, node.Port,
node.Name, node.Type, node.Role, node.Charset, node.Debug,
node.MaxIdleConnCount,
node.MaxOpenConnCount,
node.MaxConnLifetime,
node.LinkInfo,
)
}
// SetDebug enables/disables the debug mode.
func (c *Core) SetDebug(debug bool) {
if c.debug.Val() == debug {
return
}
c.debug.Set(debug)
}
@ -178,6 +174,21 @@ func (c *Core) GetPrefix() string {
return c.prefix
}
// GetGroup returns the group string configured.
func (c *Core) GetGroup() string {
return c.group
}
// SetDryRun enables/disables the DryRun feature.
func (c *Core) SetDryRun(dryrun bool) {
c.dryrun.Set(dryrun)
}
// GetDryRun returns the DryRun value.
func (c *Core) GetDryRun() bool {
return c.dryrun.Val()
}
// SetSchema changes the schema for this database connection object.
// Importantly note that when schema configuration changed for the database,
// it affects all operations on the database object in the future.

View File

@ -13,6 +13,7 @@ package gdb
import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/text/gstr"
@ -187,9 +188,10 @@ func (d *DriverMssql) Tables(schema ...string) (tables []string, err error) {
// TableFields retrieves and returns the fields information of specified table of current schema.
func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
table = gstr.Trim(table)
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
panic("function TableFields supports only single table operations")
return nil, errors.New("function TableFields supports only single table operations")
}
checkSchema := d.DB.GetSchema()
if len(schema) > 0 && schema[0] != "" {

View File

@ -8,8 +8,10 @@ package gdb
import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/text/gstr"
_ "github.com/go-sql-driver/mysql"
@ -33,6 +35,10 @@ func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) {
var source string
if config.LinkInfo != "" {
source = config.LinkInfo
// Custom changing the schema in runtime.
if config.Name != "" {
source, _ = gregex.ReplaceString(`/([\w\.\-]+)+`, "/"+config.Name, source)
}
} else {
source = fmt.Sprintf(
"%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true&parseTime=true&loc=Local",
@ -77,16 +83,20 @@ func (d *DriverMysql) Tables(schema ...string) (tables []string, err error) {
return
}
// TableFields retrieves and returns the fields information of specified table of current schema.
// TableFields retrieves and returns the fields information of specified table of current
// schema.
//
// Note that it returns a map containing the field name and its corresponding fields.
// As a map is unsorted, the TableField struct has a "Index" field marks its sequence in the fields.
// As a map is unsorted, the TableField struct has a "Index" field marks its sequence in
// the fields.
//
// It's using cache feature to enhance the performance, which is never expired util the process restarts.
// It's using cache feature to enhance the performance, which is never expired util the
// process restarts.
func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
table = gstr.Trim(table)
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
panic("function TableFields supports only single table operations")
return nil, errors.New("function TableFields supports only single table operations")
}
checkSchema := d.schema.Val()
if len(schema) > 0 && schema[0] != "" {

View File

@ -148,9 +148,10 @@ func (d *DriverOracle) Tables(schema ...string) (tables []string, err error) {
// TableFields retrieves and returns the fields information of specified table of current schema.
func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
table = gstr.Trim(table)
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
panic("function TableFields supports only single table operations")
return nil, errors.New("function TableFields supports only single table operations")
}
checkSchema := d.DB.GetSchema()
if len(schema) > 0 && schema[0] != "" {

View File

@ -13,6 +13,7 @@ package gdb
import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/text/gstr"
@ -78,7 +79,6 @@ func (d *DriverPgsql) Tables(schema ...string) (tables []string, err error) {
if err != nil {
return nil, err
}
query := "SELECT TABLENAME FROM PG_TABLES WHERE SCHEMANAME = 'public' ORDER BY TABLENAME"
if len(schema) > 0 && schema[0] != "" {
query = fmt.Sprintf("SELECT TABLENAME FROM PG_TABLES WHERE SCHEMANAME = '%s' ORDER BY TABLENAME", schema[0])
@ -97,9 +97,10 @@ func (d *DriverPgsql) Tables(schema ...string) (tables []string, err error) {
// TableFields retrieves and returns the fields information of specified table of current schema.
func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
table = gstr.Trim(table)
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
panic("function TableFields supports only single table operations")
return nil, errors.New("function TableFields supports only single table operations")
}
table, _ = gregex.ReplaceString("\"", "", table)
checkSchema := d.DB.GetSchema()

View File

@ -12,6 +12,7 @@ package gdb
import (
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/os/gfile"
@ -88,11 +89,11 @@ func (d *DriverSqlite) Tables(schema ...string) (tables []string, err error) {
// TableFields retrieves and returns the fields information of specified table of current schema.
func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
table = gstr.Trim(table)
charL, charR := d.GetChars()
table = gstr.Trim(table, charL+charR)
if gstr.Contains(table, " ") {
panic("function TableFields supports only single table operations")
return nil, errors.New("function TableFields supports only single table operations")
}
checkSchema := d.DB.GetSchema()
if len(schema) > 0 && schema[0] != "" {
checkSchema = schema[0]

View File

@ -147,8 +147,13 @@ func doQuoteWord(s, charLeft, charRight string) string {
}
// doQuoteString quotes string with quote chars. It handles strings like:
// "user", "user u", "user,user_detail", "user u, user_detail ut",
// "user.user u, user.user_detail ut", "u.id asc".
// "user",
// "user u",
// "user,user_detail",
// "user u, user_detail ut",
// "user.user u, user.user_detail ut",
// "u.id, u.name, u.age",
// "u.id asc".
func doQuoteString(s, charLeft, charRight string) string {
array1 := gstr.SplitAndTrim(s, ",")
for k1, v1 := range array1 {
@ -201,10 +206,16 @@ func GetPrimaryKey(pointer interface{}) string {
// GetPrimaryKeyCondition returns a new where condition by primary field name.
// The optional parameter <where> is like follows:
// 123, []int{1, 2, 3}, "john", []string{"john", "smith"}
// g.Map{"id": g.Slice{1,2,3}}, g.Map{"id": 1, "name": "john"}, etc.
// 123 => primary=123
// []int{1, 2, 3} => primary IN(1,2,3)
// "john" => primary='john'
// []string{"john", "smith"} => primary IN('john','smith')
// g.Map{"id": g.Slice{1,2,3}} => id IN(1,2,3)
// g.Map{"id": 1, "name": "john"} => id=1 AND name='john'
// etc.
//
// Note that it returns the given <where> parameter directly if there's the <primary> is empty.
// Note that it returns the given <where> parameter directly if the <primary> is empty
// or length of <where> > 1.
func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondition []interface{}) {
if len(where) == 0 {
return nil
@ -221,6 +232,7 @@ func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondi
}
switch kind {
case reflect.Map, reflect.Struct:
// Ignore the parameter <primary>.
break
default:
@ -236,6 +248,9 @@ func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondi
// The internal handleArguments function might be called twice during the SQL procedure,
// but do not worry about it, it's safe and efficient.
func formatSql(sql string, args []interface{}) (newQuery string, newArgs []interface{}) {
sql = gstr.Trim(sql)
sql = gstr.Replace(sql, "\n", " ")
sql, _ = gregex.ReplaceString(`\s{2,}`, ` `, sql)
return handleArguments(sql, args)
}
@ -396,11 +411,15 @@ func formatWhereKeyValue(db DB, buffer *bytes.Buffer, newArgs []interface{}, key
// underlying driver.
func handleArguments(sql string, args []interface{}) (newSql string, newArgs []interface{}) {
newSql = sql
// insertHolderCount is used to calculate the inserting position for the '?' holder.
insertHolderCount := 0
// Handles the slice arguments.
if len(args) > 0 {
for index, arg := range args {
rv := reflect.ValueOf(arg)
kind := rv.Kind()
var (
rv = reflect.ValueOf(arg)
kind = rv.Kind()
)
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
@ -416,17 +435,25 @@ func handleArguments(sql string, args []interface{}) (newSql string, newArgs []i
for i := 0; i < rv.Len(); i++ {
newArgs = append(newArgs, rv.Index(i).Interface())
}
// It the '?' holder count equals the length of the slice,
// If the '?' holder count equals the length of the slice,
// it does not implement the arguments splitting logic.
// Eg: db.Query("SELECT ?+?", g.Slice{1, 2})
if len(args) == 1 && gstr.Count(newSql, "?") == rv.Len() {
break
}
// counter is used to finding the inserting position for the '?' holder.
counter := 0
var (
counter = 0
replaced = false
)
newSql, _ = gregex.ReplaceStringFunc(`\?`, newSql, func(s string) string {
if replaced {
return s
}
counter++
if counter == index+1 {
if counter == index+insertHolderCount+1 {
replaced = true
insertHolderCount += rv.Len() - 1
return "?" + strings.Repeat(",?", rv.Len()-1)
}
return s
@ -443,11 +470,23 @@ func handleArguments(sql string, args []interface{}) (newSql string, newArgs []i
newArgs = append(newArgs, arg)
continue
}
// It converts the struct to string in default
// if it implements the String interface.
if v, ok := arg.(apiString); ok {
switch v := arg.(type) {
case time.Time, *time.Time:
newArgs = append(newArgs, arg)
continue
// Special handling for gtime.Time.
case gtime.Time:
newArgs = append(newArgs, v.String())
continue
default:
// It converts the struct to string in default
// if it implements the String interface.
if v, ok := arg.(apiString); ok {
newArgs = append(newArgs, v.String())
continue
}
}
newArgs = append(newArgs, arg)

View File

@ -7,6 +7,8 @@
package gdb
import (
"fmt"
"github.com/gogf/gf/text/gregex"
"time"
"github.com/gogf/gf/text/gstr"
@ -26,6 +28,7 @@ type Model struct {
whereHolder []*whereHolder // Condition strings for where operation.
groupBy string // Used for "group by" statement.
orderBy string // Used for "order by" statement.
having []interface{} // Used for "having..." statement.
start int // Used for "select ... start, limit ..." statement.
limit int // Used for "select ... start, limit ..." statement.
option int // Option for extra operation features.
@ -37,6 +40,7 @@ type Model struct {
cacheEnabled bool // Enable sql result cache feature.
cacheDuration time.Duration // Cache TTL duration.
cacheName string // Cache name for custom operation.
unscoped bool // Disables soft deleting features when select/delete operations.
safe bool // If true, it clones and returns a new model object whenever operation done; or else it changes the attribute of current model.
}
@ -58,70 +62,70 @@ const (
)
// Table creates and returns a new ORM model from given schema.
// The parameter <tables> can be more than one table names, like :
// "user", "user u", "user, user_detail", "user u, user_detail ud"
func (c *Core) Table(table string) *Model {
table = c.DB.QuotePrefixTableName(table)
// The parameter <table> can be more than one table names, and also alias name, like:
// 1. Table names:
// Table("user")
// Table("user u")
// Table("user, user_detail")
// Table("user u, user_detail ud")
// 2. Table name with alias: Table("user", "u")
func (c *Core) Table(table ...string) *Model {
tables := ""
if len(table) > 1 {
tables = fmt.Sprintf(
`%s AS %s`, c.DB.QuotePrefixTableName(table[0]), c.DB.QuoteWord(table[1]),
)
} else if len(table) == 1 {
tables = c.DB.QuotePrefixTableName(table[0])
} else {
panic("table cannot be empty")
}
return &Model{
db: c.DB,
tablesInit: table,
tables: table,
tablesInit: tables,
tables: tables,
fields: "*",
start: -1,
offset: -1,
safe: false,
option: OPTION_ALLOWEMPTY,
}
}
// Model is alias of Core.Table.
// See Core.Table.
func (c *Core) Model(table string) *Model {
return c.DB.Table(table)
}
// From is alias of Core.Table.
// See Core.Table.
// Deprecated.
func (c *Core) From(table string) *Model {
return c.DB.Table(table)
func (c *Core) Model(table ...string) *Model {
return c.DB.Table(table...)
}
// Table acts like Core.Table except it operates on transaction.
// See Core.Table.
func (tx *TX) Table(table string) *Model {
table = tx.db.QuotePrefixTableName(table)
return &Model{
db: tx.db,
tx: tx,
tablesInit: table,
tables: table,
fields: "*",
start: -1,
offset: -1,
safe: false,
option: OPTION_ALLOWEMPTY,
}
func (tx *TX) Table(table ...string) *Model {
model := tx.db.Table(table...)
model.db = tx.db
model.tx = tx
return model
}
// Model is alias of tx.Table.
// See tx.Table.
func (tx *TX) Model(table string) *Model {
return tx.Table(table)
}
// From is alias of tx.Table.
// See tx.Table.
// Deprecated.
func (tx *TX) From(table string) *Model {
return tx.Table(table)
func (tx *TX) Model(table ...string) *Model {
return tx.Table(table...)
}
// As sets an alias name for current table.
func (m *Model) As(as string) *Model {
if m.tables != "" {
model := m.getModel()
model.tables = gstr.TrimRight(model.tables) + " AS " + as
split := " JOIN "
if gstr.Contains(model.tables, split) {
// For join table.
array := gstr.Split(model.tables, split)
array[len(array)-1], _ = gregex.ReplaceString(`(.+) ON`, fmt.Sprintf(`$1 AS %s ON`, as), array[len(array)-1])
model.tables = gstr.Join(array, split)
} else {
// For base table.
model.tables = gstr.TrimRight(model.tables) + " AS " + as
}
return model
}
return m

View File

@ -18,19 +18,26 @@ import (
// If the parameter <duration> = 0, which means it never expires.
// If the parameter <duration> > 0, which means it expires after <duration>.
//
// The optional parameter <name> is used to bind a name to the cache, which means you can later
// control the cache like changing the <duration> or clearing the cache with specified <name>.
// The optional parameter <name> is used to bind a name to the cache, which means you can
// later control the cache like changing the <duration> or clearing the cache with specified
// <name>.
//
// Note that, the cache feature is disabled if the model is operating on a transaction.
// Note that, the cache feature is disabled if the model is performing select statement
// on a transaction.
func (m *Model) Cache(duration time.Duration, name ...string) *Model {
model := m.getModel()
model.cacheDuration = duration
if len(name) > 0 {
model.cacheName = name[0]
}
// It does not support cache on transaction.
if model.tx == nil {
model.cacheEnabled = true
}
model.cacheEnabled = true
return model
}
// checkAndRemoveCache checks and removes the cache in insert/update/delete statement if
// cache feature is enabled.
func (m *Model) checkAndRemoveCache() {
if m.cacheEnabled && m.cacheDuration < 0 && len(m.cacheName) > 0 {
m.db.GetCache().Remove(m.cacheName)
}
}

View File

@ -6,7 +6,9 @@
package gdb
import "github.com/gogf/gf/util/gconv"
import (
"strings"
)
// Where sets the condition statement for the model. The parameter <where> can be type of
// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
@ -32,10 +34,22 @@ func (m *Model) Where(where interface{}, args ...interface{}) *Model {
return model
}
// Having sets the having statement for the model.
// The parameters of this function usage are as the same as function Where.
// See Where.
func (m *Model) Having(having interface{}, args ...interface{}) *Model {
model := m.getModel()
model.having = []interface{}{
having, args,
}
return model
}
// WherePri does the same logic as Model.Where except that if the parameter <where>
// is a single condition like int/string/float/slice, it treats the condition as the primary
// key value. That is, if primary key is "id" and given <where> parameter as "123", the
// WherePri function treats it as "id=123", but Model.Where treats it as string "123".
// WherePri function treats the condition as "id=123", but Model.Where treats the condition
// as string "123".
func (m *Model) WherePri(where interface{}, args ...interface{}) *Model {
if len(args) > 0 {
return m.Where(where, args...)
@ -87,9 +101,9 @@ func (m *Model) GroupBy(groupBy string) *Model {
}
// Order sets the "ORDER BY" statement for the model.
func (m *Model) Order(orderBy string) *Model {
func (m *Model) Order(orderBy ...string) *Model {
model := m.getModel()
model.orderBy = m.db.QuoteString(orderBy)
model.orderBy = m.db.QuoteString(strings.Join(orderBy, " "))
return model
}
@ -143,28 +157,3 @@ func (m *Model) Page(page, limit int) *Model {
func (m *Model) ForPage(page, limit int) *Model {
return m.Page(page, limit)
}
// getAll does the query from database.
func (m *Model) getAll(sql string, args ...interface{}) (result Result, err error) {
cacheKey := ""
// Retrieve from cache.
if m.cacheEnabled {
cacheKey = m.cacheName
if len(cacheKey) == 0 {
cacheKey = sql + "/" + gconv.String(args)
}
if v := m.db.GetCache().Get(cacheKey); v != nil {
return v.(Result), nil
}
}
result, err = m.db.DoGetAll(m.getLink(false), sql, m.mergeArguments(args)...)
// Cache the result.
if len(cacheKey) > 0 && err == nil {
if m.cacheDuration < 0 {
m.db.GetCache().Remove(cacheKey)
} else {
m.db.GetCache().Set(cacheKey, result, m.cacheDuration)
}
}
return result, err
}

View File

@ -8,8 +8,17 @@ package gdb
import (
"database/sql"
"fmt"
"github.com/gogf/gf/os/gtime"
)
// Unscoped disables the soft deleting feature.
func (m *Model) Unscoped() *Model {
model := m.getModel()
model.unscoped = true
return model
}
// Delete does "DELETE FROM ... " statement for the model.
// The optional parameter <where> is the same as the parameter of Model.Where function,
// see Model.Where.
@ -22,6 +31,19 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
m.checkAndRemoveCache()
}
}()
condition, conditionArgs := m.formatCondition(false)
return m.db.DoDelete(m.getLink(true), m.tables, condition, conditionArgs...)
var (
fieldNameDelete = m.getSoftFieldNameDelete()
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
)
// Soft deleting.
if !m.unscoped && fieldNameDelete != "" {
return m.db.DoUpdate(
m.getLink(true),
m.tables,
fmt.Sprintf(`%s='%s'`, m.db.QuoteString(fieldNameDelete), gtime.Now().String()),
conditionWhere+conditionExtra,
conditionArgs...,
)
}
return m.db.DoDelete(m.getLink(true), m.tables, conditionWhere+conditionExtra, conditionArgs...)
}

View File

@ -7,12 +7,13 @@
package gdb
import (
"github.com/gogf/gf/container/garray"
"fmt"
"github.com/gogf/gf/container/gset"
"github.com/gogf/gf/text/gstr"
)
// Filter marks filtering the fields which does not exist in the fields of the operated table.
// Note that this function supports only single table operations.
func (m *Model) Filter() *Model {
if gstr.Contains(m.tables, " ") {
panic("function Filter supports only single table operations")
@ -30,25 +31,36 @@ func (m *Model) Fields(fields string) *Model {
}
// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
// Note that this function supports only single table operations.
func (m *Model) FieldsEx(fields string) *Model {
if gstr.Contains(m.tables, " ") {
panic("function FieldsEx supports only single table operations")
}
tableFields, err := m.db.TableFields(m.tables)
if err != nil {
panic(err)
}
if len(tableFields) == 0 {
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
}
model := m.getModel()
model.fieldsEx = fields
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
if m, err := m.db.TableFields(m.tables); err == nil {
model.fields = ""
for k, _ := range m {
if fieldsExSet.Contains(k) {
continue
}
if len(model.fields) > 0 {
model.fields += ","
}
model.fields += k
}
fieldsArray := make([]string, len(tableFields))
for k, v := range tableFields {
fieldsArray[v.Index] = k
}
model.fields = ""
for _, k := range fieldsArray {
if fieldsExSet.Contains(k) {
continue
}
if len(model.fields) > 0 {
model.fields += ","
}
model.fields += k
}
model.fields = model.db.QuoteString(model.fields)
return model
}
@ -59,14 +71,26 @@ func (m *Model) FieldsStr(prefix ...string) string {
if len(prefix) > 0 {
prefixStr = prefix[0]
}
if m, err := m.db.TableFields(m.tables); err == nil {
fieldsArray := garray.NewStrArraySize(len(m), len(m))
for _, field := range m {
fieldsArray.Set(field.Index, prefixStr+field.Name)
}
return fieldsArray.Join(",")
tableFields, err := m.db.TableFields(m.tables)
if err != nil {
panic(err)
}
return ""
if len(tableFields) == 0 {
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
}
fieldsArray := make([]string, len(tableFields))
for k, v := range tableFields {
fieldsArray[v.Index] = k
}
newFields := ""
for _, k := range fieldsArray {
if len(newFields) > 0 {
newFields += ","
}
newFields += prefixStr + k
}
newFields = m.db.QuoteString(newFields)
return newFields
}
// FieldsExStr retrieves and returns fields which are not in parameter <fields> from the table,
@ -78,17 +102,28 @@ func (m *Model) FieldsExStr(fields string, prefix ...string) string {
if len(prefix) > 0 {
prefixStr = prefix[0]
}
if m, err := m.db.TableFields(m.tables); err == nil {
fieldsArray := garray.NewStrArraySize(len(m), len(m))
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
for _, field := range m {
if fieldsExSet.Contains(field.Name) {
continue
}
fieldsArray.Set(field.Index, prefixStr+field.Name)
}
fieldsArray.FilterEmpty()
return fieldsArray.Join(",")
tableFields, err := m.db.TableFields(m.tables)
if err != nil {
panic(err)
}
return ""
if len(tableFields) == 0 {
panic(fmt.Sprintf(`empty table fields for table "%s"`, m.tables))
}
fieldsExSet := gset.NewStrSetFrom(gstr.SplitAndTrim(fields, ","))
fieldsArray := make([]string, len(tableFields))
for k, v := range tableFields {
fieldsArray[v.Index] = k
}
newFields := ""
for _, k := range fieldsArray {
if fieldsExSet.Contains(k) {
continue
}
if len(newFields) > 0 {
newFields += ","
}
newFields += prefixStr + k
}
newFields = m.db.QuoteString(newFields)
return newFields
}

View File

@ -9,8 +9,10 @@ package gdb
import (
"database/sql"
"errors"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/util/gutil"
"reflect"
)
@ -94,6 +96,9 @@ func (m *Model) Data(data ...interface{}) *Model {
// The optional parameter <data> is the same as the parameter of Model.Data function,
// see Model.Data.
func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) {
if len(data) > 0 {
return m.Data(data...).Insert()
}
return m.doInsertWithOption(gINSERT_OPTION_DEFAULT, data...)
}
@ -101,45 +106,10 @@ func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) {
// The optional parameter <data> is the same as the parameter of Model.Data function,
// see Model.Data.
func (m *Model) InsertIgnore(data ...interface{}) (result sql.Result, err error) {
return m.doInsertWithOption(gINSERT_OPTION_IGNORE, data...)
}
// doInsertWithOption inserts data with option parameter.
func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.Result, err error) {
if len(data) > 0 {
return m.Data(data...).Insert()
}
defer func() {
if err == nil {
m.checkAndRemoveCache()
}
}()
if m.data == nil {
return nil, errors.New("inserting into table with empty data")
}
if list, ok := m.data.(List); ok {
// Batch insert.
batch := 10
if m.batch > 0 {
batch = m.batch
}
return m.db.DoBatchInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(list),
option,
batch,
)
} else if data, ok := m.data.(Map); ok {
// Single insert.
return m.db.DoInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(data),
option,
)
}
return nil, errors.New("inserting into table with invalid data type")
return m.doInsertWithOption(gINSERT_OPTION_IGNORE, data...)
}
// Replace does "REPLACE INTO ..." statement for the model.
@ -149,37 +119,7 @@ func (m *Model) Replace(data ...interface{}) (result sql.Result, err error) {
if len(data) > 0 {
return m.Data(data...).Replace()
}
defer func() {
if err == nil {
m.checkAndRemoveCache()
}
}()
if m.data == nil {
return nil, errors.New("replacing into table with empty data")
}
if list, ok := m.data.(List); ok {
// Batch replace.
batch := 10
if m.batch > 0 {
batch = m.batch
}
return m.db.DoBatchInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(list),
gINSERT_OPTION_REPLACE,
batch,
)
} else if data, ok := m.data.(Map); ok {
// Single insert.
return m.db.DoInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(data),
gINSERT_OPTION_REPLACE,
)
}
return nil, errors.New("replacing into table with invalid data type")
return m.doInsertWithOption(gINSERT_OPTION_REPLACE, data...)
}
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the model.
@ -192,35 +132,70 @@ func (m *Model) Save(data ...interface{}) (result sql.Result, err error) {
if len(data) > 0 {
return m.Data(data...).Save()
}
return m.doInsertWithOption(gINSERT_OPTION_SAVE, data...)
}
// doInsertWithOption inserts data with option parameter.
func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.Result, err error) {
defer func() {
if err == nil {
m.checkAndRemoveCache()
}
}()
if m.data == nil {
return nil, errors.New("saving into table with empty data")
return nil, errors.New("inserting into table with empty data")
}
var (
nowString = gtime.Now().String()
fieldNameCreate = m.getSoftFieldNameCreate()
fieldNameUpdate = m.getSoftFieldNameUpdate()
fieldNameDelete = m.getSoftFieldNameDelete()
)
// Batch operation.
if list, ok := m.data.(List); ok {
// Batch save.
batch := gDEFAULT_BATCH_NUM
if m.batch > 0 {
batch = m.batch
}
// Automatic handling for creating/updating time.
if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") {
for k, v := range list {
gutil.MapDelete(v, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
if fieldNameCreate != "" {
v[fieldNameCreate] = nowString
}
if fieldNameUpdate != "" {
v[fieldNameUpdate] = nowString
}
list[k] = v
}
}
return m.db.DoBatchInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(list),
gINSERT_OPTION_SAVE,
option,
batch,
)
} else if data, ok := m.data.(Map); ok {
// Single save.
}
// Single operation.
if data, ok := m.data.(Map); ok {
// Automatic handling for creating/updating time.
if !m.unscoped && (fieldNameCreate != "" || fieldNameUpdate != "") {
gutil.MapDelete(data, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
if fieldNameCreate != "" {
data[fieldNameCreate] = nowString
}
if fieldNameUpdate != "" {
data[fieldNameUpdate] = nowString
}
}
return m.db.DoInsert(
m.getLink(true),
m.tables,
m.filterDataForInsertOrUpdate(data),
gINSERT_OPTION_SAVE,
option,
)
}
return nil, errors.New("saving into table with invalid data type")
return nil, errors.New("inserting into table with invalid data type")
}

Some files were not shown because too many files have changed in this diff Show More